Preferences dialogue. Patch by Ashesh and Khushboo Vashi.

This commit is contained in:
Dave Page
2016-03-07 11:48:24 +00:00
parent 43116750b4
commit 5ea822f33e
21 changed files with 1439 additions and 74 deletions

View File

@@ -0,0 +1,137 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
#
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""
Implements the routes for creating Preferences/Options Dialog on the client
side and for getting/setting preferences.
"""
from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import success_return, \
make_response as ajax_response, internal_server_error
from flask import render_template, url_for, Response, request
from flask.ext.security import login_required
from flask.ext.login import current_user
from flask.ext.babel import gettext
from pgadmin.utils.menu import MenuItem
from pgadmin.utils.preferences import Preferences
import simplejson as json
MODULE_NAME = 'preferences'
class PreferencesModule(PgAdminModule):
"""
PreferenceModule represets the preferences of different modules to the
user in UI.
And, allows the user to modify (not add/remove) as per their requirement.
"""
def get_own_javascripts(self):
return [{
'name': 'pgadmin.preferences',
'path': url_for('preferences.index') + 'preferences',
'when': None
}]
def get_own_stylesheets(self):
return [url_for('preferences.static', filename='css/preferences.css')]
def get_own_menuitems(self):
return {
'file_items': [
MenuItem(name='mnu_preferences',
priority=999,
module="pgAdmin.Preferences",
callback='show',
icon='fa fa-cog',
label=gettext('Preferences'))
]
}
blueprint = PreferencesModule(MODULE_NAME, __name__)
@blueprint.route("/")
@login_required
def index():
"""Render the preferences dialog."""
return render_template(
MODULE_NAME + "/index.html",
username=current_user.email,
_=gettext
)
@blueprint.route("/preferences.js")
@login_required
def script():
"""render the required javascript"""
return Response(response=render_template("preferences/preferences.js", _=gettext),
status=200,
mimetype="application/javascript")
@blueprint.route("/preferences", methods=["GET"])
@login_required
def preferences():
"""Fetch all the preferences of pgAdmin IV."""
# Load Preferences
preferences = Preferences.preferences()
res = []
for m in preferences:
if len(m['categories']):
om = {
"id": m['id'],
"label": m['label'],
"inode": True,
"open": True,
"branch": []
}
for c in m['categories']:
oc = {
"id": c['id'],
"mid": m['id'],
"label": c['label'],
"inode": False,
"open": False,
"preferences": c['preferences']
}
(om['branch']).append(oc)
res.append(om)
return ajax_response(
response=res,
status=200
)
@blueprint.route("/preferences/<int:pid>", methods=["PUT"])
@login_required
def save(pid):
"""
Save a specific preference.
"""
data = request.form if request.form else json.loads(request.data.decode())
res, msg = Preferences.save(data['mid'], data['cid'], data['id'], data['value'])
if not res:
return internal_server_error(errormsg=msg)
return success_return()

View File

@@ -0,0 +1,40 @@
.preferences_dialog {
height: 100%;
position: absolute;
top: 5px;
left: 0px;
bottom: 0px;
right: 0px;
padding-bottom: 30px;
}
.preferences_tree{
padding: 0px;
padding-top: 2px;
height: 100%;
overflow: auto;
border-right: 2px solid #999999;
background-image: #FAFAFA;
}
.preferences_content {
padding-top: 10px;
height: 100%;
overflow: auto;
}
.preferences_content .control-label, .preferences_content .pgadmin-controls {
min-width: 100px !important;
}
.pgadmin-preference-body {
min-width: 300px !important;
min-height: 400px !important;
}
@media (min-width: 768px) {
.pgadmin-preference-body {
min-width: 600px !important;
min-height: 480px !important;
}
}

View File

@@ -0,0 +1,8 @@
<div id="options_dialog">
<div id="options_tree" class="">
ACI TREE
</div>
<div id='options_content'>
Right hand side content
</div>
</div>

View File

@@ -0,0 +1,374 @@
define(
['jquery', 'alertify', 'pgadmin', 'underscore', 'backform', 'pgadmin.backform'],
// This defines the Preference/Options Dialog for pgAdmin IV.
function($, alertify, pgAdmin, _, Backform) {
pgAdmin = pgAdmin || window.pgAdmin || {};
/*
* Hmm... this module is already been initialized, we can refer to the old
* object from here.
*/
if (pgAdmin.Preferences)
return pgAdmin.Preferences;
pgAdmin.Preferences = {
init: function() {
if (this.initialized)
return;
this.initialized = true;
// Declare the Preferences dialog
alertify.dialog('preferencesDlg', function() {
var jTree, // Variable to create the aci-tree
controls = [], // Keep tracking of all the backform controls
// created by the dialog.
// Dialog containter
$container = $("<div class='preferences_dialog'></div>");
/*
* Preference Model
*
* This model will be used to keep tracking of the changes done for
* an individual option.
*/
var PreferenceModel = Backbone.Model.extend({
idAttribute: 'id',
defaults: {
id: undefined,
value: undefined
}
});
/*
* Preferences Collection object.
*
* We will use only one collection object to keep track of all the
* preferences.
*/
var preferences = this.preferences = new (Backbone.Collection.extend({
model: PreferenceModel,
url: "{{ url_for('preferences.preferences') }}",
updateAll: function() {
// We will send only the modified data to the server.
this.each(function(m) {
if (m.hasChanged()) {
m.save({
fail: function() {
}
});
}
});
return true;
}
}))(null);
/*
* Function: renderPreferencePanel
*
* Renders the preference panel in the content div based on the given
* preferences.
*/
var renderPreferencePanel = function(prefs) {
/*
* Clear the existing html in the preferences content
*/
var content = $container.find('.preferences_content');
content.empty();
/*
* We should clean up the existing controls.
*/
if (controls) {
_.each(controls, function(c) {
c.remove();
});
}
controls = [];
/*
* We will create new set of controls and render it based on the
* list of preferences using the Backform Field, Control.
*/
_.each(prefs, function(p) {
var m = preferences.get(p.id),
f = new Backform.Field(_.extend({}, p, {id: 'value', name: 'value'})),
cntr = new (f.get("control")) ({
field: f,
model: m
});
content.append(cntr.render().$el);
// We will keep track of all the controls rendered at the
// moment.
controls.push(cntr);
});
};
/*
* Function: dialogContentCleanup
*
* Do the dialog container cleanup on openning.
*/
var dialogContentCleanup = function() {
// Remove the existing preferences
if (!jTree)
return;
/*
* Remove the aci-tree (mainly to remove the jquery object of
* aciTree from the system for this container).
*/
try {
jTreeApi = jTree.aciTree('destroy');
} catch(ex) {
// Sometimes - it fails to destroy the tree properly and throws
// exception.
}
jTree.off('acitree', treeEventHandler);
// We need to reset the data from the preferences too
preferences.reset();
/*
* Clean up the existing controls.
*/
if (controls) {
_.each(controls, function(c) {
c.remove();
});
}
controls = [];
// Remove all the objects now.
$container.empty();
},
/*
* Function: selectFirstCategory
*
* Whenever a user select a module instead of a category, we should
* select the first categroy of it.
*/
selectFirstCategory = function(api, item) {
var data = item ? api.itemData(item) : null;
if (data && data.preferences) {
api.select(item);
return;
}
item = api.first(item);
selectFirstCategory(api, item);
},
/*
* A map on how to create controls for each datatype in preferences
* dialog.
*/
getControlMappedForType = function(p) {
switch(p.type) {
case 'boolean':
p.options = {
onText: '{{ _('True') }}',
offText: '{{ _('False') }}',
onColor: 'success',
offColor: 'default',
size: 'mini'
};
return 'switch';
case 'node':
p.options = {
onText: '{{ _('Show') }}',
offText: '{{ _('Hide') }}',
onColor: 'success',
offColor: 'default',
size: 'mini'
};
return 'switch';
case 'integer':
return 'integer';
case 'numeric':
return 'numeric';
case 'date':
// TODO::
// Datetime picker Control is missing at the moment, replace
// once it has been implemented.
return 'datepicker';
case 'datetime':
return 'datepicker';
case 'options':
var opts = [];
// Convert the array to SelectControl understandable options.
_.each(p.options, function(o) {
opts.push({'label': o, 'value': o});
});
p.options = opts;
return 'select2';
case 'multiline':
return 'textarea';
case 'switch':
return 'switch';
default:
if (console && console.log) {
// Warning for developer only.
console.log(
"Hmm.. We don't know how to render this type - ''" + type + "' of control."
);
}
return 'input';
}
},
/*
* function: treeEventHandler
*
* It is basically a callback, which listens to aci-tree events,
* and act accordingly.
*
* + Selection of the node will existance of the preferences for
* the selected tree-node, if not pass on to select the first
* category under a module, else pass on to the render function.
*
* + When a new node is added in the tree, it will add the relavent
* preferences in the preferences model collection, which will be
* called during initialization itself.
*
*
*/
treeEventHandler = function(event, api, item, eventName) {
// Look for selected item (if none supplied)!
item = item || api.selected();
// Event tree item has itemData
var d = item ? api.itemData(item) : null;
/*
* boolean (switch/checkbox), string, enum (combobox - enumvals),
* integer (min-max), font, color
*/
switch (eventName) {
case "selected":
if (!d)
return true;
if (d.preferences) {
/*
* Clear the existing html in the preferences content
*/
renderPreferencePanel(d.preferences);
return true;
} else{
selectFirstCategory(api, item);
}
break;
case 'added':
if (!d)
return true;
// We will add the preferences in to the preferences data
// collection.
if (d.preferences && _.isArray(d.preferences)) {
_.each(d.preferences, function(p) {
preferences.add({
'id': p.id, 'value': p.value, 'cid': d.id, 'mid': d.mid
});
/*
* We don't know until now, how to render the control for
* this preference.
*/
if (!p.control) {
p.control = getControlMappedForType(p);
}
});
}
d.sortable = false;
break;
case 'loaded':
// Let's select the first category from the prefrences.
// We need to wait for sometime before all item gets loaded
// properly.
setTimeout(
function() {
selectFirstCategory(api, null);
}, 300);
break;
}
return true;
};
// Dialog property
return {
main: function() {
// Remove the existing content first.
dialogContentCleanup();
$container.append(
"<div class='col-xs-3 preferences_tree aciTree'></div>"
).append(
"<div class='col-xs-9 preferences_content'>" +
" {{ _('Category is not selected.')|safe }}" +
"</div>"
);
// Create the aci-tree for listing the modules and categories of
// it.
jTree = $container.find('.preferences_tree');
jTree.on('acitree', treeEventHandler);
jTree.aciTree({
selectable: true,
expand: true,
ajax: {
url: "{{ url_for('preferences.preferences') }}"
}
});
this.show();
},
setup:function(){
return {
buttons:[
{
text: "{{ _('OK') }}", key: 13, className: "btn btn-primary"
},
{
text: "{{ _('Cancel') }}", className: "btn btn-danger"
}
],
focus: { element: 0 },
options: {
padding: !1,
overflow: !1,
title: '{{ _('Preferences')|safe }}'
}
};
},
callback: function(closeEvent){
if (closeEvent.button.text == "{{ _('OK') }}"){
preferences.updateAll();
}
},
build: function() {
this.elements.content.appendChild($container.get(0));
},
hooks: {
onshow: function() {
$(this.elements.body).addClass('pgadmin-preference-body');
}
}
};
});
},
show: function() {
alertify.preferencesDlg(true).resizeTo('60%', '60%');
}
};
return pgAdmin.Preferences;
});