block by nitaku 5952d82b1846525207f5

Virtual Filesystem UI

Full Screen

A simple User Interface for a virtual filesystem (a la Google Drive). Folders and files can be in more than one folder (e.g. Home > Pictures > goofy.png is also in Home > Documents).

index.js

// Generated by CoffeeScript 1.10.0
(function() {
  var breadcrumb, bttf_cover, cd, cd__, cut_path, cwd, documents, goofy, index, movies, open_folders, photos, pictures, redraw, root, up;

  goofy = {
    id: 0,
    name: 'goofy.png'
  };

  bttf_cover = {
    id: 1,
    name: 'Back to the Future (Cover).jpg'
  };

  photos = {
    id: 10,
    name: 'Photos',
    subfolders: [],
    files: [
      {
        id: 11,
        name: 'me.jpg'
      }
    ]
  };

  pictures = {
    id: 2,
    name: 'Pictures',
    subfolders: [photos],
    files: [
      goofy, {
        id: 8,
        name: 'mickey.png'
      }, {
        id: 9,
        name: 'donald.png'
      }, bttf_cover
    ]
  };

  movies = {
    id: 3,
    name: 'Movies',
    subfolders: [],
    files: [
      {
        id: 6,
        name: 'Back to the Future.mkv'
      }, bttf_cover
    ]
  };

  documents = {
    id: 4,
    name: 'Documents',
    subfolders: [photos],
    files: [goofy]
  };

  root = {
    id: 5,
    subfolders: [pictures, movies, documents],
    files: [
      {
        id: 7,
        name: 'vmlinuz'
      }
    ]
  };

  index = function(node) {
    if (node.index != null) {
      return;
    }
    node.index = {};
    node.subfolders.forEach(function(sf) {
      return node.index[sf.name] = sf;
    });
    node.files.forEach(function(f) {
      return node.index[f.name] = f;
    });
    return node.subfolders.forEach(function(sf) {
      return index(sf);
    });
  };

  index(root);

  open_folders = [root];

  cd = function(sf_name) {
    open_folders.push(open_folders[open_folders.length - 1].index[sf_name]);
    return redraw();
  };

  cd__ = function() {
    if (open_folders.length > 1) {
      open_folders.pop();
      return redraw();
    }
  };

  cut_path = function(folder) {
    if (open_folders.length === 1 || open_folders[open_folders.length - 1].id === folder.id) {
      redraw();
      return;
    }
    open_folders.pop();
    return cut_path(folder);
  };

  cwd = d3.select('#cwd');

  up = d3.select('#up_btn');

  breadcrumb = d3.select('#breadcrumb');

  up.on('click', function() {
    return cd__();
  });

  redraw = function() {
    var cwd_data, files, path_items, subfolders;
    cwd_data = open_folders[open_folders.length - 1];
    subfolders = cwd.selectAll('.subfolder').data(cwd_data.subfolders, function(d) {
      return d.id;
    });
    subfolders.enter().append('div').attr({
      "class": 'subfolder node'
    }).html(function(d) {
      return '<i class="icon fa fa-fw fa-folder"></i> ' + d.name;
    }).on('click', function(d) {
      return cd(d.name);
    });
    subfolders.exit().remove();
    files = cwd.selectAll('.file').data(cwd_data.files, function(d) {
      return d.id;
    });
    files.enter().append('div').attr({
      "class": 'file node'
    }).html(function(d) {
      return '<i class="icon fa fa-fw fa-file-o"></i> ' + d.name;
    });
    files.exit().remove();
    path_items = breadcrumb.selectAll('.path_item').data(open_folders, function(d) {
      return d.id;
    });
    path_items.enter().append('div').attr({
      "class": 'path_item'
    }).html(function(d) {
      return '<span>' + (d.name != null ? d.name : 'Home' + '</span>');
    }).on('click', function(d) {
      return cut_path(d);
    });
    return path_items.exit().remove();
  };

  redraw();

}).call(this);

index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Virtual Filesystem UI</title>
    <script src="//d3js.org/d3.v3.min.js"></script>
    <link rel="stylesheet" href="index.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
  </head>
  <body>
    <div id="bar">
      <button id="up_btn"><i class="icon fa fa-level-up"></i></button>
      <div id="breadcrumb"></div>
    </div>
    <div id="cwd"></div>
    <script src="index.js"></script>
  </body>
</html>

index.coffee

goofy = {id: 0, name: 'goofy.png'}
bttf_cover = {id: 1, name: 'Back to the Future (Cover).jpg'}

photos = {
  id: 10,
  name:'Photos',
  subfolders: [],
  files: [{id: 11, name: 'me.jpg'}]
}
pictures = {
  id: 2,
  name:'Pictures',
  subfolders: [photos],
  files: [goofy, {id: 8, name: 'mickey.png'}, {id: 9, name: 'donald.png'}, bttf_cover]
}
movies = {
  id: 3,
  name:'Movies',
  subfolders: [],
  files: [{id: 6, name: 'Back to the Future.mkv'}, bttf_cover]
}
documents = {
  id: 4,
  name:'Documents',
  subfolders: [photos],
  files: [goofy]
}

root = {
  id: 5,
  subfolders: [pictures, movies, documents],
  files: [{id: 7, name: 'vmlinuz'}]
}

index = (node) ->
  if node.index?
    return
  
  node.index = {}
  node.subfolders.forEach (sf) -> node.index[sf.name] = sf
  node.files.forEach (f) -> node.index[f.name] = f
  
  node.subfolders.forEach (sf) ->
    index(sf)

index(root)

# walk = (node, spath) ->
#   if spath.length is 0
#     return node
#   return walk(node.index[spath[0]], spath[1..])

# get_node = (path) ->
#   if path[path.length-1] is '/'
#     path = path[...path.length-1]
#   spath = path.split('/')
#   console.log walk(root, spath[1..])
  
open_folders = [root]

cd = (sf_name) ->
  open_folders.push open_folders[open_folders.length-1].index[sf_name]
  redraw()
  
cd__ = () ->
  if open_folders.length > 1
    open_folders.pop()
    redraw()
    
cut_path = (folder) ->
  if open_folders.length is 1 or open_folders[open_folders.length-1].id is folder.id
    redraw()
    return
  
  open_folders.pop()
  cut_path(folder)
  
# ls = () ->
#   open_folders[open_folders.length-1].subfolders.forEach (sf) ->
#     console.log '['+sf.name+']'
#   open_folders[open_folders.length-1].files.forEach (f) ->
#     console.log f.name
    
# pwd = () ->
#   console.log open_folders.map((f) -> if f.name? then f.name else '').join('/') + '/'

cwd = d3.select('#cwd')
up = d3.select('#up_btn')
breadcrumb = d3.select('#breadcrumb')

up.on 'click', () -> cd__()

redraw = () ->
  cwd_data = open_folders[open_folders.length-1]
  
  
  subfolders = cwd.selectAll('.subfolder')
    .data(cwd_data.subfolders, (d) -> d.id )
    
  subfolders.enter().append('div')
    .attr
      class: 'subfolder node'
    .html (d) -> '<i class="icon fa fa-fw fa-folder"></i> '+d.name
    .on 'click', (d) -> cd d.name
    
  subfolders.exit().remove()
  
  
  files = cwd.selectAll('.file')
    .data(cwd_data.files, (d) -> d.id )
    
  files.enter().append('div')
    .attr
      class: 'file node'
    .html (d) -> '<i class="icon fa fa-fw fa-file-o"></i> '+d.name
    
  files.exit().remove()
  
  
  path_items = breadcrumb.selectAll('.path_item')
    .data(open_folders, (d) -> d.id )
    
  path_items.enter().append('div')
    .attr
      class: 'path_item'
    .html (d) -> '<span>' + if d.name? then d.name else 'Home' + '</span>'
    .on 'click', (d) -> cut_path(d)
    
  path_items.exit().remove()
  
  
redraw()

index.css

html, body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
  font-size: 14px;
  color: #333;
  background: #DDD;
}

#cwd {
  padding: 4px;
}
#bar {
  padding: 6px;
  box-shadow: 0px 0px 6px #777;
  background: #EEE;
}
#bar > * {
  display: inline-block;
}
.path_item {
  display: inline-block;
  margin-right: 6px;
  cursor: pointer;
}
.path_item:not(:first-child)::before {
  content: '\f054';
  margin-right: 6px;
  font-family: 'FontAwesome';
  color: #999;
  font-size: 10px;
}
.path_item:hover > span {
  text-decoration: underline;
}

.node {
  padding: 6px;
  margin: 4px;
  background: white;
}
.subfolder:hover {
  cursor: pointer;
  background: #FFF4DC;
}

#cwd .icon {
  color: #777;
}