From fc569bdee88033eaf043cf8cd4fea38e8c96ff9a Mon Sep 17 00:00:00 2001 From: ianb Date: Mon, 13 Jun 2005 06:18:19 +0000 Subject: Added new example application (not completely finished), a file browsing application, with lots of Ajax --- examples/filebrowser/__init__.py | 4 + examples/filebrowser/handlers.py | 32 + examples/filebrowser/icons/1downarrow.png | Bin 0 -> 457 bytes examples/filebrowser/icons/1leftarrow.png | Bin 0 -> 476 bytes examples/filebrowser/icons/1rightarrow.png | Bin 0 -> 492 bytes examples/filebrowser/icons/1uparrow.png | Bin 0 -> 439 bytes examples/filebrowser/icons/editcopy.png | Bin 0 -> 1029 bytes examples/filebrowser/icons/editcut.png | Bin 0 -> 1051 bytes examples/filebrowser/icons/editpaste.png | Bin 0 -> 1035 bytes examples/filebrowser/icons/remove.png | Bin 0 -> 896 bytes examples/filebrowser/js-lib/common.js | 57 ++ examples/filebrowser/js-lib/prototype-1.2.0.js | 767 +++++++++++++++++++++ examples/filebrowser/server.conf | 28 + examples/filebrowser/sitepage.py | 243 +++++++ examples/filebrowser/static/stylesheet.css | 43 ++ examples/filebrowser/templates/copybin.pt | 28 + examples/filebrowser/templates/directory.pt | 42 ++ examples/filebrowser/templates/generic_error.pt | 7 + .../filebrowser/templates/standard_template.pt | 55 ++ examples/filebrowser/templates/view_file.pt | 26 + examples/filebrowser/web/__init__.py | 39 ++ examples/filebrowser/web/copybin.py | 54 ++ examples/filebrowser/web/download.py | 20 + examples/filebrowser/web/index.py | 40 ++ 24 files changed, 1485 insertions(+) create mode 100644 examples/filebrowser/__init__.py create mode 100644 examples/filebrowser/handlers.py create mode 100644 examples/filebrowser/icons/1downarrow.png create mode 100644 examples/filebrowser/icons/1leftarrow.png create mode 100644 examples/filebrowser/icons/1rightarrow.png create mode 100644 examples/filebrowser/icons/1uparrow.png create mode 100644 examples/filebrowser/icons/editcopy.png create mode 100644 examples/filebrowser/icons/editcut.png create mode 100755 examples/filebrowser/icons/editpaste.png create mode 100644 examples/filebrowser/icons/remove.png create mode 100644 examples/filebrowser/js-lib/common.js create mode 100644 examples/filebrowser/js-lib/prototype-1.2.0.js create mode 100644 examples/filebrowser/server.conf create mode 100644 examples/filebrowser/sitepage.py create mode 100644 examples/filebrowser/static/stylesheet.css create mode 100644 examples/filebrowser/templates/copybin.pt create mode 100644 examples/filebrowser/templates/directory.pt create mode 100644 examples/filebrowser/templates/generic_error.pt create mode 100644 examples/filebrowser/templates/standard_template.pt create mode 100644 examples/filebrowser/templates/view_file.pt create mode 100644 examples/filebrowser/web/__init__.py create mode 100644 examples/filebrowser/web/copybin.py create mode 100644 examples/filebrowser/web/download.py create mode 100644 examples/filebrowser/web/index.py (limited to 'examples') diff --git a/examples/filebrowser/__init__.py b/examples/filebrowser/__init__.py new file mode 100644 index 0000000..1cb2d1b --- /dev/null +++ b/examples/filebrowser/__init__.py @@ -0,0 +1,4 @@ +from paste.util.thirdparty import add_package + +add_package('ZopePageTemplates') +add_package('ZPTKit') diff --git a/examples/filebrowser/handlers.py b/examples/filebrowser/handlers.py new file mode 100644 index 0000000..6d28d87 --- /dev/null +++ b/examples/filebrowser/handlers.py @@ -0,0 +1,32 @@ +from paste.util.classinit import ClassInitMeta + +handler_exts = {} + +def get_handler(path): + if path.check(dir=True): + ext_type = 'dir' + else: + ext_type = path.ext + return handler_exts.get(ext, handler_exts['*'])(path) + +class Handler(object): + + __metaclass__ = ClassInitMeta + + abstract = True + exts = () + + def __classinit__(cls, new_args): + if cls.abstract and 'abstract' not in new_args: + cls.abstract = False + if not cls.abstract: + for ext in self.exts: + assert ext not in handler_exts, ( + "Handler %s already registered for extension %r; " + "trying to overwrite with %s" + % (handler_exts[ext], ext, cls)) + handler_exts[ext] = cls + + def __init__(self, path): + self.path = path + diff --git a/examples/filebrowser/icons/1downarrow.png b/examples/filebrowser/icons/1downarrow.png new file mode 100644 index 0000000..a0491b6 Binary files /dev/null and b/examples/filebrowser/icons/1downarrow.png differ diff --git a/examples/filebrowser/icons/1leftarrow.png b/examples/filebrowser/icons/1leftarrow.png new file mode 100644 index 0000000..7e3fda7 Binary files /dev/null and b/examples/filebrowser/icons/1leftarrow.png differ diff --git a/examples/filebrowser/icons/1rightarrow.png b/examples/filebrowser/icons/1rightarrow.png new file mode 100644 index 0000000..3264e13 Binary files /dev/null and b/examples/filebrowser/icons/1rightarrow.png differ diff --git a/examples/filebrowser/icons/1uparrow.png b/examples/filebrowser/icons/1uparrow.png new file mode 100644 index 0000000..c68fd87 Binary files /dev/null and b/examples/filebrowser/icons/1uparrow.png differ diff --git a/examples/filebrowser/icons/editcopy.png b/examples/filebrowser/icons/editcopy.png new file mode 100644 index 0000000..ee19948 Binary files /dev/null and b/examples/filebrowser/icons/editcopy.png differ diff --git a/examples/filebrowser/icons/editcut.png b/examples/filebrowser/icons/editcut.png new file mode 100644 index 0000000..0407aff Binary files /dev/null and b/examples/filebrowser/icons/editcut.png differ diff --git a/examples/filebrowser/icons/editpaste.png b/examples/filebrowser/icons/editpaste.png new file mode 100755 index 0000000..0e3ecff Binary files /dev/null and b/examples/filebrowser/icons/editpaste.png differ diff --git a/examples/filebrowser/icons/remove.png b/examples/filebrowser/icons/remove.png new file mode 100644 index 0000000..d65de9e Binary files /dev/null and b/examples/filebrowser/icons/remove.png differ diff --git a/examples/filebrowser/js-lib/common.js b/examples/filebrowser/js-lib/common.js new file mode 100644 index 0000000..a7e52c7 --- /dev/null +++ b/examples/filebrowser/js-lib/common.js @@ -0,0 +1,57 @@ +Element.hidden = function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + if (element.style.display != 'none') { + return false; + } + } + return true; +} + +function lazyToggle(container, url, options) { + options = ({method: 'get'}).extend(options); + if (Element.hidden(container)) { + Element.show(container); + if (! $(container).lazy_loaded) { + new Ajax.Updater(container, url, {method: 'get'}); + $(container).lazy_loaded = true; + } + if (options.link) { + var newHTML = options.link.getAttribute('shownhtml'); + if (newHTML) { + options.link.setAttribute('hiddenhtml', options.link.innerHTML); + options.link.innerHTML = newHTML; + } + } + } else { + Element.hide(container); + if (options.link) { + var newHTML = options.link.getAttribute('hiddenhtml'); + if (newHTML) { + options.link.innerHTML = newHTML; + } + } + } +} + +function toggle(container, options) { + if (Element.hidden(container)) { + Element.show(container); + if (options && options.link) { + var newHTML = options.link.getAttribute('shownhtml'); + if (newHTML) { + options.link.setAttribute('hiddenhtml', options.link.innerHTML); + options.link.innerHTML = newHTML; + } + } + } else { + Element.hide(container); + if (options && options.link) { + var newHTML = options.link.getAttribute('hiddenhtml'); + if (newHTML) { + options.link.setAttribute('shownhtml', options.link.innerHTML); + options.link.innerHTML = newHTML; + } + } + } +} diff --git a/examples/filebrowser/js-lib/prototype-1.2.0.js b/examples/filebrowser/js-lib/prototype-1.2.0.js new file mode 100644 index 0000000..c872152 --- /dev/null +++ b/examples/filebrowser/js-lib/prototype-1.2.0.js @@ -0,0 +1,767 @@ +/* Prototype: an object-oriented Javascript library, version 1.2.0 + * (c) 2005 Sam Stephenson + * + * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff + * against the source tree, available from the Prototype darcs repository. + * + * Prototype is freely distributable under the terms of an MIT-style license. + * + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.2.0' +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.prototype.extend = function(object) { + for (property in object) { + this[property] = object[property]; + } + return this; +} + +Function.prototype.bind = function(object) { + var method = this; + return function() { + method.apply(object, arguments); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var method = this; + return function(event) { + method.call(object, event || window.event); + } +} + +Number.prototype.toColorPart = function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; +} + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + + this.registerCallback(); + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + return elements; +} + +/*--------------------------------------------------------------------------*/ + +if (!Array.prototype.push) { + Array.prototype.push = function() { + var startLength = this.length; + for (var i = 0; i < arguments.length; i++) + this[startLength + i] = arguments[i]; + return this.length; + } +} + +if (!Function.prototype.apply) { + // Based on code from http://www.youngpup.net/ + Function.prototype.apply = function(object, parameters) { + var parameterStrings = new Array(); + if (!object) object = window; + if (!parameters) parameters = new Array(); + + for (var i = 0; i < parameters.length; i++) + parameterStrings[i] = 'x[' + i + ']'; + + object.__apply__ = this; + var result = eval('obj.__apply__(' + + parameterStrings[i].join(', ') + ')'); + object.__apply__ = null; + + return result; + } +} + +/*--------------------------------------------------------------------------*/ + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + }, + + emptyFunction: function() {} +} + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + parameters: '' + }.extend(options || {}); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = (new Ajax.Base()).extend({ + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + + try { + if (this.options.method == 'get') + url += '?' + this.options.parameters + '&_='; + + this.transport.open(this.options.method, url, true); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.transport.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + this.transport.setRequestHeader('X-Prototype-Version', Prototype.Version); + + if (this.options.method == 'post') { + this.transport.setRequestHeader('Connection', 'close'); + this.transport.setRequestHeader('Content-type', + 'application/x-www-form-urlencoded'); + } + + this.transport.send(this.options.method == 'post' ? + this.options.parameters + '&_=' : null); + + } catch (e) { + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + (this.options['on' + event] || Ajax.emptyFunction)(this.transport); + } +}); + +Ajax.Updater = Class.create(); +Ajax.Updater.prototype = (new Ajax.Base()).extend({ + initialize: function(container, url, options) { + this.container = $(container); + if (! this.container) { + alert('Container ' + container + ' does not exist'); + } + this.setOptions(options); + + if (this.options.asynchronous) { + this.onComplete = this.options.onComplete; + this.options.onComplete = this.updateContent.bind(this); + } + + this.request = new Ajax.Request(url, this.options); + + if (!this.options.asynchronous) + this.updateContent(); + }, + + updateContent: function() { + if (this.options.insertion) { + new this.options.insertion(this.container, + this.request.transport.responseText); + } else { + this.container.innerHTML = this.request.transport.responseText; + } + + if (this.onComplete) { + setTimeout((function() {this.onComplete(this.request)}).bind(this), 10); + } + } +}); + +/*--------------------------------------------------------------------------*/ + +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + $(element).focus(); + $(element).select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disable = 'true'; + } + }, + + focusFirstElement: function(form) { + form = $(form); + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (element.type != 'hidden' && !element.disabled) { + Field.activate(element); + break; + } + } + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return encodeURIComponent(parameter[0]) + '=' + + encodeURIComponent(parameter[1]); + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + var index = element.selectedIndex; + var value = element.options[index].value || element.options[index].text; + return [element.name, (index >= 0) ? value : '']; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + + this.registerCallback(); + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ + getValue: function() { + return Form.serialize(this.element); + } +}); + + +/*--------------------------------------------------------------------------*/ + +document.getElementsByClassName = function(className) { + var children = document.getElementsByTagName('*') || document.all; + var elements = new Array(); + + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var classNames = child.className.split(' '); + for (var j = 0; j < classNames.length; j++) { + if (classNames[j] == className) { + elements.push(child); + break; + } + } + } + + return elements; +} + +/*--------------------------------------------------------------------------*/ + +var Element = { + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = + (element.style.display == 'none' ? '' : 'none'); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + } +} + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content; + + if (this.adjacency && this.element.insertAdjacentHTML) { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.fragment = this.range.createContextualFragment(this.content); + this.insertContent(); + } + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, this.element); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function() { + this.element.insertBefore(this.fragment, this.element.firstChild); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function() { + this.element.appendChild(this.fragment); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, + this.element.nextSibling); + } +}); + +/*--------------------------------------------------------------------------*/ + +var Effect = new Object(); + +Effect.Highlight = Class.create(); +Effect.Highlight.prototype = { + initialize: function(element) { + this.element = $(element); + this.start = 153; + this.finish = 255; + this.current = this.start; + this.fade(); + }, + + fade: function() { + if (this.isFinished()) return; + if (this.timer) clearTimeout(this.timer); + this.highlight(this.element, this.current); + this.current += 17; + this.timer = setTimeout(this.fade.bind(this), 250); + }, + + isFinished: function() { + return this.current > this.finish; + }, + + highlight: function(element, current) { + element.style.backgroundColor = "#ffff" + current.toColorPart(); + } +} + + +Effect.Fade = Class.create(); +Effect.Fade.prototype = { + initialize: function(element) { + this.element = $(element); + this.start = 100; + this.finish = 0; + this.current = this.start; + this.fade(); + }, + + fade: function() { + if (this.isFinished()) { this.element.style.display = 'none'; return; } + if (this.timer) clearTimeout(this.timer); + this.setOpacity(this.element, this.current); + this.current -= 10; + this.timer = setTimeout(this.fade.bind(this), 50); + }, + + isFinished: function() { + return this.current <= this.finish; + }, + + setOpacity: function(element, opacity) { + opacity = (opacity == 100) ? 99.999 : opacity; + element.style.filter = "alpha(opacity:"+opacity+")"; + element.style.opacity = opacity/100 /*//*/; + } +} + +Effect.Scale = Class.create(); +Effect.Scale.prototype = { + initialize: function(element, percent) { + this.element = $(element); + this.startScale = 1.0; + this.startHeight = this.element.offsetHeight; + this.startWidth = this.element.offsetWidth; + this.currentHeight = this.startHeight; + this.currentWidth = this.startWidth; + this.finishScale = (percent/100) /*//*/; + if (this.element.style.fontSize=="") this.sizeEm = 1.0; + if (this.element.style.fontSize.indexOf("em")>0) + this.sizeEm = parseFloat(this.element.style.fontSize); + if(this.element.effect_scale) { + clearTimeout(this.element.effect_scale.timer); + this.startScale = this.element.effect_scale.currentScale; + this.startHeight = this.element.effect_scale.startHeight; + this.startWidth = this.element.effect_scale.startWidth; + if(this.element.effect_scale.sizeEm) + this.sizeEm = this.element.effect_scale.sizeEm; + } + this.element.effect_scale = this; + this.currentScale = this.startScale; + this.factor = this.finishScale - this.startScale; + this.options = arguments[2] || {}; + this.scale(); + }, + + scale: function() { + if (this.isFinished()) { + this.setDimensions(this.element, this.startWidth*this.finishScale, this.startHeight*this.finishScale); + if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.finishScale + "em"; + if(this.options.complete) this.options.complete(this); + return; + } + if (this.timer) clearTimeout(this.timer); + if (this.options.step) this.options.step(this); + this.setDimensions(this.element, this.currentWidth, this.currentHeight); + if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.currentScale + "em"; + this.currentScale += (this.factor/10) /*//*/; + this.currentWidth = this.startWidth * this.currentScale; + this.currentHeight = this.startHeight * this.currentScale; + this.timer = setTimeout(this.scale.bind(this), 50); + }, + + isFinished: function() { + return (this.factor < 0) ? + this.currentScale <= this.finishScale : this.currentScale >= this.finishScale; + }, + + setDimensions: function(element, width, height) { + element.style.width = width + 'px'; + element.style.height = height + 'px'; + } +} + +Effect.Squish = Class.create(); +Effect.Squish.prototype = { + initialize: function(element) { + this.element = $(element); + new Effect.Scale(this.element, 1, { complete: this.hide.bind(this) } ); + }, + hide: function() { + this.element.style.display = 'none'; + } +} + +Effect.Puff = Class.create(); +Effect.Puff.prototype = { + initialize: function(element) { + this.element = $(element); + this.opacity = 100; + this.startTop = this.element.top || this.element.offsetTop; + this.startLeft = this.element.left || this.element.offsetLeft; + new Effect.Scale(this.element, 200, { step: this.fade.bind(this), complete: this.hide.bind(this) } ); + }, + fade: function(effect) { + topd = (((effect.currentScale)*effect.startHeight) - effect.startHeight)/2; + leftd = (((effect.currentScale)*effect.startWidth) - effect.startWidth)/2; + this.element.style.position='absolute'; + this.element.style.top = this.startTop-topd + "px"; + this.element.style.left = this.startLeft-leftd + "px"; + this.opacity -= 10; + this.setOpacity(this.element, this.opacity); + if(navigator.appVersion.indexOf('AppleWebKit')>0) this.element.innerHTML += ''; //force redraw on safari + }, + hide: function() { + this.element.style.display = 'none'; + }, + setOpacity: function(element, opacity) { + opacity = (opacity == 100) ? 99.999 : opacity; + element.style.filter = "alpha(opacity:"+opacity+")"; + element.style.opacity = opacity/100 /*//*/; + } +} + +Effect.Appear = Class.create(); +Effect.Appear.prototype = { + initialize: function(element) { + this.element = $(element); + this.start = 0; + this.finish = 100; + this.current = this.start; + this.fade(); + }, + + fade: function() { + if (this.isFinished()) return; + if (this.timer) clearTimeout(this.timer); + this.setOpacity(this.element, this.current); + this.current += 10; + this.timer = setTimeout(this.fade.bind(this), 50); + }, + + isFinished: function() { + return this.current > this.finish; + }, + + setOpacity: function(element, opacity) { + opacity = (opacity == 100) ? 99.999 : opacity; + element.style.filter = "alpha(opacity:"+opacity+")"; + element.style.opacity = opacity/100 /*//*/; + element.style.display = ''; + } +} + +Effect.ContentZoom = Class.create(); +Effect.ContentZoom.prototype = { + initialize: function(element, percent) { + this.element = $(element); + if (this.element.style.fontSize=="") this.sizeEm = 1.0; + if (this.element.style.fontSize.indexOf("em")>0) + this.sizeEm = parseFloat(this.element.style.fontSize); + if(this.element.effect_contentzoom) { + this.sizeEm = this.element.effect_contentzoom.sizeEm; + } + this.element.effect_contentzoom = this; + this.element.style.fontSize = this.sizeEm*(percent/100) + "em" /*//*/; + if(navigator.appVersion.indexOf('AppleWebKit')>0) { this.element.scrollTop -= 1; }; + } +} diff --git a/examples/filebrowser/server.conf b/examples/filebrowser/server.conf new file mode 100644 index 0000000..7a0dd1b --- /dev/null +++ b/examples/filebrowser/server.conf @@ -0,0 +1,28 @@ +# -*- python -*- Note: this file is in Python syntax + +import os + +app_template = 'wareweb_zpt' +app_name = 'filebrowser' +framework = 'wareweb' +root_path = '/home/ianb/svn.w4py.org/Paste/trunk/examples/filebrowser' +publish_dir = os.path.join(root_path, 'web') +sys_path.extend([ + os.path.dirname(root_path), + '/home/ianb/co/py-lib', + '/home/ianb/co/ZPTKit', + ]) + +## Server options: + +verbose = False +# The name of the server-type to start: +server = 'wsgiutils' +# If true, files will be regularly polled and the server restarted +# if files are modified: +reload = True +# If true, tracebacks will be shown in the browser: +debug = True + +browse_path = os.path.join(root_path, 'test-data') +#browse_path = '/home/ianb/media/music/artist' diff --git a/examples/filebrowser/sitepage.py b/examples/filebrowser/sitepage.py new file mode 100644 index 0000000..10ac1a5 --- /dev/null +++ b/examples/filebrowser/sitepage.py @@ -0,0 +1,243 @@ +import cgi +import itertools +import os +import urllib +from paste.wareweb import * +from paste import wsgilib +from ZPTKit.zptwareweb import ZPTComponent +from py.path import local +import handlers + +class SitePage(Servlet): + + message = Notify() + template = ZPTComponent() + app_name = 'filebrowser' + + def awake(self): + super(SitePage, self).awake(call_setup=False) + self.root = local(self.config['browse_path']) + if 'browser.filepath' in self.environ: + self.pathinfo = self.environ['browser.filepath'] + self.path = self.root.join(self.pathinfo) + else: + self.pathinfo = '' + self.path = None + self.pathurl = URL(self.environ, + script_name=self.app_url, + path_info=self.pathinfo) + self.globalurl = URL(self.environ, + script_name=self.app_url + '/_app', + path_info='') + self.icons = Icon(self.app_url + '/_icons') + self.app_static_url = self.app_url + '/_static' + self.fragment = '_' in self.fields + self.copybin = self.session.setdefault('copybin', {}) + self.update_copybin_display() + self.setup() + + def rootedpath(self, path): + path = str(path) + assert path.startswith(str(self.root)), ( + "Bad path: %r (doesn't start with %r)" % (path, self.root)) + return path[len(str(self.root)):] + + def handler(self, path=None): + path = path or self.file + handler = handlers.get_handler(path) + + def update_copybin_display(self): + files = self.options.copyfiles = [] + for filename, cutcopy in self.copybin.items(): + path = local(self.root).join(filename) + files.append( + {'filename': filename, + 'url': self.app_url + filename, + 'path': path, + 'name': path.basename, + 'cut': cutcopy=='cut', + 'copy': cutcopy=='copy', + 'id': self.pathid('copy_', filename), + }) + files.sort(key=lambda x: x['filename']) + + def pathid(self, prefix, path): + if isinstance(path, local): + path = self.rootedpath(path) + return prefix + path.replace('/', '__') + + def copybutton(self, path, copytype='copy'): + assert copytype in ('copy', 'cut') + copyurl = self.globalurl('copybin', **{ + copytype: self.rootedpath(path)}) + remote = copyurl.remote( + id='copybin', + onComplete='function (req) {new Effect.Highlight(%r)}' + % self.pathid('copy_', path)) + img = self.icons('edit%s.png' % copytype, alt=copytype, + title=copytype) + return remote.link(img) + + def pastebutton(self, path): + copyurl = self.globalurl('copybin', paste=self.rootedpath(path), + back=wsgilib.construct_url(self.environ)) + return '%s' % ( + copyurl, self.icons('editpaste', alt="paste", + title="Paste Files")) + + +class URL(object): + + def __init__(self, environ, script_name=None, path_info=None, + vars=None): + if script_name is None: + script_name = environ['SCRIPT_NAME'] + self.script_name = script_name + if path_info is None: + path_info = environ['PATH_INFO'] + self.path_info = path_info + self.environ = environ + self.vars = vars or {} + + def __str__(self): + return wsgilib.construct_url( + self.environ, with_query_string=True, with_path_info=True, + script_name=self.script_name, path_info=self.path_info, + querystring=urllib.urlencode(self.vars)) + + def __repr__(self): + return '' % self + + def url_without_qs(self): + return wsgilib.construct_url( + self.environ, with_query_string=True, with_path_info=True, + script_name=self.script_name, path_info=self.path_info, + querystring='') + url_without_qs = property(url_without_qs) + + def __getitem__(self, add): + vars = self.vars.copy() + path_info = self.path_info + if add and '=' in add: + name, value = add.split('=', 1) + vars[name] = value + elif add: + if not path_info.endswith('/'): + path_info += '/' + path_info += add + return self.__class__(self.environ, script_name=self.script_name, + path_info=path_info, vars=vars) + + def __call__(self, *args, **kw): + obj = self[None] + for arg in args: + obj = obj[arg] + for name, value in kw.items(): + obj.vars[name] = value + return obj + + def remote(self, id=None, **options): + if id is None: + id = idgen() + return JSRemote(self, id, options) + + def toggle(self, id=None, **options): + if id is None: + id = idgen() + return JSToggle(self, id, options) + + def up(self): + path_info = '/'.join(self.path_info.split('/')[:-1]) + return self.__class__( + self.environ, script_name=self.script_name, + path_info=path_info, vars=self.vars) + + def name(self): + return self.path_info.split('/')[-1] + +_idgen_count = itertools.count() +def idgen(): + return 'node%s' % hex(_idgen_count.next() % 0xffffff)[2:] + +class JSRemote(object): + + def __init__(self, url, id, options): + self.url = url + self.id = id + self.options = options + + def option_js(self, **base): + base.update(self.options) + return '{%s}' % (', '.join([ + '%s: %s' % (name, value) + for name, value in base.items()])) + + def javascript(self): + return "new Ajax.Updater(%r, %r, %s); return false" % ( + self.id, self.url.url_without_qs, + self.option_js(parameters=repr(urllib.urlencode(self.url.vars.items())), + method=repr('get'))) + + javascript = property(javascript) + + def link(self, description, structure=True, **kw): + if not structure: + description = cgi.escape(description, 1) + return '%s' % ( + cgi.escape(str(self.url), 1), cgi.escape(self.javascript, 1), + html_attrs(kw), description) + +class JSToggle(JSRemote): + + def javascript(self): + return "lazyToggle(%r, %r, {link: this, parameters: %r}); return false" % ( + self.id, self.url.url_without_qs, + self.option_js(parameters=repr(urllib.urlencode(self.url.vars.items())), + link='this')) + + javascript = property(javascript) + +def html_attrs(kw): + if not kw: + return '' + return ''.join([ + ' %s="%s"' % (name, cgi.escape(str(value), 1)) + for name, value in kw.items()]) + +class Icon(object): + + def __init__(self, url, attrs=None): + self.url = url + self.attrs = attrs or {} + + def html__get(self): + return '' % ( + self.url, html_attrs(self.attrs)) + html = property(html__get) + + __str__ = html__get + + def copy(self): + return self.__class__(self.url, self.attrs.copy()) + + def __call__(self, *names, **kw): + obj = self.copy() + for name in names: + obj = obj[name] + obj.attrs.update(kw) + return obj + + def __getitem__(self, icon_name): + if '=' in icon_name: + obj = self.copy() + name, value = icon_name.split('=', 1) + value = value.replace('+', ' ') + obj.attrs[name] = value + return obj + else: + return self.__class__(self.url + '/' + icon_name) + +# This protects "from sitepage import *", since we will no longer +# need these variables: +del ZPTComponent +del Notify diff --git a/examples/filebrowser/static/stylesheet.css b/examples/filebrowser/static/stylesheet.css new file mode 100644 index 0000000..5560993 --- /dev/null +++ b/examples/filebrowser/static/stylesheet.css @@ -0,0 +1,43 @@ +body { + font-family: Helvetica, Arial, sans-serif; +} + +fieldset#copybinframe { + width: 40%; + float: right; + padding: 3px; +} + +td { + vertical-align: top; +} + +div.controls { + float: right; + display: inline; +} + +blockquote.subdisplay { + margin-top: 0; + margin-bottom: 0; + margin-right: 0; +} + +div.item { + margin-top: 0; + margin-bottom: 0; +} + +div.odd { + background-color: #eee; +} + +pre { + overflow: auto; +} + +div.notifyMessage { + border: 2px solid black; + background-color: #007700; + color: #ffffff; +} diff --git a/examples/filebrowser/templates/copybin.pt b/examples/filebrowser/templates/copybin.pt new file mode 100644 index 0000000..a2d011c --- /dev/null +++ b/examples/filebrowser/templates/copybin.pt @@ -0,0 +1,28 @@ + + + + + +
+
+ +
+ + (cut) +
+
+ + +
+ + +the bin is empty + + +
diff --git a/examples/filebrowser/templates/directory.pt b/examples/filebrowser/templates/directory.pt new file mode 100644 index 0000000..6215086 --- /dev/null +++ b/examples/filebrowser/templates/directory.pt @@ -0,0 +1,42 @@ + + + + + + +
+
+ + + +
+ +
+ +
+ + +

+There are no files in this directory +

+ + + \ No newline at end of file diff --git a/examples/filebrowser/templates/generic_error.pt b/examples/filebrowser/templates/generic_error.pt new file mode 100644 index 0000000..7b32820 --- /dev/null +++ b/examples/filebrowser/templates/generic_error.pt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/examples/filebrowser/templates/standard_template.pt b/examples/filebrowser/templates/standard_template.pt new file mode 100644 index 0000000..9cf589a --- /dev/null +++ b/examples/filebrowser/templates/standard_template.pt @@ -0,0 +1,55 @@ + + + + + +title + + + + + + + + + +
+clipboard +
+ + +
+
+ +

+ +
+ [This is where the notification messages go] +
+ +
+ +[This page has not customized its "body" slot] + +
+ + + + + + +[This page has not customized its "body" slot] + + + diff --git a/examples/filebrowser/templates/view_file.pt b/examples/filebrowser/templates/view_file.pt new file mode 100644 index 0000000..f0904df --- /dev/null +++ b/examples/filebrowser/templates/view_file.pt @@ -0,0 +1,26 @@ + + + +

+ + +Parent: name
+ + +Mimetype:
+download +

+ +
content
+ + + + + + + \ No newline at end of file diff --git a/examples/filebrowser/web/__init__.py b/examples/filebrowser/web/__init__.py new file mode 100644 index 0000000..0ffdb8b --- /dev/null +++ b/examples/filebrowser/web/__init__.py @@ -0,0 +1,39 @@ +import os +import urllib +from py.path import local +from paste import wsgilib +from paste import httpexceptions +from paste import urlparser + +def urlparser_hook(environ): + if not environ.has_key('filebrowser.base_url'): + environ['filebrowser.base_url'] = environ['SCRIPT_NAME'] + +special_dirs = {} + +for special_dir in ['js-lib', 'icons', 'static']: + special_dirs[special_dir] = urlparser.URLParser( + os.path.join(os.path.dirname(os.path.dirname(__file__)), + special_dir), None) + +special_dirs['app'] = urlparser.URLParser( + os.path.dirname(__file__), 'filebrowser.web') + +def application(environ, start_response): + if environ.get('browser.resolved'): + return special_dirs['app'](environ, start_response) + conf = environ['paste.config'] + path_info = environ.get('PATH_INFO', '') + for special_dir, parser in special_dirs.items(): + if path_info.startswith('/_%s/' % special_dir): + environ['browser.resolved'] = True + wsgilib.path_info_pop(environ) + return parser(environ, start_response) + environ['browser.filepath'] = urllib.unquote(environ['PATH_INFO']) + vars = dict(wsgilib.parse_querystring(environ)) + action = (vars.get('action') or ['index'])[0] + servlet_fn = os.path.join(os.path.dirname(__file__), action + '.py') + servlet_app = urlparser.make_py(environ, servlet_fn) + return servlet_app(environ, start_response) + + diff --git a/examples/filebrowser/web/copybin.py b/examples/filebrowser/web/copybin.py new file mode 100644 index 0000000..b17153f --- /dev/null +++ b/examples/filebrowser/web/copybin.py @@ -0,0 +1,54 @@ +from filebrowser.sitepage import * +from py.path import local +import py + +class copybin(SitePage): + + bins = ['copy', 'cut'] + + def setup(self): + for fn in self.fields.getlist('copy'): + self.copybin[fn] = 'copy' + for fn in self.fields.getlist('cut'): + self.copybin[fn] = 'cut' + for fn in self.fields.getlist('remove'): + if fn in self.copybin: + del self.copybin[fn] + if self.fields.get('clear'): + self.copybin.clear() + if 'paste' in self.fields: + return self.paste() + self.update_copybin_display() + + def paste(self): + if not self.copybin: + self.message.write('No items to paste') + self.redirect(self.fields.back) + return + dest = self.root.join(self.fields.paste) + copied = 0 + moved = 0 + for filename, copytype in self.copybin.items(): + path = self.root.join(filename) + if copytype == 'copy': + copied += 1 + path.copy(dest) + elif copytype == 'cut': + try: + path.move(dest) + except py.error.ENOTEMPTY, e: + self.message.write( + 'Cannot move %s (file by the same name exists' % dest) + else: + moved += 1 + msg = [] + if copied: + msg.append('%i file%s copied' % ( + copied, copied>1 and 's' or '')) + if moved: + msg.append('%i file%s moved' % ( + moved, moved>1 and 's' or '')) + msg = ', '.join(msg) + self.copybin.clear() + self.message.write('%s to %s' % (msg, self.fields.paste)) + self.redirect(self.fields.back) diff --git a/examples/filebrowser/web/download.py b/examples/filebrowser/web/download.py new file mode 100644 index 0000000..a18ebf6 --- /dev/null +++ b/examples/filebrowser/web/download.py @@ -0,0 +1,20 @@ +import mimetypes +import re +from filebrowser.sitepage import SitePage + +bad_regexes = [ + re.compile(r'', re.I+re.S), + re.compile(r'style="[^"]*position:.*?"', re.I+re.S), + ] + +class download(SitePage): + + def setup(self): + mime, _ = mimetypes.guess_type(str(self.path)) + self.set_header('Content-type', mime) + self.view = None + content = self.path.read() + if self.fields.get('html') == 'clean': + for bad_regex in bad_regexes: + content = bad_regex.sub('', content) + self.write(content) diff --git a/examples/filebrowser/web/index.py b/examples/filebrowser/web/index.py new file mode 100644 index 0000000..2a80712 --- /dev/null +++ b/examples/filebrowser/web/index.py @@ -0,0 +1,40 @@ +import mimetypes +from filebrowser.sitepage import SitePage + +class index(SitePage): + + def setup(self): + self.title = 'File: %s' % self.path.basename + self.options.parent = { + 'url': self.pathurl.up(), + 'name': self.pathurl.up().name() or 'root', + } + if self.path.check(dir=1): + self.setup_dir() + else: + self.setup_file() + + def setup_dir(self): + files = [] + for f in self.path.listdir(sort=True): + files.append({ + 'path': f, + 'name': f.basename, + 'url': self.pathurl[f.basename], + 'copyid': self.pathid('copy_', f), + }) + if f.check(dir=1): + files[-1]['name'] += '/' + self.options.files = files + self.view = 'directory.pt' + + def setup_file(self): + self.options.mimetype, _ = mimetypes.guess_type(str(self.path)) + mime = self.options.mimetype + self.view = 'view_file.pt' + if mime and mime.startswith('text/'): + self.options.content = self.path.read() + else: + self.options.content = None + self.options.is_image = mime and mime.startswith('image/') + self.options.use_iframe = mime == 'text/html' -- cgit v1.2.1