Initial commit

This commit is contained in:
ECAILLE Fabrice (externe)
2017-05-03 16:46:01 +02:00
commit 2e64cb961e
117 changed files with 10765 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
{
"directory" : "lib",
"json" : "bower.json"
}

View File

@@ -0,0 +1,2 @@
lib
.codenvy

View File

@@ -0,0 +1,7 @@
Copyright (c) 2015 Fabrice ECAILLE aka Febbweiss
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,19 @@
# Durandal Filebrowser widget
## What's this widget ?
This [Durandal](http://durandaljs.com/) widget allows to display a folder tree and add some actions to manipulate this items :
* A clic on a file or folder selects it
* A double-clic opens / closes a folder
* A double-clic displays the file content in the editor
* A right-clic opens a context menu with different options :
* Rename the item
* Copy the selected item(s)
* Paste the selected item(s)
* Create an item (in a folder)
* Delete an item (and its components)
#Licence
Source code is under [MIT Licence](http://opensource.org/licenses/mit-license.php)

View File

@@ -0,0 +1,44 @@
requirejs.config({
paths: {
'text' : '../lib/requirejs-text/text',
'durandal' : '../lib/durandal/js',
'plugins' : '../lib/durandal/js/plugins',
'transitions' : '../lib/durandal/js/transitions',
'knockout' : '../lib/knockout.js/knockout',
'knockout.mapping' : '../lib/bower-knockout-mapping/dist/knockout.mapping.min',
'knockout.validation' : '../lib/knockout-validation/dist/knockout.validation.min',
'jquery' : '../lib/jquery/jquery.min',
'perfect.scrollbar' : '../lib/perfect-scrollbar/js/perfect-scrollbar.jquery',
'highlightjs' : '../lib/highlightjs/highlight.pack'
},
shim: {
'knockout.mapping': {
deps: ['knockout'],
exports: 'knockout.mapping'
},
'knockout.validation': {
deps: ['knockout'],
exports: 'knockout.validation'
}
}
});
define(['durandal/system', 'durandal/app'], function (system, app) {
system.debug(true);
app.title = 'File browser Durandal Widget';
app.configurePlugins({
router : true,
dialog : true,
widget : {
kinds: [
'filebrowser'
]
}
});
app.start().then(function() {
app.setRoot('shell');
});
});

View File

@@ -0,0 +1,34 @@
<div class="row-fluid">
<aside class="col-sm-2 col-md-2">
<div data-bind="widget: {kind:'filebrowser'}"></div>
</aside>
<main class="col-sm-10 col-md-6" id="main">
<pre data-bind="visible: content">
<code data-bind="attr: {css: type}, text: content" id="editor"></code>
</pre>
<span class="text-center" data-bind="visible: !content()">Open file to view it.</span>
</main>
<aside class="col-sd-12 col-md-4" id="helpPane">
<h2>Welcome to the filebrowser Durandal widget demo</h2>
<p>
This <a href="http://durandaljs.com/" 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.
<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>
</div>

View File

@@ -0,0 +1,26 @@
define(['durandal/app', 'knockout', 'highlightjs'], function (app, ko) {
var type = ko.observable(),
content = ko.observable();
var sub = app.on('filebrowser:open_file').then(function(message) {
type(message.type);
if( message.type === "json" ) {
content(ko.utils.stringifyJson(message.content));
} else {
content(message.content);
}
hljs.highlightBlock($('#editor')[0]);
}, this);
return {
attached: function () {
hljs.configure({
tabReplace: ' '
});
hljs.initHighlighting();
},
type: type,
content: content
};
});

View File

@@ -0,0 +1,31 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-bind="click: close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">New item</h4>
</div>
<div class="modal-body">
<form data-bind="submit: ok" class="form-horizontal">
<div class="row form-group">
<label class="col-sm-2">Type</label>
<div class="col-sm-10">
<label class="radio-inline">
<input type="radio" name="typeItem" id="typeItemFile" value="file" data-bind="checked: typeItem"> File
</label>
<label class="radio-inline">
<input type="radio" name="typeItem" id="typeItemFolder" value="folder" data-bind="checked: typeItem"> Folder
</label>
</div>
</div>
<div class="row form-group">
<label for="nameInput" class="col-sm-2 control-label">Name</label>
<div class="col-sm-10">
<input data-bind="value: input, valueUpdate: 'afterkeydown'" name="nameInput" class="form-control autofocus"/>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-primary" data-bind="click: ok, visible: isValid">Create</button>
<button class="btn btn-default" data-bind="click: close">Cancel</button>
</div>
</div>

View File

@@ -0,0 +1,34 @@
define(['plugins/dialog', 'knockout', 'knockout.validation'], function (dialog, ko, ko_validation) {
ko.validation = ko_validation;
var NewItemModal = function() {
var self = this;
self.input = ko.observable('').extend({
required: true,
pattern: {
message : 'The name must not contain a \'/\'',
params : '^[^/]+$'
}
});
self.typeItem = ko.observable('file');
self.form = ko.validatedObservable( {input: self.input} );
self.isValid = ko.computed(function() {
return self.form.isValid();
});
};
NewItemModal.prototype.ok = function() {
dialog.close(this, { name: this.input(), type: this.typeItem()});
};
NewItemModal.prototype.close = function() {
dialog.close(this);
};
NewItemModal.show = function(defaultValue){
return dialog.show(new NewItemModal(defaultValue));
};
return NewItemModal;
});

View File

@@ -0,0 +1,18 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-bind="click: close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Rename</h4>
</div>
<div class="modal-body">
<form data-bind="submit: ok" class="form-inline">
<div class="form-group">
<label for="renameInput">New name</label>
<input data-bind="value: input, valueUpdate: 'afterkeydown'" name="renameInput" class="form-control autofocus"/>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-primary" data-bind="click: ok, visible: isValid">Ok</button>
<button class="btn btn-default" data-bind="click: close">Cancel</button>
</div>
</div>

View File

@@ -0,0 +1,34 @@
define(['plugins/dialog', 'knockout', , 'knockout.validation'], function (dialog, ko, ko_validation) {
ko.validation = ko_validation;
var RenameModal = function(defaultValue) {
var self = this;
self.previousName = defaultValue;
self.input = ko.observable(defaultValue).extend({
required: true,
pattern: {
message : 'The name must not contain a \'/\'',
params : '^[^/]+$'
}
});
self.form = ko.validatedObservable( {input: self.input} );
self.isValid = ko.computed(function() {
return self.form.isValid() && self.input() != self.previousName;
});
};
RenameModal.prototype.ok = function() {
dialog.close(this, this.input());
};
RenameModal.prototype.close = function() {
dialog.close(this);
};
RenameModal.show = function(defaultValue){
return dialog.show(new RenameModal(defaultValue));
};
return RenameModal;
});

View File

@@ -0,0 +1,56 @@
<div id="filebrowser">
<!-- ko if: folder() -->
<!-- ko let: { loopRoot: $data } -->
<ul data-bind="template: { name: 'tree-template', foreach: folder().children() }" class="tree-file"></ul>
<!-- /ko -->
<!-- /ko -->
<script id="tree-template" type="text/html">
<!-- ko if: $data.type() === "folder" -->
<li class="folder">
<span data-bind="event: { contextmenu: loopRoot.openContextMenu, dblclick: loopRoot.open, 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}" />
<ul data-bind="template: { name: 'tree-template', foreach: $data.children }"></ul>
</li>
<!-- /ko -->
<!--ko if: $data.type() !== "folder"-->
<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 -->
<i data-bind="attr: {class: 'fa fa-file-o'}"></i>
<!-- /ko -->
<!--ko if: $data.type -->
<i data-bind="attr: {class: 'fa fa-file-' + $data.type() + '-o', title: $data.type()}"></i>
<!-- /ko -->
<span data-bind="text: $data.name, attr: {'data-extra': $data.extra ? $data.extra : ''}"></span>
</li>
<!-- /ko -->
</script>
<!-- Context menu -->
<div id="fileBrowserContextMenu" class="dropdown open" data-bind="visible: showContextMenu" tabindex="1">
<ul class="dropdown-menu" role="menu" aria-labelledby="contextMenu">
<li data-bind="visible: hasSelectedFolder()" >
<a role="menuitem" tabindex="-1" href="#" data-bind="click: newItem">New ...</a>
</li>
<li>
<a role="menuitem" tabindex="-1" href="#" data-bind="click: openRenamePopup">Rename</a>
</li>
<li>
<a role="menuitem" tabindex="-1" href="#" data-bind="click: copy">Copy</a>
</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>
</div>
<!-- End of Context menu -->
</div>

View File

@@ -0,0 +1,147 @@
define(['durandal/app', 'durandal/composition', 'plugins/http',
'jquery', 'knockout', 'knockout.mapping',
'perfect.scrollbar',
'./renameModal', './newItemModal'],
function(app, composition,http, $, ko, ko_mapping, perfectScrollbar, RenameModal, NewItemModal) {
ko.mapping = ko_mapping;
ko.bindingHandlers['let'] = {
'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
// Make a modified binding context, with extra properties, and apply it to descendant elements
var innerContext = bindingContext.extend(valueAccessor());
ko.applyBindingsToDescendants(innerContext, element);
return { controlsDescendantBindings: true };
}
};
ko.virtualElements.allowedBindings['let'] = true;
var ctor = function() {},
selected = ko.observable(),
showContextMenu = ko.observable(),
copied = ko.observable(undefined),
folder = ko.observable(ko.mapping.fromJS({children: []})),
scrollable = $('#filebrowser');
ctor.prototype.attached = function() {
showContextMenu(false);
$('#filebrowser').perfectScrollbar();
};
ctor.prototype.activate = function(settings) {
this.settings = settings;
this.selected = selected;
this.folder = folder;
this.showContextMenu = showContextMenu;
this.hasSelectedFolder = ko.computed(function() {
return selected() != undefined && selected().type === 'folder';
});
this.hasCopied = ko.computed( function() {
return copied() !== undefined;
});
};
ctor.prototype.open = function(object,event) {
console.log('Opening', object);
if( object.type() === 'folder' ) {
var id = object.uuid(),
checkbox = $('input[type=checkbox][id=' + id + ']');
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) {
$('li > span.select').removeClass('select');
$(event.target).toggleClass('select');
selected( ko.mapping.fromJS(object) );
};
/** Context Menu **/
ctor.prototype.openContextMenu = function(object, event) {
// Position du menu, calculer la pos pour eviter sortie du viewport
var posX = event.pageX,
posY = event.pageY,
windowWidth = $(window).width(),
windowHeight = $(window).height(),
contextMenu = $('#fileBrowserContextMenu'),
menuWidth = contextMenu.width(),
menuHeight = contextMenu.height();
posX = Math.min(posX, windowWidth - menuWidth - 15);
posY = Math.min(posY, windowHeight - menuHeight - 10);
// affichage
contextMenu.css({
left : posX + 'px',
top : posY + 'px'
});
showContextMenu(true);
};
ctor.prototype.openRenamePopup = function(ctor, event) {
RenameModal.show(ctor.selected().name()).then(function(response) {
if( response !== undefined ) {
ctor.selected().name(response);
}
});
};
ctor.prototype.openDeletePopup = function(ctor, event) {
app.showMessage(
'Are you sure you want to delete this ' +
(ctor.selected().is_folder ? ' folder and all its content' : 'file') + '?',
'Delete ' + ctor.selected().name(), [ { text: "Yes", value: "yes" }, { text: "No", value: "no" }]).then( function( dialogResult ) {
if( dialogResult === 'yes' ) {
console.log('Deleting', ctor.selected().name());
}
});
};
ctor.prototype.copy = function(ctor, event) {
console.log('Copied', ctor.selected().name());
copied( ctor.selected() );
};
ctor.prototype.paste = function(ctor, event) {
if( copied() !== undefined ) {
console.log('Paste', copied().name(), 'into', ctor.selected().name());
copied( undefined );
}
};
ctor.prototype.newItem = function(ctor, event) {
NewItemModal.show().then(function(response) {
if( response !== undefined ) {
console.log('New item : ' + response.type + ' - ' + response.name );
}
});
};
$(document).on('click', function() {
showContextMenu(false);
});
/** End of Context Menu */
http.get('/data/filesystem.json').then(function(response) {
folder(ko.mapping.fromJS(response));
$('#filebrowser').perfectScrollbar('update');
});
return ctor;
});

View File

@@ -0,0 +1,15 @@
{
"name": "filebrowser-durandal-widget",
"version": "0.0.1-SNAPSHOT",
"dependencies" : {
"durandal" : "~2.1.0",
"knockout-validation" : "~2.0.2",
"bower-knockout-mapping" : "~2.5.0",
"perfect-scrollbar" : "~0.6.0",
"bootstrap" : "~3.3.4",
"fontawesome" : "~4.3.0",
"less.js" : "~2.4.0",
"lesshat" : "~3.0.2",
"highlightjs" : "~8.4.0"
}
}

View File

@@ -0,0 +1,154 @@
{
"uuid":"workspace",
"name":"workspace",
"type":"folder",
"children":[
{
"uuid":"app",
"name":"app",
"type":"folder",
"children":[
{
"uuid":"widgets",
"name":"widgets",
"type":"folder",
"children":[
{
"uuid":"filebrowser",
"name":"filebrowser",
"type":"folder",
"children": [
{
"name":"newItemModal.html",
"uuid":"newItemModalhtml",
"type":"code",
"extra":"html",
"path": "/app/widgets/filebrowser/newItemModal.html"
},
{
"name":"newItemModal.js",
"uuid":"newItemModaljs",
"type":"code",
"extra":"javascript",
"path": "/app/widgets/filebrowser/newItemModal.js"
},
{
"name":"renameModal.html",
"uuid":"renameModalhtml",
"type":"code",
"extra":"html",
"path": "/app/widgets/filebrowser/renameModal.html"
},
{
"name":"renameModal.js",
"uuid":"renameModaljs",
"type":"code",
"extra":"javascript",
"path": "/app/widgets/filebrowser/renameModal.js"
},
{
"name":"view.html",
"uuid":"viewhtml",
"type":"code",
"extra":"html",
"path": "/app/widgets/filebrowser/view.html"
},
{
"name":"viewmodel.js",
"uuid":"viewmodeljs",
"type":"code",
"extra":"javascript",
"path": "/app/widgets/filebrowser/viewmodel.js"
}
]
}
]
},
{
"name":"main.js",
"uuid":"mainjs",
"type":"code",
"extra":"javascript",
"path": "/app/main.js"
},
{
"name":"shell.html",
"uuid":"shellhtml",
"type":"code",
"extra":"html",
"path": "/app/shell.html"
},
{
"name":"shell.js",
"uuid":"shelljs",
"type":"code",
"extra":"javascript",
"path": "/app/shell.js"
}
]
},
{
"uuid":"data",
"name":"data",
"type":"folder",
"children":[
{
"name":"filesystem.json",
"uuid":"filesystemjson",
"type":"code",
"extra":"json",
"path": "/data/filesystem.json"
}
]
},
{
"uuid": "style",
"name": "style",
"type": "folder",
"children": [
{
"name": "filebrowser.less",
"uuid": "filebrowserless",
"type": "code",
"extra": "less",
"path": "/style/filebrowser.less"
},
{
"name": "global.less",
"uuid": "globalless",
"type": "code",
"extra": "less",
"path": "/style/global.less"
},
{
"name": "starterkit.css",
"uuid": "starterkitcss",
"type": "code",
"extra": "less",
"path": "/style/starterkit.css"
}
]
},
{
"uuid": "bowerrc",
"name": ".bowerrc",
"type": "code",
"extra": "json",
"path": "/.bowerrc"
},
{
"uuid": "bowerjson",
"name": "bower.json",
"type": "code",
"extra": "json",
"path": "/bower.json"
},
{
"uuid": "indexhtml",
"name": "index.html",
"type": "code",
"extra": "html",
"path": "/index.html"
}
]
}

View File

@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.css"/>
<link rel="stylesheet" href="lib/fontawesome/css/font-awesome.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/highlightjs/styles/github.css" />
<link rel="stylesheet" href="style/starterkit.css" />
<link rel="stylesheet/less" href="style/global.less" />
<link rel="stylesheet/less" href="style/filebrowser.less" />
<script>
less = {
env: "development"
};
</script>
<script src="lib/less.js/dist/less.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Filebrowser Durandal Widget</a>
</div>
</div>
</nav>
<div id="applicationHost">
<div class="splash">
<h1 class="message">Filebrowser Durandal widget</h1>
<i class="fa fa-spinner fa-2x fa-spin active"></i>
</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>
</body>
</html>

View File

@@ -0,0 +1,56 @@
@import "../lib/lesshat/build/lesshat.less";
#filebrowser {
max-height: 500px;
position: relative;
overflow: hidden;
white-space: nowrap;
}
.folder {
& > label {
cursor: pointer;
}
& input[type=checkbox] {
opacity: 0;
& + ul > li {
display: none;
}
&:checked + ul > li {
display: block;
}
}
}
ul {
&.tree-file {
list-style-type: none;
padding-left: 0;
}
};
.folder > span,
.file > span {
.user-select(none);
&.select {
font-weight: bold;
}
};
#fileBrowserContextMenu {
position: fixed;
z-index: 9999;
}
.validationMessage {
color: red;
}

View File

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

View File

@@ -0,0 +1,52 @@
/*!
* Durandal 2.1.0 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved.
* Available via the MIT license.
* see: http://durandaljs.com or https://github.com/BlueSpire/Durandal for details
*/
.splash {
text-align: center;
margin: 10% 0 0 0;
}
.splash .message {
font-size: 3em;
line-height: 1.5em;
-webkit-text-shadow: rgba(0, 0, 0, 0.5) 0 0 15px;
text-shadow: rgba(0, 0, 0, 0.5) 0 0 15px;
text-transform: uppercase;
}
.splash .fa-spinner {
text-align: center;
display: inline-block;
font-size: 3em;
margin-top: 50px;
}
.page-host {
position: absolute;
left: 0;
right: 0;
top: 50px;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
}
section {
margin: 0 20px;
}
.navbar-nav li.loader {
margin: 12px 6px 0 6px;
visibility: hidden;
}
.navbar-nav li.loader.active {
visibility: visible;
}
.pictureDetail {
max-width: 425px;
}