mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Preferences dialogue. Patch by Ashesh and Khushboo Vashi.
This commit is contained in:
137
web/pgadmin/preferences/__init__.py
Normal file
137
web/pgadmin/preferences/__init__.py
Normal 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()
|
||||
40
web/pgadmin/preferences/static/css/preferences.css
Normal file
40
web/pgadmin/preferences/static/css/preferences.css
Normal 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;
|
||||
}
|
||||
}
|
||||
8
web/pgadmin/preferences/templates/preferences/index.html
Normal file
8
web/pgadmin/preferences/templates/preferences/index.html
Normal 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>
|
||||
374
web/pgadmin/preferences/templates/preferences/preferences.js
Normal file
374
web/pgadmin/preferences/templates/preferences/preferences.js
Normal 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;
|
||||
});
|
||||
Reference in New Issue
Block a user