Browse files

backend: vfs: item icons

backend: vfs: disappearing expansion buttons (like in Windows7)
cli: add file
event broadcasted when vfs changes
bugfixes
  • Loading branch information...
1 parent a1181b1 commit eaa5065787493108b3389b7c3886bfcf77634641 @rejetto committed Jun 14, 2012
Showing with 548 additions and 76 deletions.
  1. +13 −3 admin-server.js
  2. +33 −0 cli.js
  3. +1 −2 file-server.js
  4. +3 −3 lib/extending.js
  5. +1 −0 lib/serving.js
  6. +9 −1 main.js
  7. +2 −17 note.txt
  8. +2 −2 package.json
  9. +2 −1 static/backend.css
  10. +2 −1 static/backend.html
  11. +101 −23 static/backend.js
  12. +13 −3 static/extending.js
  13. +14 −19 static/frontend.js
  14. +273 −0 static/jquery.rule-1.0.2.js
  15. +54 −0 static/misc.js
  16. +1 −1 static/tpl.js
  17. +24 −0 todo.txt
View
16 admin-server.js
@@ -58,7 +58,8 @@ function nodeToObject(fnode, depth, cb) {
delete res.resource;
}
- if (!depth) {
+ if (!depth
+ || !fnode.isFolder()) {
if (cb) cb(res);
return res;
}
@@ -78,7 +79,7 @@ function nodeToObject(fnode, depth, cb) {
SET UP SOCKET.IO
*/
-var io = socket_io.listen(srv);
+var io = exports.io = socket_io.listen(srv);
serving.setupSocketIO(io);
io.sockets.on('connection', function(socket){
@@ -92,7 +93,8 @@ io.sockets.on('connection', function(socket){
socket.on('vfs.set', function onSet(data, cb){
if (!data) return;
vfs.fromUrl(data.uri, function(fnode) {
- fnode.set(data.resource, serving.ioOk.bind(this,cb));
+ fnode.set(data.resource, serving.ioOk.bind(this,cb));
+ vfsChanged(socket, data.uri);
});
});
@@ -111,8 +113,16 @@ io.sockets.on('connection', function(socket){
}
fnode.add(data.resource, function(newNode){
serving.ioOk(cb, {item:nodeToObject(newNode)});
+ vfsChanged(socket, data.uri);
});
});
});
});
+
+vfsChanged = function(socket, uri) {
+ dbg('vfs.changed');
+ [socket.broadcast, require('./file-server').io.sockets].forEach(function(o){
+ o.emit('vfs.changed', {uri:uri});
+ });
+}; // vfsChanged
View
33 cli.js
@@ -0,0 +1,33 @@
+/**
+ * @fileOverview command line interface to the server
+ * @author Massimo Melina <a@rejetto.com>
+ */
+require('./lib/common');
+var socket_io = require('socket.io-client');
+
+// read arguments
+var v = process.argv;
+if (v.length < 3) { // quick help
+ log('Usage: node '+path.basename(v[1])+' <path>');
+ return;
+}
+var fpath = v[2];
+var under = v.length < 4 ? '/' : v[3];
+
+// connect to admin-server
+var socket = socket_io.connect('http://localhost:88');
+socket.on('connect', function () {
+ // ask to add this file
+ socket.emit('vfs.add', {uri:under, resource:fpath, depth:1}, function(data){
+ if (!data) {
+ log('communication error');
+ process.exit(1);
+ }
+ if (!data.ok) {
+ log('error: '+(data.error || 'generic'));
+ process.exit(2);
+ }
+ socket.disconnect();
+ process.exit(0);
+ });
+});
View
3 file-server.js
@@ -41,12 +41,11 @@ srv.on('error', function(err){
SET UP SOCKET.IO
*/
-var io = socket_io.listen(srv);
+var io = exports.io = socket_io.listen(srv);
serving.setupSocketIO(io);
io.sockets.on('connection', function(socket){
//** sequences like these may be better with Step(). Try
socket.on('get list', function onGetList(data, cb){
- dbg('get list', arguments);
vfs.fromUrl(data.path, function(fnode) {
getReplyForFolder(fnode, serving.ioOk.bind(this,cb));
});
View
6 lib/extending.js
@@ -27,7 +27,7 @@ String.prototype.format = function() {
var args = arguments;
if (typeof args[0] == 'object')
args = args[0];
- return this.replace(/\{([_ a-z0-9]+)(\|([^}]+))?\}/gi, function(){
+ return this.replace(/\{([-_ a-z0-9]+)(\|([^}]+))?\}/gi, function(){
var ret = args[arguments[1]];
var par = arguments[3];
if (par) {
@@ -105,13 +105,13 @@ function extendObject(key, value) {
});
} // extendObject
-extendObject('keyOf', function(value) {
+extendObject('getKeyOf', function(value) {
for (var i in this)
if (this.hasOwnProperty(i)
&& this[i] === value)
return i;
return null;
-}); // Object.keyOf
+}); // Object.getKeyOf
extendObject('forEach', function(cb) {
for (var i in this)
View
1 lib/serving.js
@@ -70,6 +70,7 @@ exports.setupSocketIO = function(io) {
io.enable('browser client etag'); // apply etag caching logic based on version number
io.enable('browser client gzip'); // gzip the file
io.set('log level', 1); // reduce logging
+ // this next command is giving a warning. Disabled for now.
/*io.set('transports', [ // enable all transports (optional if you want flashsocket)
'websocket'
, 'flashsocket'
View
10 main.js
@@ -2,9 +2,10 @@
* @author Massimo Melina <a@rejetto.com>
*/
require('./lib/common');
+debug();
GLOBAL.vfs = new vfsLib.Vfs();
//vfs.root.set('C:\\vedere').add('c:\\data\\pics\\fantasy');
-vfs.root.add('2', function(node){ node.add('3') });
+//vfs.root.add('2', function(node){ node.add('3') });
var fileServer = require('./file-server');
var listenOn = {port:8, ip:'0.0.0.0'};
@@ -14,3 +15,10 @@ var adminServer = require('./admin-server');
var adminOn = {port:88, ip:'127.0.0.1'};
adminServer.start(adminOn);
+
+// still trying
+function debug(){
+ require('net').createServer(function(sock){
+ require('repl').start('debug> ', sock, undefined, true);
+ }).listen('6969');
+}
View
19 note.txt
@@ -1,16 +1,10 @@
-=== DOING
-backend: vfs manipulation
- add single file
-try to document a little
- http://en.wikipedia.org/wiki/JSDoc
-
=== FEASIBILITY
distribution: how? size?
https://github.com/wearefractal/npkg
npm install hfs
system integration
- some of these features are expected to not be possible with Node, but must be verified. Those will be
- achieved with a different executable communicating with node possibly via socket.
+ some of these features are expected to not be possible with Node, but must be verified.
+ Those who are not possible will be achieved with a different executable communicating with node possibly via socket.
background notifications
Windows' tray icons
automatic handling starting/restaring of the server
@@ -102,12 +96,3 @@ http://superdit.com/2011/06/01/extjs-simple-file-browser/
- icon
- ? message when not allowed
- permissions and options have default values that are enforced when not assigned, to not clutter the stored data structure
-
-=== TO DO
-what wiki to use for development, github or rejetto.com?
-ensure the web apps are perfectly working offline, don't rely on online resources (or do it just as an option or gracefully degrade)
-modules that could be useful
- for better performance on socket.io exchanges https://github.com/pgriess/node-msgpack
- mime https://github.com/bentomas/node-mime
- most used reloader https://github.com/remy/nodemon
- browse the whole list at https://github.com/joyent/node/wiki/modules
View
4 package.json
@@ -5,7 +5,7 @@
"file server",
"http server"
],
- "version": "0.1.1",
+ "version": "0.1.2",
"homepage": "http://www.rejetto.com/hfs",
"author": "Massimo Melina <a@rejetto.com> (http://rejetto.com)",
"repository": {
@@ -23,7 +23,7 @@
"main": "./main.js",
"dependencies": {
"socket.io": "0.9.5",
- "cloneextend": "0.0.3"
+ "cloneextend": "0.0.3",
"async": "0.1.18"
},
"bundleDependencies": [
View
3 static/backend.css
@@ -7,7 +7,8 @@ a img { border:0; } /* conform IE */
#vfs li.hovered { background:#eee; }
#vfs .expand-button { margin-right:0.5em; cursor:pointer; }
#vfs .expand-button.hovered { background-color:#ddd; }
-#vfs li.collapsed ul { display:none; }
+#vfs li.collapsed>ul { display:none; }
#vfs .no-children { color:#888; }
#vfs .no-children:before,
#vfs .no-children:after { content:"..."; }
+#vfs li .icon img { height:1.5em; margin-right:0.5em; vertical-align:middle; }
View
3 static/backend.html
@@ -8,6 +8,7 @@
<!--[if lt IE 9 ]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]-->
<script src='/socket.io/socket.io.js'></script>
<script src='/~/jquery.js'></script>
+ <script src='/~/jquery.rule-1.0.2.js'></script>
<script src='/~/extending.js'></script>
<script src='/~/misc.js'></script>
<script src='/~/backend.js'></script>
@@ -18,7 +19,7 @@
<h2>Virtual File System</h2>
<div id='vfs-buttons'>
<button id='bind'>bind to disk</button>
- <button id='addFolder'>add folder</button>
+ <button id='addItem'>add item</button>
</div>
<div id='vfs'>
</div>
View
124 static/backend.js
@@ -1,17 +1,32 @@
var socket = io.connect(window.location.origin);
var tpl = {
- item: "<li><span class='expand-button'></span><span class='label'></span></li>",
+ item: "<li>"
+ +"<span class='expand-button'></span>"
+ +"<span class='icon'></span>"
+ +"<span class='label'></span>"
+ +"</li>",
noChildren: "<span class='no-children'>nothing</span>",
};
$(function(){ // dom ready
$(tpl.item).appendTo($('<ul>').appendTo('#vfs')); // create the root element
+
+ // hide expansion button
+ expansionCss = $.rule('#vfs .expand-button { opacity:0 }').appendTo('style')[0].style;
+
vfsUpdateButtons();
setupEventHandlers();
socket.on('connect', function(){ // socket ready
reloadVFS();
});
+ socket.on('vfs.changed', function(data){
+ if (!data) return; // something wrong
+ var it = getItemFromURI(data.uri);
+ if (!it) return; // not in the visible tree: ignore
+ if (!isExpanded(it)) return; // not expanded, we don't see its content, no need to reload
+ reloadVFS(it);
+ });
});
/* display a dialog for input.
@@ -51,12 +66,13 @@ function itemBind() {
});
} // itemBind
-function addFolder() {
+function addItem() {
var it = getFirstSelectedFolder() || getRootItem();
inputBox('Enter name or path', function(s){
if (!s) return;
socket.emit('vfs.add', { uri:getURIfromItem(it), resource:s }, function(result){
if (result.ok) {
+ setExpanded(it);
addItemUnder(it, result.item);
vfsSelect(result.item);
}
@@ -65,14 +81,15 @@ function addFolder() {
}
});
});
-} // addFolder
+} // addItem
function setupEventHandlers() {
+
$('#vfs').click(function(){
vfsSelect(null); // deselect
});
$('#bind').click(itemBind);
- $('#addFolder').click(addFolder);
+ $('#addItem').click(addItem);
$('#vfs li').live({
click: function(ev){
ev.stopImmediatePropagation();
@@ -91,6 +108,7 @@ function setupEventHandlers() {
ev.stopImmediatePropagation();
$('#vfs li.hovered').removeClass('hovered');
$(this).addClass('hovered');
+ $('#vfs .expand-button').fadeIn();
},
mouseout: function(ev){
ev.stopImmediatePropagation();
@@ -102,9 +120,9 @@ function setupEventHandlers() {
ev.stopImmediatePropagation();
removeBrowserSelection();
var li = $(ev.target).closest('li');
- var collapse = li.hasClass('expanded');
- setCollapsed(li, collapse);
- if (!collapse) {
+ var mustExpand = !isExpanded(li);
+ setExpanded(li, mustExpand);
+ if (mustExpand) {
reloadVFS(li);
}
},
@@ -116,6 +134,11 @@ function setupEventHandlers() {
$(this).removeClass('hovered');
}
});
+ $('#vfs').hover(function(){
+ animate(expansionCss, 'opacity', 1, {duration:0.2});
+ }, function(){
+ animate(expansionCss, 'opacity', 0);
+ });
} // setupEventHandlers
function vfsSelect(el) {
@@ -155,11 +178,45 @@ function getRoot() { return $('#vfs li:first'); }
function getRootItem() { return getRoot().data('item'); }
+/** return children in the same format of the parameter: jQuery, array of items or array of HTMLElements */
+function getChildren(x) {
+ if (!x) return false;
+ var item = !!x.element;
+ var children = $(x.element || x).find('ul>li');
+ if (x instanceof $) return children;
+ var res = [];
+ children.each(function(idx,el){
+ res.push(item ? asItem(el) : el);
+ });
+ return res;
+} // getChildren
+
+/** return first child of "parent" matching "pattern" */
+function getChildAs(parent, pattern) {
+ assert(parent && parent.element && pattern, 'arguments');
+ var res = false;
+ $(parent.element).find('ul>li').each(function(idx,el){
+ var child = asItem(el)
+ for (var k in pattern)
+ if (pattern.hasOwnProperty(k))
+ if (pattern[k] !== child[k])
+ return; // this child does not match, skip
+ res = child; // found! mark it
+ return false; // stop it
+ });
+ return res;
+} // getChildAs
+
function getParentFromItem(it) {
return $(it.element).parent().closest('li').data('item');
} // getParentFromItem
-function isFolder(it) { return it && it.itemKind.endsBy('folder') }
+function isFolder(it) {
+ it = asItem(it);
+ return it && it.itemKind.endsBy('folder');
+} // isFolder
+
+function isExpanded(x) { return asLI(x).hasClass('expanded') }
function getURIfromItem(item) {
item = asItem(item);
@@ -169,6 +226,18 @@ function getURIfromItem(item) {
+ (p && isFolder(item) ? '/' : '');
} // getURIfromItem
+/** get the item from the uri, but only if it's currently in our tree */
+function getItemFromURI(uri) {
+ var run = getRootItem();
+ for (var i=0, a=uri.split('/'), l=a.length; i<l; ++i) {
+ var name = a[i];
+ if (!name) continue;
+ run = getChildAs(run, {name:name});
+ if (!run) return false;
+ }
+ return run;
+} // getItemFromURI
+
function vfsUpdateButtons() {
var it = getFirstSelectedItem();
enableButton('bind', it && it.itemKind == 'virtual folder');
@@ -183,8 +252,8 @@ function reloadVFS(item) {
e.find('ul').remove(); // remove possible children
socket.emit('vfs.get', { uri:item ? getURIfromItem(item) : '/', depth:1 }, function(data){
if (!data) return;
- setItem(e, data);
- setCollapsed(e, false);
+ setItem(e, log(data));
+ setExpanded(e);
var ul = e.find('ul');
if (!data.children.length) {
$(tpl.noChildren).appendTo(ul);
@@ -196,8 +265,7 @@ function reloadVFS(item) {
});
} // reloadVFS
-/** util function for those functions who want to accept several types but work with the $(LI)
- */
+/** util function for those functions who want to accept several types but work with the $(LI) */
function asLI(x) {
x = !x ? null
: x.element ? x.element
@@ -206,40 +274,50 @@ function asLI(x) {
return x ? $(x) : x;
} // asLI
-/** util function for those functions who want to accept several types but work with the $(LI)
- */
+/** util function for those functions who want to accept several types but work with the $(LI) */
function asItem(x) {
return !x ? null
: x.element ? x
: (x instanceof HTMLElement || x instanceof $) ? $(x).closest('li').data('item')
: null;
} // asItem
-function setCollapsed(item, state) {
+function setExpanded(item, state) {
var li = asLI(item);
if (!li) return;
+ var button = li.find('.expand-button:first');
if (state == undefined) state = true;
- li.addClass(state ? 'collapsed' : 'expanded')
- .removeClass(!state ? 'collapsed' : 'expanded')
- .find('.expand-button:first').text(state ? '' : '');
+ button.text(state ? '' : '');
+ if (!isFolder(li)) {
+ button.css({visibility:'hidden'});
+ return;
+ }
+ button.css({visibility:''});
+ li.addClass(state ? 'expanded' : 'collapsed')
+ .removeClass(!state ? 'expanded' : 'collapsed');
// deal with the container of children
var ul = li.find('ul:first');
if (state) {
- ul.remove();
- }
- else {
if (!ul.size())
ul = $('<ul>').appendTo(li);
+ }
+ else {
+ ul.remove();
}
return true;
-} // setCollapsed
+} // setExpanded
function setItem(element, item) {
var li = asLI(element);
item.element = li[0]; // bind the item to the html element, so we can get to it
li.data({item:item});
li.find('.label:first').text(item.name);
- setCollapsed(li);
+ var icon = isFolder(item) ? 'folder' : item.itemKind;
+ if (icon=='file') {
+ icon = nameToType(item.name) || icon;
+ }
+ li.find('.icon:first').html("<img src='"+getIconURI(icon)+"' />");
+ setExpanded(li, false);
return element;
} // setItem
View
16 static/extending.js
@@ -12,7 +12,7 @@ String.prototype.format = function() {
var args = arguments;
if (typeof args[0] == 'object')
args = args[0];
- return this.replace(/\{([_ a-z0-9]+)(\|([^}]+))?\}/gi, function(){
+ return this.replace(/\{([-_ a-z0-9]+)(\|([^}]+))?\}/gi, function(){
var ret = args[arguments[1]];
var par = arguments[3];
if (par) {
@@ -90,13 +90,13 @@ function extendObject(key, value) {
});
} // extendObject
-extendObject('keyOf', function(value) {
+extendObject('getKeyOf', function(value) {
for (var i in this)
if (this.hasOwnProperty(i)
&& this[i] === value)
return i;
return null;
-}); // Object.keyOf
+}); // Object.getKeyOf
extendObject('forEach', function(cb) {
for (var i in this)
@@ -124,7 +124,17 @@ extendObject('extend', function(from) {
var destination = Object.getOwnPropertyDescriptor(from, name);
Object.defineProperty(dest, name, destination);
}
+ else {
+ dest[name] = from[name];
+ }
});
return this;
}); // Object.extend
+extendObject('getProperties', function(){
+ var res = [];
+ for (var i in this)
+ if (this.hasOwnProperty(i))
+ res.push(this[i]);
+ return res;
+}); // Object.getProperties
View
33 static/frontend.js
@@ -28,10 +28,8 @@ $(function onJQ(){ // dom ready
loadFolder(getURLfolder(), function onFolder(){ // folder ready
- /* support for the BACK button.
- When the user clicks the BACK button, the address bar changes going out of sync with the view.
- We fix it ASAP. I don't know an event for the address bar, so i'm using an setInterval.
- */
+ /* support for the BACK button: when the user clicks the BACK button, the address bar changes going out of
+ sync with the view. We fix it ASAP. I don't know an event for the address bar, so i'm using an setInterval. */
setInterval(function onBackSupport(){
var shouldBe = getURLfolder();
if (currentFolder != shouldBe) {
@@ -41,6 +39,13 @@ $(function onJQ(){ // dom ready
}); // don't redraw
});//socket connect
+
+ socket.on('vfs.changed', function(data){
+ log('vfs.changed');
+ if (log(data).uri === log(currentFolder)) {
+ loadFolder();
+ }
+ });
// item hovering
$('.item-link').live({
@@ -70,7 +75,7 @@ $(function onJQ(){ // dom ready
});//dom ready
// ask the server for the items list of the specified folder, then sort and display
-function loadFolder(path, cb) {
+function loadFolder(path /** optional */, cb /** optional */) {
if (path) currentFolder = path;
$('#folder').text(currentFolder);
socket.emit('get list', { path:currentFolder }, function onGetList(reply){
@@ -97,7 +102,7 @@ function convertList(serverReply) {
case undefined: // no type is default type: file
o.type = 'file';
case 'file': // for files, calculate specific type
- o.type = nameToType(k) || 'file';
+ o.type = nameToType(k) || o.type;
break;
case 'link':
o.url = o.resource;
@@ -147,8 +152,9 @@ function redrawItems() {
// build the DOM for the single item, applying possible filtering functions
function addItem(it) {
+ it.extend({'icon-file':getIconURI(it.icon)}); // make this additions before the hook, so it can change these too
TPL('onObjectItem', it); // custom treatment, especially mode-based
- $('<li>').append(TPL.item.format(it))
+ $('<li>').append(TPL.item.format(log(it)))
.appendTo('#items')
.find('a.item-link').click(itemClickHandler);
} // addItem
@@ -160,7 +166,7 @@ function itemClickHandler() {
if (h.substr(-1) == '/') {
if (location.pathname != '/') { // reloads the page to have a neater URL
location = '/#'+h.substr(1);
- return;
+ return false;
}
location.hash = (h.startsBy(location.pathname)) ? h.substr(location.pathname.length) : h;
@@ -173,17 +179,6 @@ function itemClickHandler() {
return true;
} // itemClickHandler
-function nameToType(name) {
- switch (getFileExt(name).low()) {
- case 'jpg': case 'jpeg': case 'gif': case 'png':
- return 'image';
- case 'avi': case 'mpg': case 'mp4': case 'mov':
- return 'video';
- default:
- return '';
- }
-} // nameToType
-
function updateMode(v){
// if no value is passed, then read it from the DOM, otherwise write it in the DOM
if (!v || !v.length) v = $('#mode').val();
View
273 static/jquery.rule-1.0.2.js
@@ -0,0 +1,273 @@
+/*!
+ * jQuery.Rule - Css Rules manipulation, the jQuery way.
+ * Copyright (c) 2007-2011 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
+ * Dual licensed under MIT and GPL.
+ * Date: 02/7/2011
+ * Compatible with jQuery 1.2+
+ *
+ * @author Ariel Flesler
+ * @version 1.0.2
+ *
+ * @id jQuery.rule
+ * @param {Undefined|String|jQuery.Rule} The rules, can be a selector, or literal CSS rules. Many can be given, comma separated.
+ * @param {Undefined|String|DOMElement|jQuery) The context stylesheets, all of them by default.
+ * @return {jQuery.Rule} Returns a jQuery.Rule object.
+ *
+ * @example $.rule('p,div').filter(function(){ return this.style.display != 'block'; }).remove();
+ *
+ * @example $.rule('div{ padding:20px;background:#CCC}, p{ border:1px red solid; }').appendTo('style');
+ *
+ * @example $.rule('div{}').append('margin:40px').css('margin-left',0).appendTo('link:eq(1)');
+ *
+ * @example $.rule().not('div, p.magic').fadeOut('slow');
+ *
+ * @example var text = $.rule('#screen h2').add('h4').end().eq(4).text();
+ */
+;(function( $ ){
+
+ /**
+ * Notes
+ * Some styles and animations might fail, please report it.
+ * The plugin needs a style node to stay in the DOM all along to temporarily hold rules. DON'T TOUCH IT.
+ * Opera requires this style to have alternate in the rel to allow disabling it.
+ * Rules in IE don't have .parentStylesheet. We need to find it each time(slow).
+ * Animations need close attention. Programatically knowing which rule has precedence, would require a LOT of work.
+ * This plugin adds $.rule and also 4 methods to $.fn: ownerNode, sheet, cssRules and cssText
+ * Note that rules are not directly inside nodes, you need to do: $('style').sheet().cssRules().
+ */
+
+ var storageNode = $('<style rel="alternate stylesheet" type="text/css" />').appendTo('head')[0],//we must append to get a stylesheet
+ sheet = storageNode.sheet ? 'sheet' : 'styleSheet',
+ storage = storageNode[sheet],//css rules must remain in a stylesheet for IE and FF
+ rules = storage.rules ? 'rules' : 'cssRules',
+ remove = storage.deleteRule ? 'deleteRule' : 'removeRule',
+ owner = storage.ownerNode ? 'ownerNode' : 'owningElement',
+ reRule = /^([^{]+)\{([^}]*)\}/m,
+ reStyle = /([^:]+):([^;}]+)/;
+
+ storage.disabled = true;//let's ignore your rules
+
+ var $rule = $.rule = function( r, c ){
+ if(!(this instanceof $rule))
+ return new $rule( r, c );
+
+ this.sheets = $rule.sheets(c);
+ if( r && reRule.test(r) )
+ r = $rule.clean( r );
+ if( typeof r == 'object' && !r.exec ) {
+ setArray( this, r.get ? r.get() : r.splice ? r : [r] );
+ } else {
+ setArray( this, this.sheets.cssRules().get() );
+ if (r)
+ return this.filter( r );
+ }
+ return this;
+ };
+
+ $.extend( $rule, {
+ sheets:function( c ){
+ var o = c;
+ if( typeof o != 'object' )
+ o = $.makeArray(document.styleSheets);
+ o = $(o).not(storage);//skip our stylesheet
+ if( typeof c == 'string' )
+ o = o.ownerNode().filter(c).sheet();
+ return o;
+ },
+ rule:function( str ){
+ if( str.selectorText )/* * */
+ return [ '', str.selectorText, str.style.cssText ];
+ return reRule.exec( str );
+ },
+ appendTo:function( r, ss, skip ){
+ switch( typeof ss ){//find the desired stylesheet
+ case 'string': ss = this.sheets(ss);
+ case 'object':
+ if( ss[0] ) ss = ss[0];
+ if( ss[sheet] ) ss = ss[sheet];
+ if( ss[rules] ) break;//only if the stylesheet is valid
+ default:
+ if( typeof r == 'object' ) return r;//let's not waist time, it is parsed
+ ss = storage;
+ }
+ var p;
+ if( !skip && (p = this.parent(r)) )//if this is an actual rule, and it's appended.
+ r = this.remove( r, p );
+
+ var rule = this.rule( r );
+ if( ss.addRule )
+ ss.addRule( rule[1], rule[2]||';' );//IE won't allow empty rules
+ else if( ss.insertRule )
+ ss.insertRule( rule[1] + '{'+ rule[2] +'}', ss[rules].length );
+
+ return ss[rules][ ss[rules].length - 1 ];//return the added/parsed rule
+ },
+ remove:function( r, p ){
+ p = p || this.parent(r);
+ if( p != storage ){//let's save some unnecesary cycles.
+ var i = p ? $.inArray( r, p[rules] ) : -1;
+ if( i != -1 ){//if not stored before removal, IE will crash eventually, and some rules in FF get messed up
+ r = this.appendTo( r, 0 /*storage*/, true );//is faster and shorter to imply storage
+ p[remove](i);
+ }
+ }
+ return r;
+ },
+ clean:function( r ){
+ return $.map( r.split('}'), function( txt ){
+ if( txt )
+ return $rule.appendTo( txt + '}' /*, storage*/ );//parse the string, storage implied
+ });
+ },
+ parent:function( r ){//CSS rules in IE don't have parentStyleSheet attribute
+ if( typeof r == 'string' || !$.browser.msie )//if it's a string, just return undefined.
+ return r.parentStyleSheet;
+
+ var par;
+ this.sheets().each(function(){
+ if( $.inArray(r, this[rules]) != -1 ){
+ par = this;
+ return false;
+ }
+ });
+ return par;
+ },
+ outerText:function( rule ){
+ return !rule || !rule.selectorText ? '' : [rule.selectorText+'{', '\t'+rule.style.cssText,'}'].join('\n').toLowerCase();
+ },
+ text:function( rule, txt ){
+ if( txt !== undefined )
+ rule.style.cssText = txt;
+ return !rule ? '' : rule.style.cssText.toLowerCase();
+ }
+ });
+
+ $rule.fn = $rule.prototype = {
+ pushStack:function( rs, sh ){
+ var ret = $rule( rs, sh || this.sheets );
+ ret.prevObject = this;
+ return ret;
+ },
+ end:function(){
+ return this.prevObject || $rule(0,[]);
+ },
+ filter:function( s ){
+ var o;
+ if( !s ) s = /./;//just keep them all.
+ if( s.split ){
+ o = $.trim(s).toLowerCase().split(/\s*,\s*/);
+ s = function(){
+ var s = this.selectorText || '';
+ return !!$.grep( s.toLowerCase().split(/\s*,\s*/), function( sel ){
+ return $.inArray( sel, o ) != -1;
+ }).length;
+ };
+ }else if( s.exec ){//string regex, or actual regex
+ o = s;
+ s = function(){ return o.test(this.selectorText); };
+ }
+ return this.pushStack($.grep( this, function( e, i ){
+ return s.call( e, i );
+ }));
+ },
+ add:function( rs, c ){
+ return this.pushStack( $.merge(this.get(), $rule(rs, c)) );
+ },
+ is:function( s ){
+ return !!(s && this.filter( s ).length);
+ },
+ not:function( n, c ){
+ n = $rule( n, c );
+ return this.filter(function(){
+ return $.inArray( this, n ) == -1;
+ });
+ },
+ append:function( s ){
+ var rules = this, rule;
+ $.each( s.split(/\s*;\s*/),function(i,v){
+ if(( rule = reStyle.exec( v ) ))
+ rules.css( rule[1], rule[2] );
+ });
+ return this;
+ },
+ text:function( txt ){
+ return !arguments.length ? $rule.text( this[0] )
+ : this.each(function(){ $rule.text( this, txt ); });
+ },
+ outerText:function(){
+ return $rule.outerText(this[0]);
+ }
+ };
+
+ $.each({
+ ownerNode:owner,//when having the stylesheet, get the node that contains it
+ sheet:sheet, //get the stylesheet from the node
+ cssRules:rules //get the rules from the stylesheet.
+ },function( m, a ){
+ var many = a == rules;//the rules need some more processing
+ $.fn[m] = function(){
+ return this.map(function(){
+ return many ? $.makeArray(this[a]) : this[a];
+ });
+ };
+ });
+
+ $.fn.cssText = function(){
+ return this.filter('link,style').eq(0).sheet().cssRules().map(function(){
+ return $rule.outerText(this);
+ }).get().join('\n');
+ };
+
+ $.each('remove,appendTo,parent'.split(','),function( k, f ){
+ $rule.fn[f] = function(){
+ var args = $.makeArray(arguments), that = this;
+ args.unshift(0);
+ return this.each(function( i ){
+ args[0] = this;
+ that[i] = $rule[f].apply( $rule, args ) || that[i];
+ });
+ };
+ });
+
+ $.each(('each,index,get,size,eq,slice,map,attr,andSelf,css,show,hide,toggle,'+
+ 'queue,dequeue,stop,animate,fadeIn,fadeOut,fadeTo').split(','),function( k, f ){
+ $rule.fn[f] = $.fn[f];
+ });
+
+ // this function has been pulled in from jQuery 1.4.1, because it is an internal function and has been dropped as of 1.4.2
+ function setArray(rule, elems) {
+ rule.length = 0;
+ Array.prototype.push.apply( rule, elems );
+ }
+
+ var curCSS = $.curCSS;
+ $.curCSS = function( e, a ){//this hack is still quite exprimental
+ return ('selectorText' in e ) ?
+ e.style[a] || $.prop( e, a=='opacity'? 1 : 0,'curCSS', 0, a )//TODO: improve these defaults
+ : curCSS.apply(this,arguments);
+ };
+
+ /**
+ * Time to hack jQuery.data for animations.
+ * Only IE really needs this, but to keep the behavior consistent, I'll hack it for all browsers.
+ * TODO: This kind of id doesn't seem to be good enough
+ * TODO: Avoid animating similar rules simultaneously
+ * TODO: Avoid rules' precedence from interfering on animations ?
+ */
+ $rule.cache = {};
+ var mediator = function( original ){
+ return function( elm ){
+ var id = elm.selectorText;
+ if( id )
+ arguments[0] = $rule.cache[id] = $rule.cache[id] || {};
+ return original.apply( $, arguments );
+ };
+ };
+ $.data = mediator( $.data );
+ $.removeData = mediator( $.removeData );
+
+ $(window).unload(function(){
+ $(storage).cssRules().remove();//empty our rules bin
+ });
+
+})( jQuery );
View
54 static/misc.js
@@ -68,6 +68,17 @@ function getFileExt(path) {
return v ? v[1] : '';
} // getFileExt
+function nameToType(name) {
+ switch (getFileExt(name).low()) {
+ case 'jpg': case 'jpeg': case 'gif': case 'png':
+ return 'image';
+ case 'avi': case 'mpg': case 'mp4': case 'mov':
+ return 'video';
+ default:
+ return '';
+ }
+} // nameToType
+
// tries to access a deep property of object, following array "path" as for properties names
function getDeep(obj, path) {
for (var i=0, a=path, l=a.length; obj && i<l; ++i) {
@@ -103,6 +114,49 @@ round = function(v, decimals) {
function cmp(a,b) { return a>b ? 1 : a<b ? -1 : 0 }
+function getIconURI(icon) { return "/~/icons/files/"+icon+".png"; }
+
+function animate(object, property, endValue, options) {
+ options = options||{};
+ var freq = options.freq||30; // Hz
+ var duration = options.duration||0.5; // seconds
+ var steps = freq*duration; // number of steps to the end
+ if (!object || !property || !steps) return;
+ var trackingKey = 'animation_running_'+property;
+ var endParameter = { canceling:1 }; // at this stage, calling end() will inform the callback that we have been canceled by another animation
+
+ var end = function(){
+ object[property] = endValue;
+ clearInterval(object[trackingKey]);
+ delete object[trackingKey];
+ if (typeof options.onEnd == 'function') {
+ options.onEnd(endParameter);
+ }
+ };
+
+ if (object[trackingKey]) {
+ end(); // terminate previous
+ }
+ // we passed the stage of the cancellation
+ delete endParameter.canceling;
+ if (Object.keys(endParameter).length === 0) {
+ endParameter = undefined;
+ }
+
+ var from;
+ var current;
+ var inc;
+ // track this operation. It would be nice to do it without touching, but we'd need an hash/id of the object, and i don't know a way to get it.
+ object[trackingKey] = setInterval(function(){
+ if (typeof from === 'undefined') { // initialize
+ from = current = Number(object[property]);
+ inc = (endValue-from)/steps;
+ }
+ object[property] = current += inc;
+ if (! --steps) end();
+ }, 1000/freq);
+} // animate
+
/* CURRENTLY UNUSED
// reports how many pixels the element is exceeding the viewport, on the right side
View
2 static/tpl.js
@@ -1,7 +1,7 @@
assert(TPL, 'tpl');
TPL.item =
- "<a href='{url}' class='item-link'><img src='/~/icons/files/{icon}.png' /><span class='item-label'>{label}</span></a>"
+ "<a href='{url}' class='item-link'><img src='{icon-file}' /><span class='item-label'>{label}</span></a>"
+"<div class='item-details'><div class='item-details-wrapper'>"
+"<div class='item-type'>{type}</div>"
+"<div class='item-size'>{size}</div>"
View
24 todo.txt
@@ -0,0 +1,24 @@
+=== DONE
+
+=== DOING
+debugging console via telnet: still working very bad
+improve in-source documentation
+ http://en.wikipedia.org/wiki/JSDoc
+
+=== NEXT
+backend: vfs: key navigation: up, down, right(expand), left(collapse)
+backend: vfs: overlay icon to signalise temp nodes (based on nodeKind)
+backend: replace $.rule with a custom function, then remove jquery plugin
+backend: rename expand-button in expansion-button
+
+=== AFTER
+handle non-working IO connection for both frontend and backend
+backend: vfs: complete manipulation
+consider coffee-script
+what wiki to use for development, github or rejetto.com?
+ensure the web apps are perfectly working offline, don't rely on online resources (or do it just as an option or gracefully degrade)
+modules that could be useful
+ for better performance on socket.io exchanges https://github.com/pgriess/node-msgpack
+ mime https://github.com/bentomas/node-mime
+ most used reloader https://github.com/remy/nodemon
+ browse the whole list at https://github.com/joyent/node/wiki/modules

0 comments on commit eaa5065

Please sign in to comment.