You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
331 lines
9.8 KiB
331 lines
9.8 KiB
# plemp.rb - The Plemp! application, create your own on-line pile of junk! |
|
# |
|
# Plemp! is Copyright © 2010 Paul van Tilburg <paul@luon.net> |
|
# |
|
# This program is free software; you can redistribute it and/or modify it under |
|
# the terms of the GNU General Public License as published by the Free Software |
|
# Foundation; either version 2 of the License, or (at your option) any later |
|
# version. |
|
|
|
require "active_record" |
|
require "camping" |
|
require "coderay" |
|
require "fileutils" |
|
require "json" |
|
require "markaby" |
|
require "mime/types" |
|
require "pathname" |
|
|
|
Camping.goes :Plemp |
|
|
|
# We want (somewhat) indent HTML to be produced. |
|
Markaby::Builder.set(:indent, 2) |
|
|
|
# Global variables. |
|
unless defined? BASE_DIR |
|
BASE_DIR = Pathname.new(__FILE__).dirname |
|
PUBLIC_DIR = BASE_DIR + "public" |
|
UPLOAD_DIR = BASE_DIR + "upload" |
|
|
|
Program = "Plemp!" |
|
Version = "0.1" |
|
end |
|
|
|
# = The Plemp module |
|
module Plemp |
|
|
|
# Calls methods at application creation/setup time. |
|
def self.create |
|
Plemp::Models.create_schema |
|
end |
|
|
|
end |
|
|
|
# = The Plemp models |
|
module Plemp::Models |
|
|
|
# = The draggable model class |
|
# |
|
# This class represents an draggable object attached to an uploaded |
|
# file. |
|
class Draggable < Base |
|
end |
|
|
|
# = The basic fields class |
|
# |
|
# Class that connects model classes to database tables. |
|
class BasicFields < V 1.0 |
|
def self.up |
|
create_table Draggable.table_name do |d| |
|
d.string :file |
|
d.integer :left, :top, :z_index |
|
# This gives us created_at and updated_at. |
|
d.timestamps |
|
end |
|
end |
|
|
|
def self.down |
|
drop_table Draggable.table_name |
|
end |
|
end |
|
|
|
end # module Plemp::Models |
|
|
|
# = The Plemp controllers |
|
module Plemp::Controllers |
|
|
|
# = The index controller |
|
# |
|
# Main controller that shows the canvas with the draggable objects. |
|
# |
|
# path:: / |
|
# view:: Views#main |
|
class Index |
|
# Retrieves all draggables and renders the main view. |
|
def get |
|
@draggables = Draggable.all |
|
render :main |
|
end |
|
end |
|
|
|
# = The current controller |
|
# |
|
# Controller accessed through AJAX requests by the main page for getting |
|
# the current (global) state (position) of the draggables. |
|
# |
|
# path:: /current |
|
# view:: N/A |
|
class Current |
|
# Retrieves all draggables with there top/left position and returns |
|
# an Hash in JSON format. |
|
def get |
|
out = Hash.new |
|
Draggable.all.each { |d| out[d.file] = [d.left, d.top] } |
|
$stderr.puts("Current status requested: #{out.to_json}") |
|
out.to_json |
|
end |
|
end |
|
|
|
# = The draggable controller |
|
# |
|
# Controller that provides direct access to the HTML generating code |
|
# for draggable objects. |
|
# |
|
# path:: /draggable/<id> |
|
# view:: views#Draggable |
|
class DraggableX |
|
# Retrieves the draggable with the given _id_ from the database and |
|
# returns the HTML. |
|
def get(id) |
|
@drag = Draggable.find_by_file(id) |
|
if @drag.nil? |
|
@status = "404" |
|
return "Error 404: Unknown draggable ID: #{id}" |
|
end |
|
# Call draggable from the views, but do not wrap the layout. |
|
mab(false) { draggable } |
|
end |
|
end |
|
|
|
# = The position save controller |
|
# |
|
# Controller used through AJAX request by the main page for committing |
|
# position changes of the draggables to the database, i.e. the global |
|
# state. |
|
# |
|
# path:: /savepos/<id>/<top>/<left> |
|
# view:: N/A |
|
class SaveposXXX |
|
# Retrieves the draggable _file_ from the database and updates its |
|
# _top_ and _left_ position. |
|
def post(file, top, left) |
|
[top, left].each { |pos| pos.gsub!(/px$/, '') } |
|
$stderr.puts("Got: file id: #{file} -> top: #{top}, left: #{left}") |
|
drag = Draggable.find_by_file(file) |
|
drag.top = top |
|
drag.left = left |
|
drag.save |
|
"" |
|
end |
|
end |
|
|
|
# = The static controller |
|
# |
|
# Controller to serve static data from either the public or upload |
|
# subdirectory using X-Sendfile. |
|
# |
|
# path:: /static/(public,upload)/<file> |
|
# view:: N/A (X-Sendfile) |
|
class StaticXX |
|
# Determines the MIME type of the file referenced by _path_ and sets |
|
# the suitable headers and X-Sendfile header. The _type_ can be either |
|
# _public_ if the file came with Plemp!, or _upload_ if the file was |
|
# once uploaded to Plemp!. |
|
def get(type, path) |
|
mime_type = MIME::Types.type_for(path).first |
|
@headers['Content-Type'] = mime_type.nil? ? "text/plain" : mime_type.to_s |
|
unless path.include? ".." or not ["public", "upload"].include? type |
|
@headers['X-Sendfile'] = (BASE_DIR + type + path).to_s |
|
else |
|
@status = "403" |
|
"Error 403: Invalid path: #{path}" |
|
end |
|
end |
|
end |
|
|
|
# = The upload controller |
|
# |
|
# Controller to handle uploads from the main site. This can either |
|
# be a file or some pasted text. After upload the controler redirects |
|
# to the main page which includes the just uploaded file. |
|
# |
|
# path:: /upload |
|
# redirect:: Index |
|
class Upload |
|
# Creates a text file from input text or otherise from the supplied |
|
# uploaded temp file. The file name is base on the current timestamp |
|
# plus the appropriate extension. Also creates an Draggable object in |
|
# the database. |
|
def post |
|
# TODO: store the MIME type in the database! |
|
# TODO: in case of text + filename, commit both. |
|
if @input.text.empty? |
|
# Text input has priority over file uploads. |
|
orig_ext = File.extname(@input.file[:filename]).downcase |
|
file_base = Time.now.strftime("%Y%m%d%H%M%S#{orig_ext}") |
|
contents = @input.file[:tempfile].read |
|
else |
|
file_base = Time.now.strftime("%Y%m%d%H%M%S.txt") |
|
contents = @input.text.chomp + "\n" |
|
end |
|
new_file = UPLOAD_DIR + file_base |
|
new_file.open("w") do |f| |
|
f.write(contents) |
|
end |
|
Draggable.create(:file => file_base, |
|
:left => 350, :top => 200, :z_index => 0) |
|
redirect Index |
|
end |
|
end |
|
|
|
end # module Plemp::Controllers |
|
|
|
# The Plemp views |
|
module Plemp::Views |
|
|
|
# The main layout used by Controllers::*#render. Loads the appropriate |
|
# style and JavaScript files. |
|
def layout |
|
xhtml_strict do |
|
head do |
|
title "Plemp!" |
|
link :rel => "stylesheet", :type => "text/css", |
|
:media => "screen", :href => R(StaticXX, "public", "plemp.css") |
|
['prototype', 'scriptaculous', 'dragreg'].each do |js| |
|
script :src => R(StaticXX, "public", "#{js}.js"), |
|
:type => "text/javascript" |
|
end |
|
script :type => "text/javascript" do |
|
"init_plemp();" |
|
end |
|
end |
|
# TODO: move this to init_plemp()? |
|
body(:onLoad => "return setup_draggables();") do |
|
self << yield |
|
end |
|
end |
|
end |
|
|
|
# The main view. Shows the header, the (hidden) add_dialog and the |
|
# cavnas with the draggable objects. |
|
def main |
|
div.header! do |
|
h1 do |
|
div.title! { span Program; span.subtitle! "v#{Version}" } |
|
div.add!(:onClick => "show_add_dialog(); return false;") { "+" } |
|
end |
|
end |
|
add_dialog |
|
draggables |
|
end |
|
|
|
# The add dialog. A hidden dialog only shown when the Plus button/key |
|
# is used. Uses Controllers::Upload to upload a file or pasted text. |
|
def add_dialog |
|
div.add_dialog!(:style => "display:none;") do |
|
div.background {} |
|
form :action => R(Upload), :method => "post", :id => "add_form", |
|
:enctype => "multipart/form-data" do |
|
h2 "Plemp it!" |
|
p "Scribble something below:" |
|
textarea :name => "text", :id => "text", :cols => 60, :rows => 12 |
|
p do |
|
span "… or upload a file:" |
|
input :name => "file", :id => "file", :type => "file" |
|
end |
|
div.right do |
|
input :type => "button", :value => "Cancel", |
|
:onClick => "hide_add_dialog(); return false;" |
|
input :type => "submit", :value => "Upload!" |
|
end |
|
end |
|
end |
|
end |
|
|
|
# The canvas with draggables repsenting the objects in the database. |
|
def draggables |
|
div.draggables! do |
|
@draggables.each { |d| @drag = d; draggable } |
|
end |
|
end # def draggables |
|
|
|
# Creates a draggable HTML (block) element including the actually |
|
# contents of the associated file for Draggbale object _d_. |
|
def draggable |
|
file = UPLOAD_DIR + @drag.file |
|
unless file.exist? |
|
# The associated file of this Draggable object is gone, remove |
|
# the object from the database too. |
|
@drag.destroy |
|
return "" |
|
end |
|
# Determine the MIME type. |
|
file_type = `file --brief --mime-type #{file}`.chomp |
|
mime_type = MIME::Types[file_type].first |
|
if mime_type.nil? |
|
mime_type = MIME::Type.new(file_type) |
|
end |
|
# Create an HTML element based on the type of the Draggable object. |
|
default_style = "left:#{@drag.left}px;top:#{@drag.top}px;z-index:0;display:none" |
|
case mime_type.media_type |
|
when "image" |
|
img.draggable :id => @drag.file, :style => default_style, |
|
:src => R(StaticXX, "upload", file.basename), |
|
:alt => file.basename |
|
when "video" |
|
# HTML5 is not supported by Markaby! |
|
self << \ |
|
" <video class=\"draggable\" id=\"#{@drag.file}\" " + |
|
"style=\"#{default_style}\" " + |
|
"src=\"#{R(StaticXX, "upload", @drag.file)}\" " + |
|
"controls=\"true\">" + "</video>\n" |
|
when "audio" |
|
# HTML5 is not supported by Markaby! |
|
self << |
|
" <audio class=\"draggable\" id=\"#{@drag.file}\" " + |
|
"style=\"#{default_style};height=80px;\" " + |
|
"src=\"#{R(StaticXX, "upload", @drag.file)}\" " + |
|
"controls=\"true\">" + "</audio>\n" |
|
when "text" |
|
div.draggable :id => @drag.file, :style => default_style do |
|
CodeRay.scan_file(file).div(:tab_width => 2, |
|
:line_numbers => :inline) |
|
end |
|
else |
|
span.draggable :id => @drag.file, :style => default_style do |
|
em "#{@drag.file}: Unsupported file type!" |
|
end |
|
end # case |
|
end # def draggable |
|
|
|
end # module Plemp::Views
|
|
|