add context menu management

This commit is contained in:
2015-06-23 13:17:05 +00:00
parent a6634c4207
commit 3975981fce
10 changed files with 307 additions and 252 deletions

View File

@@ -1,15 +1,15 @@
requirejs.config({ requirejs.config({
paths: { paths: {
'text' : '../lib/requirejs-text/text', 'text' : '../lib/requirejs-text/text',
'durandal' : '../lib/durandal/js', 'durandal' : '../lib/durandal/js',
'plugins' : '../lib/durandal/js/plugins', 'plugins' : '../lib/durandal/js/plugins',
'transitions' : '../lib/durandal/js/transitions', 'transitions' : '../lib/durandal/js/transitions',
'knockout' : '../lib/knockout.js/knockout', 'knockout' : '../lib/knockout.js/knockout',
'knockout.mapping' : '../lib/bower-knockout-mapping/dist/knockout.mapping.min', 'knockout.mapping' : '../lib/bower-knockout-mapping/dist/knockout.mapping.min',
'knockout.validation' : '../lib/knockout-validation/dist/knockout.validation.min', 'knockout.validation' : '../lib/knockout-validation/dist/knockout.validation.min',
'jquery' : '../lib/jquery/jquery.min', 'jquery' : '../lib/jquery/jquery.min',
'perfect.scrollbar' : '../lib/perfect-scrollbar/js/perfect-scrollbar.jquery', 'perfect.scrollbar' : '../lib/perfect-scrollbar/js/perfect-scrollbar.jquery',
'highlightjs' : '../lib/highlightjs/highlight.pack' 'highlightjs' : '../lib/highlightjs/highlight.pack'
}, },
shim: { shim: {
'knockout.mapping': { 'knockout.mapping': {

View File

@@ -1,15 +1,34 @@
<div class="row-fluid"> <div class="row-fluid">
<aside class="col-md-3" id="filebrowser"> <aside class="col-sm-2 col-md-2">
<div data-bind="widget: {kind:'filebrowser'}"></div> <div data-bind="widget: {kind:'filebrowser'}"></div>
</aside> </aside>
<main class="col-md-6" id="main"> <main class="col-sm-10 col-md-6" id="main">
<pre data-bind="visible: content"> <pre data-bind="visible: content">
<code data-bind="attr: {css: type}, text: content" id="editor"></code> <code data-bind="attr: {css: type}, text: content" id="editor"></code>
</pre> </pre>
<span class="text-center" data-bind="visible: !content()">Open file to view it.</span> <span class="text-center" data-bind="visible: !content()">Open file to view it.</span>
</main> </main>
<aside class="col-md-3" id="helpPane"> <aside class="col-sd-12 col-md-4" id="helpPane">
<h2>Welcome to the filebrowser Durandal widget demo</h2>
<p>
This <a href="" target="_blank">Durandal</a> widget allows to display a folder tree and add some actions to manipulate this items.
<ul>
<li>A clic on a file or folder selects it. Multiple select is possible holding the <kbd>Ctrl</kbd> key</li>
<li>A double-clic opens / closes a folder</li>
<li>A double-clic displays the file content in the editor</li>
<li>
A right-clic opens a context menu with different options :
<ul>
<li>Rename the item</li>
<li>Copy the selected item(s)</li>
<li>Paste the selected item(s)</li>
<li>Create an item (in a folder)</li>
<li>Delete an item (and its components)</li>
</ul>
</li>
</ul>
</p>
</aside> </aside>
</div> </div>

View File

@@ -1,29 +1,32 @@
<div id="filebrowser" class="filebrowser"> <div id="filebrowser">
<!-- ko if: folder() --> <!-- ko if: folder() -->
<!-- ko let: { loopRoot: $data } --> <!-- ko let: { loopRoot: $data } -->
<ul data-bind="template: { name: 'tree-template', foreach: folder().children() }" class="tree-file"></ul> <ul data-bind="template: { name: 'tree-template', foreach: folder().children() }" class="tree-file"></ul>
<!-- /ko --> <!-- /ko -->
<!-- /ko --> <!-- /ko -->
<span data-bind="visible: !folder()" class="text-center">Loading workspace...</span>
<script id="tree-template" type="text/html"> <script id="tree-template" type="text/html">
<!-- ko if: $data.type() === "folder" --> <!-- ko if: $data.type() === "folder" -->
<li class="folder"> <li class="folder">
<i class="fa fa-folder-o" data-bind="attr: {id: 'icon_folder_' + $data.uuid()}"></i> <span data-bind="event: { contextmenu: loopRoot.openContextMenu, dblclick: loopRoot.open, click: loopRoot.select }">
<span data-bind="text: $data.name, event: { dblclick: loopRoot.openFolder, contextmenu: loopRoot.openContextMenu, click: loopRoot.select }"/> <i class="fa fa-folder-o" data-bind="attr: {id: 'icon_folder_' + $data.uuid()}"></i>
<!-- ko text: $data.name --><!-- /ko -->
</span>
<input type="checkbox" data-bind="attr: {id: $data.uuid}" /> <input type="checkbox" data-bind="attr: {id: $data.uuid}" />
<ul data-bind="template: { name: 'tree-template', foreach: $data.children }"></ul> <ul data-bind="template: { name: 'tree-template', foreach: $data.children }"></ul>
</li> </li>
<!-- /ko --> <!-- /ko -->
<!--ko if: $data.type() !== "folder"--> <!--ko if: $data.type() !== "folder"-->
<li data-bind="attr: {'data-id': $data.uuid, 'data-filetype': $data.type()}" class="file"> <li data-bind="attr: {'data-id': $data.uuid, 'data-filetype': $data.type()},
event: { contextmenu: loopRoot.openContextMenu, dblclick: loopRoot.open, click: loopRoot.select }"
class="file">
<!--ko ifnot: $data.type --> <!--ko ifnot: $data.type -->
<i data-bind="attr: {class: 'fa fa-file-o'}"></i> <i data-bind="attr: {class: 'fa fa-file-o'}"></i>
<!-- /ko --> <!-- /ko -->
<!--ko if: $data.type --> <!--ko if: $data.type -->
<i data-bind="attr: {class: 'fa fa-file-' + $data.type() + '-o', title: $data.type()}"></i> <i data-bind="attr: {class: 'fa fa-file-' + $data.type() + '-o', title: $data.type()}"></i>
<!-- /ko --> <!-- /ko -->
<span data-bind="text: $data.name, event: { dblclick: loopRoot.openFile, contextmenu: loopRoot.openContextMenu, click: loopRoot.select }, attr: {'data-extra': $data.extra ? $data.extra : ''}"></span> <span data-bind="text: $data.name, attr: {'data-extra': $data.extra ? $data.extra : ''}"></span>
</li> </li>
<!-- /ko --> <!-- /ko -->
</script> </script>
@@ -31,16 +34,23 @@
<!-- Context menu --> <!-- Context menu -->
<div id="fileBrowserContextMenu" class="dropdown open" data-bind="visible: showContextMenu" tabindex="1"> <div id="fileBrowserContextMenu" class="dropdown open" data-bind="visible: showContextMenu" tabindex="1">
<ul class="dropdown-menu" role="menu" aria-labelledby="contextMenu"> <ul class="dropdown-menu" role="menu" aria-labelledby="contextMenu">
<!-- ko if: selected() != undefined && !selected().extra --> <li data-bind="visible: hasSelectedFolder()" >
<li role="presentation"><a role="menuitem" tabindex="-1" href="#" data-bind="click: newItem">New ...</a></li> <a role="menuitem" tabindex="-1" href="#" data-bind="click: newItem">New ...</a>
<!-- /ko --> </li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#" data-bind="click: openRenamePopup">Rename</a></li> <li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#" data-bind="click: copy">Copy</a></li> <a role="menuitem" tabindex="-1" href="#" data-bind="click: openRenamePopup">Rename</a>
<!-- ko if: selected() != undefined && !selected().extra --> </li>
<li role="presentation" data-bind="css: { 'disabled': !hasCopied()}"><a role="menuitem" tabindex="-1" href="#" data-bind="click: paste">Paste</a></li> <li>
<!-- /ko --> <a role="menuitem" tabindex="-1" href="#" data-bind="click: copy">Copy</a>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#" data-bind="click: openDeletePopup">Delete</a></li> </li>
<li data-bind="css: { 'disabled': !hasCopied()}, visible: hasSelectedFolder()">
<a role="menuitem" tabindex="-1" href="#" data-bind="click: paste">Paste</a>
</li>
<li>
<a role="menuitem" tabindex="-1" href="#" data-bind="click: openDeletePopup">Delete</a>
</li>
</ul> </ul>
</div> </div>
<!-- End of Context menu --> <!-- End of Context menu -->
</div> </div>

View File

@@ -17,56 +17,61 @@ define(['durandal/app', 'durandal/composition', 'plugins/http',
}; };
ko.virtualElements.allowedBindings['let'] = true; ko.virtualElements.allowedBindings['let'] = true;
var ctor = function() { }, var ctor = function() {},
selected = ko.observableArray(), selected = ko.observable(),
showContextMenu = ko.observable(false), showContextMenu = ko.observable(),
copied = ko.observable(undefined), copied = ko.observable(undefined),
folder = ko.observable(ko.mapping.fromJS({children: []})), folder = ko.observable(ko.mapping.fromJS({children: []})),
scrollable = $('#filebrowser'), scrollable = $('#filebrowser');
cachedData;
ctor.prototype.attached = function() {
showContextMenu(false);
$('#filebrowser').perfectScrollbar();
};
ctor.prototype.activate = function(settings) { ctor.prototype.activate = function(settings) {
this.settings = settings; this.settings = settings;
this.selected = selected; this.selected = selected;
this.hasCopied = ko.computed( function() { this.folder = folder;
return copied() !== undefined; this.showContextMenu = showContextMenu;
}); this.hasSelectedFolder = ko.computed(function() {
this.showContextMenu = showContextMenu; return selected() != undefined && selected().type === 'folder';
this.folder = folder; });
scrollable.perfectScrollbar(); this.hasCopied = ko.computed( function() {
return copied() !== undefined;
});
}; };
ctor.prototype.openFile = function(object, event) { ctor.prototype.open = function(object,event) {
var type = arguments[0].extra(); console.log('Opening', object);
http.get(arguments[0].path()).then(function(response) { if( object.type() === 'folder' ) {
app.trigger('filebrowser:open_file', { var id = object.uuid(),
type: type, checkbox = $('input[type=checkbox][id=' + id + ']');
content: response
}); checkbox.prop('checked', !checkbox.prop('checked'));
}); $('#icon_folder_' + id).toggleClass('fa-folder-o').toggleClass('fa-folder-open-o');
$('#filebrowser').perfectScrollbar('update');
} else {
var type = object.extra();
http.get(object.path()).then(function(response) {
app.trigger('filebrowser:open_file', {
type: type,
content: response
});
});
}
}; };
ctor.prototype.select = function(object, event) { ctor.prototype.select = function(object, event) {
if( !event.ctrlKey ) { $('li > span.select').removeClass('select');
$('li > span.select').removeClass('select');
selected.removeAll();
}
$(event.target).toggleClass('select'); $(event.target).toggleClass('select');
selected.push( ko.mapping.fromJS(object) ); selected( ko.mapping.fromJS(object) );
};
ctor.prototype.openFolder = function(event) {
var id = arguments[0].uuid();
console.log('openFolder', id);
$('input[type=checkbox][id=' + id + ']').click();
$('#icon_folder_' + id).toggleClass('fa-folder-o').toggleClass('fa-folder-open-o');
$('#filebrowser').perfectScrollbar('update');
}; };
/** Context Menu **/ /** Context Menu **/
ctor.prototype.openContextMenu = function(object, event) { ctor.prototype.openContextMenu = function(object, event) {
console.log('openContextMenu');
selected( ko.mapping.fromJS(object) );
// Position du menu, calculer la pos pour eviter sortie du viewport // Position du menu, calculer la pos pour eviter sortie du viewport
var posX = event.pageX, var posX = event.pageX,
posY = event.pageY, posY = event.pageY,
@@ -76,16 +81,17 @@ define(['durandal/app', 'durandal/composition', 'plugins/http',
menuWidth = contextMenu.width(), menuWidth = contextMenu.width(),
menuHeight = contextMenu.height(); menuHeight = contextMenu.height();
posX = Math.min(posX - 45, windowWidth - menuWidth - 15); posX = Math.min(posX, windowWidth - menuWidth - 15);
posY = Math.min(posY - 80, windowHeight - menuHeight - 10); posY = Math.min(posY, windowHeight - menuHeight - 10);
// display // affichage
contextMenu.css({ contextMenu.css({
left : posX + 'px', left : posX + 'px',
top : posY + 'px' top : posY + 'px'
}); });
showContextMenu(true);
showContextMenu(true);
}; };
ctor.prototype.openRenamePopup = function(ctor, event) { ctor.prototype.openRenamePopup = function(ctor, event) {
@@ -127,14 +133,14 @@ define(['durandal/app', 'durandal/composition', 'plugins/http',
}); });
}; };
$(document).on('click', function() { $(document).on('click', function() {
showContextMenu(false); showContextMenu(false);
}); });
/** End of Context Menu */ /** End of Context Menu */
http.get('/data/filesystem.json').then(function(response) { http.get('/data/filesystem.json').then(function(response) {
folder(ko.mapping.fromJS(response)); folder(ko.mapping.fromJS(response));
$('#filebrowser').perfectScrollbar(); $('#filebrowser').perfectScrollbar('update');
}); });
return ctor; return ctor;

View File

@@ -7,9 +7,9 @@
"bower-knockout-mapping" : "~2.5.0", "bower-knockout-mapping" : "~2.5.0",
"perfect-scrollbar" : "~0.6.0", "perfect-scrollbar" : "~0.6.0",
"bootstrap" : "~3.3.4", "bootstrap" : "~3.3.4",
"fontawesome" : "~4.3.0", "fontawesome" : "~4.3.0",
"less.js" : "~2.4.0", "less.js" : "~2.4.0",
"lesshat" : "~3.0.2", "lesshat" : "~3.0.2",
"highlightjs" : "~8.4.0" "highlightjs" : "~8.4.0"
} }
} }

View File

@@ -113,6 +113,13 @@
"extra": "less", "extra": "less",
"path": "/style/filebrowser.less" "path": "/style/filebrowser.less"
}, },
{
"name": "global.less",
"uuid": "globalless",
"type": "code",
"extra": "less",
"path": "/style/global.less"
},
{ {
"name": "starterkit.css", "name": "starterkit.css",
"uuid": "starterkitcss", "uuid": "starterkitcss",

View File

@@ -6,8 +6,8 @@
<link rel="stylesheet" href="lib/durandal/css/durandal.css"/> <link rel="stylesheet" href="lib/durandal/css/durandal.css"/>
<link rel="stylesheet" href="lib/perfect-scrollbar/css/perfect-scrollbar.css" /> <link rel="stylesheet" href="lib/perfect-scrollbar/css/perfect-scrollbar.css" />
<link rel="stylesheet" href="lib/highlightjs/styles/github.css" /> <link rel="stylesheet" href="lib/highlightjs/styles/github.css" />
<link rel="stylesheet" href="style/starterkit.css" /> <link rel="stylesheet" href="style/starterkit.css" />
<link rel="stylesheet/less" href="style/global.less" />
<link rel="stylesheet/less" href="style/filebrowser.less" /> <link rel="stylesheet/less" href="style/filebrowser.less" />
<script> <script>
@@ -33,6 +33,12 @@
</div> </div>
</div> </div>
<nav class="navbar navbar-default navbar-fixed-bottom">
<div class="container">
Footer
</div>
</nav>
<script src="lib/requirejs/require.js" data-main="app/main"></script> <script src="lib/requirejs/require.js" data-main="app/main"></script>
</body> </body>

View File

@@ -1,17 +1,14 @@
@import "../lib/lesshat/build/lesshat.less"; @import "../lib/lesshat/build/lesshat.less";
.filebrowser { #filebrowser {
max-height: 500px; max-height: 500px;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
} }
//http://codepen.io/joshnh/pen/Hkqxu
//http://codepen.io/tevko/pen/DdsnK
.folder { .folder {
& > label { & > label {
@@ -50,7 +47,8 @@ ul {
}; };
#fileBrowserContextMenu { #fileBrowserContextMenu {
position: absolute; position: fixed;
z-index: 9999;
} }
.validationMessage { .validationMessage {

9
style/global.less Normal file
View File

@@ -0,0 +1,9 @@
body {
padding-bottom: 100px;
}
#editor {
position: relative;
overflow: hidden;
}