Commit ca5735c1 authored by Fabian Heidemann's avatar Fabian Heidemann
Browse files

Integrated bookmark bar. Global getter from public and private workspace.ini...

Integrated bookmark bar. Global getter from public and private workspace.ini and setter to private workspace.ini
parent d31b3b65
......@@ -296,3 +296,27 @@ class FSAjaxController(AbstractController):
if err:
raise MessageException(err)
return {"success": not err}
@cherrypy.expose
@cherrypy.tools.ajax(encoded=True)
def get_from_public_workspaceini(self, section, option):
self.release_session()
fs = self.get('fs')
self.release_database()
return fs.get_from_public_workspaceini(section, option)
@cherrypy.expose
@cherrypy.tools.ajax(encoded=True)
def get_from_private_workspaceini(self, section, option):
self.release_session()
fs = self.get('fs')
self.release_database()
return fs.get_from_private_workspaceini(section, option)
@cherrypy.expose
@cherrypy.tools.ajax(encoded=True)
def set_to_private_workspaceini(self, section, option, value):
self.release_session()
fs = self.get('fs')
self.release_database()
return fs.set_to_private_workspaceini(section, option, value)
\ No newline at end of file
......@@ -29,6 +29,7 @@ class FileBrowserExtension(AbstractExtension):
self.add_js('js/base/helper.js')
self.add_js('js/base/view.js')
self.add_js('js/base/sort.js')
self.add_js('js/base/bookmark.js')
self.add_js('js/base/selections.js')
self.add_js('js/base/views/symbol/view.js')
self.add_js('js/base/views/table/view.js')
......@@ -53,5 +54,6 @@ class FileBrowserExtension(AbstractExtension):
self.add_js('js/menuentries.js')
self.add_css('css/base/base.css')
self.add_css('css/base/bookmark.css')
self.add_css('css/base/views/symbol/symbol.css')
self.add_css('css/base/views/table/table.css')
......@@ -8,18 +8,16 @@
.filebrowser .file-content {
position: absolute;
bottom: 0px;
right: 0px;
top: 0px;
left: 0px;
height: 100%;
overflow: hidden;
border-left: 1px solid #b8b8b8;
}
.fileselector .file-content {
position: relative;
height: 100%;
width: 100%;
overflow: hidden;
border-left: 1px solid #b8b8b8;
}
.file-view-content-container {
......
.bookmark-container {
position: absolute;
left: 0px;
overflow: hidden;
}
.bookmarks {
overflow: scroll;
white-space: nowrap;
}
.bookmark {
margin: 1% 1% 1% 1%;
cursor: pointer;
}
.bookmark:nth-child(even) {
background-color: #e9e9e9;
}
.bookmark:hover {
background-color: rgba(51, 131, 187, 0.7);
color: #ffffff;
}
.bookmark-button {
margin: 1% 1% 1% 1%;
background-color: #ffffff;
border-top: 1px solid #b8b8b8;
border-right: 1px solid #b8b8b8;
border-bottom: 1px solid #b8b8b8;
border-left: 1px solid #b8b8b8;
}
.bookmark-button:hover {
background-color: #e9e9e9;
}
.bookmark:hover > a {
color: #ffffff;
}
.bookmark-glyphicon-edit {
color: rgba(0,0,0,0);
}
.bookmark-glyphicon-edit:hover {
color: rgba(0,0,0,1) !important;
}
.bookmark-glyphicon-remove {
color: rgba(0,0,0,0);
}
.bookmark-glyphicon-remove:hover {
color: rgba(0,0,0,1) !important;
}
\ No newline at end of file
<div class="bookmark-container">
<button class="bookmark-button">
<i class="glyphicon glyphicon-bookmark"></i>
Add bookmark
</button>
<div class="bookmarks">
<p class="bookmark">
<span class="bookmark-name" data-bind="bookmark-name"></span>
<a class="bookmark-glyphicon-edit" title="Edit bookmark"><i class="glyphicon glyphicon-pencil"></i></a>
<a class="bookmark-glyphicon-remove" title="Remove bookmark"><i class="glyphicon glyphicon-remove"></i></a>
</p>
</div>
</div>
\ No newline at end of file
......@@ -2,7 +2,7 @@ var FileBase = Class.extend({
init: function(instance) {
var self = this;
this.instance = instance;
// components
this.view = new FileBaseView(this);
......@@ -12,6 +12,8 @@ var FileBase = Class.extend({
this.menuitems = new FileBaseMenuItems(this);
this.helper = new FileBaseHelper(this);
this.sorting = new FileBaseSorting(this);
this.bookmarkcontainer = new BookmarkContainer(this);
// the workflow object may be extended
this.workflow = {
......@@ -24,27 +26,28 @@ var FileBase = Class.extend({
reverse: [false, false],
lastRefresh: null,
updateState: 0, // 0: idle, >0: last request, -1: running,
// -2: done & requested again, -3: running & requested again
// -2: done & requested again, -3: running & requested again
lazyUpdate: false,
};
// Get the default view from the preferences
viewstring = vispa.device.hasTouch ? "Table" : this.instance.getPreference("View");
this.workflow.currentView = viewstring == "Symbol" ? Symbolview : Tableview;
// buffered refresh events
window.setInterval(function(){
if (self.workflow.updateState == -2 || (self.workflow.updateState>0 &&
($.now()-self.workflow.updateState) > (self.workflow.lazyUpdate?2000:0))) {
// 200ms since "move from/to" upon rename have a 160ms gap (idk why)
window.setInterval(function() {
if (self.workflow.updateState == -2 || (self.workflow.updateState > 0 &&
($.now() - self.workflow.updateState) > (self.workflow.lazyUpdate ? 2000 : 0))) {
// 200ms since "move from/to" upon rename have a 160ms gap (idk why)
self.workflow.updateState = -1
self._updateView()
}
}, 50)
},
setContent: function(node) {
this.bookmarkcontainer.setContainer(node);
this.view.setMainContainer(node);
},
......@@ -65,8 +68,8 @@ var FileBase = Class.extend({
updateView: function(additionalDelay) {
if (this.workflow.updateState >= 0) {
if(additionalDelay !== undefined) { // events may want to wait for more events
this.workflow.updateState =
if (additionalDelay !== undefined) { // events may want to wait for more events
this.workflow.updateState =
Math.max(this.workflow.updateState, $.now() + additionalDelay);
} else { // everyone else wants a fast update
this.workflow.updateState = 1;
......@@ -75,10 +78,10 @@ var FileBase = Class.extend({
this.workflow.updateState = -3;
}
},
_updateView: function () {
_updateView: function() {
var self = this;
// remove the links needed for the preview lightbox
$("a[data-lightbox=" + this.view.previewBoxID + "]").remove();
this.instance.GET("/ajax/fs/filecount", {
......@@ -143,6 +146,7 @@ var FileBase = Class.extend({
}
this.helper.sortItems(content, sort, reverse);
this.workflow.currentView.setContent(content);
this.bookmarkcontainer.setContent();
this.menuitems.hideMenu();
this.instance.setLabel(this.workflow.path, true);
this.instance.setLoading(false);
......
var BookmarkContainer = Class.extend({
init: function(FileBase) {
this.FileBase = FileBase;
this.instance = FileBase.instance;
this.parentnode = null;
},
setContainer: function(node) {
var self = this;
// get template and append to node
this.instance.getTemplate("html/bookmark.html", function(err, tmpl, dfd) {
$(tmpl).appendTo(node);
self.parentnode = node;
// set width
var r = self.instance.getPreference("BookmarkRatio");
$(".bookmark-container").css("width", String(r) + "%");
});
},
setContent: function() {
var self = this;
// get names from workspaceini
var promise1 = self.instance.GET("/ajax/fs/get_from_private_workspaceini", {
"section": "Bookmarks",
"option": "names"
});
promise1.done(function(names) {
// then get pathes from workspaceini
var promise2 = self.instance.GET("/ajax/fs/get_from_private_workspaceini", {
"section": "Bookmarks",
"option": "pathes"
});
promise2.done(function(pathes) {
if (!names.success || !pathes.success) {
self.names = [];
self.pathes = [];
self.instance.alert("Can't read private workspace.ini.");
} else {
// split into arrays
self.names = names.content.split(":");
self.pathes = pathes.content.split(":");
}
if (self.names.length !== self.pathes.length) {
self.names = [];
self.pathes = [];
self.instance.alert("Bookmarks in workspace.ini may be broken.");
}
// set directives and render
var directives = {
"bookmark-name": {
"text": function() {
return this.value;
}
}
};
$(".bookmarks", self.parentnode).render(self.names, directives);
// add clicks
var clicktype = vispa.device.hasTouch ? "tap" : "click";
// button click
var node = $(".bookmark-button", self.parentnode);
node.off();
node.on(clicktype, function(event) {
event.stopPropagation();
self.addBookmark();
});
// bookmark clicks
$.each($(".bookmark", self.parentnode), function(index, node) {
// click on bookmark self
$(node).off();
$(node).on(clicktype, function(event) {
event.stopPropagation();
self.FileBase.workflow.path = self.pathes[index];
self.FileBase.updateView();
});
// click on remove glyph
$(".bookmark-glyphicon-remove", node).off();
$(".bookmark-glyphicon-remove", node).on(clicktype, function(event) {
event.stopPropagation();
self.removeBookmark(index);
});
// click on edit glyph
$(".bookmark-glyphicon-edit", node).off();
$(".bookmark-glyphicon-edit", node).on(clicktype, function(event) {
event.stopPropagation();
self.editBookmark(index);
});
});
});
});
},
addBookmark: function() {
var self = this;
// add data
self.pathes[self.pathes.length] = self.FileBase.workflow.path;
self.names[self.names.length] = self.FileBase.helper.filenameFromPath(self.pathes[self.pathes.length - 1]);
// check for already existing name
var twice = false;
for (var i = 0; i < self.names.length - 1; i++) {
if (self.names[i] == self.names[self.names.length - 1]) twice = true;
}
if (twice) {
self.names.splice(self.names.length - 1, 1);
self.pathes.splice(self.pathes.length - 1, 1);
self.instance.alert("Bookmark name \"" + self.names[self.names.length - 1] + "\" already exists.");
return;
}
// set new data to ini and update view
var promise1 = self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "names",
"value": self.names.join(":")
});
promise1.done(function(res1) {
var promise2 = self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "pathes",
"value": self.pathes.join(":")
});
promise2.done(function(res2) {
if (res1.success && res2.success) {
self.FileBase.updateView();
} else {
self.instance.alert("Couldn't add bookmark.");
self.names.splice(self.names.length - 1, 1);
self.pathes.splice(self.pathes.length - 1, 1);
self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "names",
"value": self.names.join(":")
});
self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "pathes",
"value": self.pathes.join(":")
});
}
});
});
},
removeBookmark: function(index) {
// delete entry in arrays
var self = this;
var name = self.names[index];
var path = self.pathes[index];
self.names.splice(index, 1);
self.pathes.splice(index, 1);
// set new data to ini and update view
var promise1 = self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "names",
"value": self.names.join(":")
});
promise1.done(function(res1) {
var promise2 = self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "pathes",
"value": self.pathes.join(":")
});
promise2.done(function(res2) {
if (res1.success && res2.success) {
self.FileBase.updateView();
} else {
self.instance.alert("Couldn't remove bookmark.");
self.names.splice(index, 0, name);
self.pathes.splice(index, 0, path);
self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "names",
"value": self.names.join(":")
});
self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "pathes",
"value": self.pathes.join(":")
});
}
});
});
},
editBookmark: function(index) {
var self = this;
// prompt for new data
self.instance.prompt(
"Enter: new position + \":\" + new name",
function(newdata) {
var newindex = parseInt(newdata.split(":")[0], 10);
var newname = newdata.split(":")[1];
// check for wrong format
if (newdata.split(":").length != 2 || newindex < 1 || self.names.length < newindex || isNaN(newindex)) {
self.instance.alert("Wrong format inserted!");
return;
}
// change position and name
newindex--;
var oldname = self.names[index];
self.names[index] = newname;
var name = self.names[index];
var path = self.pathes[index];
self.names.splice(index, 1);
self.names.splice(newindex, 0, name);
self.pathes.splice(index, 1);
self.pathes.splice(newindex, 0, path);
// set new data to ini and update view
var promise1 = self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "names",
"value": self.names.join(":")
});
promise1.done(function(res1) {
var promise2 = self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "pathes",
"value": self.pathes.join(":")
});
promise2.done(function(res2) {
if (res1.success && res2.success) {
self.FileBase.updateView();
} else {
self.instance.alert("Couldn't edit bookmark.");
self.names.splice(newindex, 1);
self.names.splice(index, 0, name);
self.pathes.splice(newindex, 1);
self.pathes.splice(index, 0, path);
self.names[index] = oldname;
self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "names",
"value": self.names.join(":")
});
self.instance.POST("/ajax/fs/set_to_private_workspaceini", {
"section": "Bookmarks",
"option": "pathes",
"value": self.pathes.join(":")
});
}
});
});
}, {
defaultValue: String(index + 1) + ":" + self.names[index]
}
);
}
});
\ No newline at end of file
......@@ -85,6 +85,11 @@ var FileBaseView = Class.extend({
// default view
self.FileBase.changeView(self.FileBase.workflow.currentView);
// set width and left margin
var r = self.instance.getPreference("BookmarkRatio");
$(".file-content").css("width",String(100-r) + "%");
$(".file-content").css("left",String(r) + "%");
});
self.instance.getTemplate("html/filter.html", function(err2, filterField, dfd2) {
self.filterField = $(filterField);
......
......@@ -37,6 +37,12 @@ var FileExtension = vispa.Extension.extend({
type: "bool",
value: true,
description: "With this preference the delete confirm request can be enabled (true) / disabled (false)."
},
BookmarkRatio: {
descr: "The width of bookmark column in percent.",
type: "integer",
range: [0, 100, 1],
value: 15
}
}, {
title: "File Browser"
......@@ -70,6 +76,12 @@ var FileExtension = vispa.Extension.extend({
type: "bool",
value: true,
description: "With this preference the delete confirm request can be enabled (true) / disabled (false)."
},
BookmarkRatio: {
descr: "The width of bookmark column in percent.",
type: "integer",
range: [0, 100, 1],
value: 15
}
}, {
title: "File Selector"
......
......@@ -100,6 +100,7 @@ var FileSelector = FileBase.extend({
}
this.helper.sortItems(cnt_filt, sort, reverse);
this.workflow.currentView.setContent(cnt_filt);
this.bookmarkcontainer.setContent();
this.instance.setLabel(this.workflow.path, true);
this.instance.setLoading(false);
}
......
......@@ -6,6 +6,7 @@ from distutils.spawn import find_executable
from mimetypes import guess_type
from zipfile import ZipFile
from threading import Lock
import ConfigParser
import json
import logging
import os
......@@ -601,6 +602,55 @@ class FileSystem(object):
return ""
def get_from_public_workspaceini(self, section, option):
try:
config = ConfigParser.ConfigParser()
config.read("/etc/vispa/workspace.ini")
return json.dumps({
"content": config.get(section, option),
"success": True
})
except Exception as e:
return json.dumps({
"content": str(e),
"success": False
})
def get_from_private_workspaceini(self, section, option):
try:
config = ConfigParser.ConfigParser()
config.read(os.path.expanduser(os.path.expandvars("$HOME/.vispa/workspace.ini")))
return json.dumps({
"content": config.get(section, option),
"success": True
})
except Exception as e:
return json.dumps({
"content": str(e),
"success": False
})
def set_to_private_workspaceini(self, section, option, value):
try:
config = ConfigParser.ConfigParser()
config.read(os.path.expanduser(os.path.expandvars("$HOME/.vispa/workspace.ini")))
if (config.has_section(section)):
config.set(section, option, value)
else:
config.add_section(section)
config.set(section, option, value)
workspaceini = open(os.path.expanduser(os.path.expandvars("$HOME/.vispa/workspace.ini")), "w")
config.write(workspaceini)
workspaceini.close()
return json.dumps({
"message": "Written successfully",
"success": True
})
except Exception as e:
return json.