Add scheduler plugin page to webui

This commit is contained in:
samuel337 2012-01-25 00:14:26 +00:00 committed by Calum Lind
parent 101ad99c14
commit 4a7876f203
2 changed files with 619 additions and 100 deletions

View File

@ -1,17 +1,88 @@
ScheduleSelectPanel = Ext.extend(Ext.form.FieldSet, { /*
constructor: function(config) { Script: scheduler.js
config = Ext.apply({ The client-side javascript code for the Scheduler plugin.
Copyright:
(C) samuel337 2011
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, write to:
The Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor
Boston, MA 02110-1301, USA.
In addition, as a special exception, the copyright holders give
permission to link the code of portions of this program with the OpenSSL
library.
You must obey the GNU General Public License in all respects for all of
the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete
this exception statement from your version. If you delete this exception
statement from all source files in the program, then also delete it here.
*/
Ext.ns('Deluge.ux');
Deluge.ux.ScheduleSelector = Ext.extend(Ext.form.FieldSet, {
title: _('Schedule'), title: _('Schedule'),
autoHeight: true autoHeight: true,
}, config); style: 'margin-bottom: 0px; padding-bottom: 0px;',
ScheduleSelectPanel.superclass.constructor.call(this, config); border: false,
states: [
{
name: 'Normal',
backgroundColor: 'LightGreen',
borderColor: 'DarkGreen',
value: 0
},
{
name: 'Throttled',
backgroundColor: 'Yellow',
borderColor: 'Gold',
value: 1
},
{
name: 'Paused',
backgroundColor: 'OrangeRed',
borderColor: 'FireBrick',
value: 2
}
],
daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
initComponent: function() {
Deluge.ux.ScheduleSelector.superclass.initComponent.call(this);
// ExtJS' radiogroup implementation is very broken for styling.
/*this.stateBrush = this.add({
xtype: 'radiogroup',
fieldLabel: _('State Brush'),
name: 'current_state_brush',
submitValue: false,
items: [
{ boxLabel: 'Normal', name: 'current_state_brush', inputValue: 0 },
{ boxLabel: 'Throttled', name: 'current_state_brush', inputValue: 1, checked: true },
{ boxLabel: 'Paused', name: 'current_state_brush', inputValue: 2 },
]
});*/
}, },
onRender: function(ct, position) { onRender: function(ct, position) {
ScheduleSelectPanel.superclass.onRender.call(this, ct, position); Deluge.ux.ScheduleSelector.superclass.onRender.call(this, ct, position);
var dom = this.body.dom; var dom = this.body.dom;
var table = createEl(dom, 'table');
function createEl(parent, type) { function createEl(parent, type) {
var el = document.createElement(type); var el = document.createElement(type);
@ -19,30 +90,417 @@ ScheduleSelectPanel = Ext.extend(Ext.form.FieldSet, {
return el; return el;
} }
Ext.each(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], function(day) { // create state brushes
// tack a random number to the end to avoid clashes
this.stateBrushName = 'schedule-state-brush-' + Math.round(Math.random() * 10000);
var el1 = createEl(dom, 'div');
var el2 = createEl(el1, 'div');
this.stateBrush = el2;
el2.id = this.stateBrushName;
// for webkit
var floatAttr = 'float';
if (el2.style.float == undefined) {
// for firefox
if (el2.style.cssFloat != undefined) floatAttr = 'cssFloat';
// for IE
if (el2.style.styleFloat != undefined) floatAttr = 'styleFloat';
}
el2.style[floatAttr] = 'right';
for (var i=0; i < this.states.length; i++) {
var el3 = createEl(el2, 'input');
el3.type = 'radio';
el3.value = this.states[i].value;
el3.name = this.stateBrushName;
el3.id = this.stateBrushName + '-' + this.states[i].name;
// isn't the first one
if (i > 0) el3.style.marginLeft = '7px';
// assume the first is the default state, so make the 2nd one the default brush
if (i == 1) el3.checked = true;
var el4 = createEl(el2, 'label');
el4.appendChild(document.createTextNode(this.states[i].name));
el4.htmlFor = el3.id;
el4.style.backgroundColor = this.states[i].backgroundColor;
el4.style.borderBottom = '2px solid ' + this.states[i].borderColor;
el4.style.padding = '2px 3px';
el4.style.marginLeft = '3px';
}
el1.appendChild(document.createTextNode('Select a state brush:'));
el1.style.marginBottom = '10px';
// keep the radio buttons separate from the time bars
createEl(dom, 'div').style.clear = 'both';
var table = createEl(dom, 'table');
table.cellSpacing = 0;
// cache access to cells for easier access later
this.scheduleCells = { };
Ext.each(this.daysOfWeek, function(day) {
var cells = [ ];
var row = createEl(table, 'tr'); var row = createEl(table, 'tr');
var label = createEl(row, 'th'); var label = createEl(row, 'th');
label.setAttribute('style', 'font-weight: bold; padding-right: 5px;'); label.setAttribute('style', 'font-weight: bold; padding-right: 5px;');
label.innerHTML = day; label.appendChild(document.createTextNode(day));
for (var hour = 0; hour < 24; hour++) { for (var hour = 0; hour < 24; hour++) {
var cell = createEl(row, 'td'); var cell = createEl(row, 'td');
cell.setAttribute('style', 'border: 1px solid Green; width: 16px; height: 20px; background: LightGreen;');
}
});
}
});
SchedulerPreferences = Ext.extend(Ext.Panel, { // assume the first state is the default state
constructor: function(config) { cell.currentValue = cell.oldValue = this.states[0].value;
config = Ext.apply({ cell.day = day;
border: false, cell.hour = hour;
title: _('Scheduler')
}, config); cell.width = '16px';
SchedulerPreferences.superclass.constructor.call(this, config); cell.height = '20px';
cell.style.border = '1px solid #999999';
// don't repeat borders in between cells
if (hour != 23) // not the last cell
cell.style.borderRight = 'none';
this.updateCell(cell);
cells.push(cell);
cell = Ext.get(cell);
cell.on('click', this.onCellClick, this);
cell.on('mouseover', this.onCellMouseOver, this);
cell.on('mouseout', this.onCellMouseOut, this);
cell.on('mousedown', this.onCellMouseDown, this);
cell.on('mouseup', this.onCellMouseUp, this);
}
// insert gap row to provide visual separation
row = createEl(table, 'tr');
// blank cell to create gap
createEl(row, 'td').height = '3px';
this.scheduleCells[day] = cells;
}, this);
}, },
updateCell: function(cell) {
// sanity check
if (cell.currentValue == undefined) return;
for (var i in this.states) {
var curState = this.states[i];
if (curState.value == cell.currentValue) {
cell.style.background = curState.backgroundColor;
break;
}
}
},
getCurrentBrushValue: function() {
var v = null;
var brushes = Ext.get(this.body.dom).findParent('form').elements[this.stateBrushName];
Ext.each(brushes, function(b) {
if (b.checked)
v = b.value;
});
return v;
},
onCellClick: function(event, cell) {
cell.oldValue = cell.currentValue;
this.dragAnchor = null;
},
onCellMouseDown: function(event, cell) {
this.dragAnchor = cell;
},
onCellMouseUp: function(event, cell) {
// if we're dragging...
if (this.dragAnchor) {
// set all those between here and the anchor to the new values
if (cell.hour > this.dragAnchor.hour)
this.confirmCells(cell.day, this.dragAnchor.hour, cell.hour);
else if (cell.hour < this.dragAnchor.hour)
this.confirmCells(cell.day, cell.hour, this.dragAnchor.hour);
else
this.confirmCells(cell.day, cell.hour, cell.hour);
this.hideCellLeftTooltip();
this.hideCellRightTooltip();
this.dragAnchor = null;
}
},
onCellMouseOver: function(event, cell) {
// LEFT TOOL TIP
// if it isn't showing and we're dragging, show it.
// otherwise if dragging, leave it alone unless we're dragging to the left.
// if we're not dragging, show it.
var leftTooltipCell = null;
if (!this.dragAnchor)
leftTooltipCell = cell;
else if ((this.dragAnchor && this.isCellLeftTooltipHidden()) ||
(this.dragAnchor && this.dragAnchor.hour > cell.hour))
leftTooltipCell = this.dragAnchor;
if (leftTooltipCell) {
var hour = leftTooltipCell.hour;
var pm = false;
// convert to 12-hour time
if (hour >= 12) {
pm = true;
if (hour > 12) hour -= 12;
}
// change 0 hour to 12am
else if (hour == 0) {
hour = 12;
}
this.showCellLeftTooltip(hour + ' ' + (pm ? 'pm' : 'am'), leftTooltipCell);
}
// RIGHT TOOL TIP
var rightTooltipCell = null;
if (this.dragAnchor) {
if (this.dragAnchor.hour == cell.hour)
this.hideCellRightTooltip();
else if (this.dragAnchor.hour > cell.hour && this.isCellRightTooltipHidden())
rightTooltipCell = this.dragAnchor;
else // cell.hour > this.dragAnchor.hour
rightTooltipCell = cell;
}
if (rightTooltipCell) {
var hour = rightTooltipCell.hour;
var pm = false;
// convert to 12-hour time
if (hour >= 12) {
pm = true;
if (hour > 12) hour -= 12;
}
// change 0 hour to 12am
else if (hour == 0) {
hour = 12;
}
this.showCellRightTooltip(hour + ' ' + (pm ? 'pm' : 'am'), rightTooltipCell);
}
// preview colour change and
// revert state for all those on the outer side of the drag if dragging
if (this.dragAnchor) {
if (cell.day != this.dragAnchor.day) {
// dragged into another day. Abort! Abort!
Ext.each(this.daysOfWeek, function(day) {
this.revertCells(day, 0, 23);
}, this);
this.dragAnchor = null;
this.hideCellLeftTooltip();
this.hideCellRightTooltip();
}
else if (cell.hour > this.dragAnchor.hour) {
// dragging right
this.revertCells(cell.day, cell.hour+1, 23);
this.previewCells(cell.day, this.dragAnchor.hour, cell.hour);
}
else if (cell.hour < this.dragAnchor.hour) {
// dragging left
this.revertCells(cell.day, 0, cell.hour-1);
this.previewCells(cell.day, cell.hour, this.dragAnchor.hour);
}
else {
// back to anchor cell
// don't know if it is from right or left, so revert all except this
this.revertCells(cell.day, cell.hour+1, 23);
this.revertCells(cell.day, 0, cell.hour-1);
}
}
else {
// not dragging, just preview this cell
this.previewCells(cell.day, cell.hour, cell.hour);
}
},
onCellMouseOut: function(event, cell) {
if (!this.dragAnchor)
this.hideCellLeftTooltip();
// revert state. If new state has been set, old and new will be equal.
// if dragging, this will be handled by the next mouse over
if (this.dragAnchor == null && cell.oldValue != cell.currentValue) {
this.revertCells(cell.day, cell.hour, cell.hour);
}
},
previewCells: function(day, fromHour, toHour) {
var cells = this.scheduleCells[day];
var curBrushValue = this.getCurrentBrushValue();
if (toHour > cells.length) toHour = cells.length;
for (var i=fromHour; i <= toHour; i++) {
if (cells[i].currentValue != curBrushValue) {
cells[i].oldValue = cells[i].currentValue;
cells[i].currentValue = curBrushValue;
this.updateCell(cells[i]);
}
}
},
revertCells: function(day, fromHour, toHour) {
var cells = this.scheduleCells[day];
if (toHour > cells.length) toHour = cells.length;
for (var i=fromHour; i <= toHour; i++) {
cells[i].currentValue = cells[i].oldValue;
this.updateCell(cells[i]);
}
},
confirmCells: function(day, fromHour, toHour) {
var cells = this.scheduleCells[day];
if (toHour > cells.length) toHour = cells.length;
for (var i=fromHour; i <= toHour; i++) {
if (cells[i].currentValue != cells[i].oldValue) {
cells[i].oldValue = cells[i].currentValue;
}
}
},
showCellLeftTooltip: function(text, cell) {
var tooltip = this.cellLeftTooltip;
if (!tooltip) {
// no cached left tooltip exists, create one
tooltip = document.createElement('div');
this.cellLeftTooltip = tooltip;
this.body.dom.appendChild(tooltip);
tooltip.style.position = 'absolute';
tooltip.style.backgroundColor = '#F2F2F2';
tooltip.style.border = '1px solid #333333';
tooltip.style.padding = '1px 3px';
tooltip.style.opacity = 0.8;
}
// remove all existing children
while (tooltip.childNodes.length > 0) {
tooltip.removeChild(tooltip.firstChild);
}
// add the requested text
tooltip.appendChild(document.createTextNode(text));
// place the tooltip
Ext.get(tooltip).alignTo(cell, 'br-tr');
// make it visible
tooltip.style.visibility = 'visible';
},
hideCellLeftTooltip: function() {
if (this.cellLeftTooltip) {
this.cellLeftTooltip.style.visibility = 'hidden';
}
},
isCellLeftTooltipHidden: function() {
if (this.cellLeftTooltip)
return this.cellLeftTooltip.style.visibility == 'hidden';
else
return true;
},
showCellRightTooltip: function(text, cell) {
var tooltip = this.cellRightTooltip;
if (!tooltip) {
// no cached left tooltip exists, create one
tooltip = document.createElement('div');
this.cellRightTooltip = tooltip;
this.body.dom.appendChild(tooltip);
tooltip.style.position = 'absolute';
tooltip.style.backgroundColor = '#F2F2F2';
tooltip.style.border = '1px solid #333333';
tooltip.style.padding = '1px 3px';
tooltip.style.opacity = 0.8;
}
// remove all existing children
while (tooltip.childNodes.length > 0) {
tooltip.removeChild(tooltip.firstChild);
}
// add the requested text
tooltip.appendChild(document.createTextNode(text));
// place the tooltip
Ext.get(tooltip).alignTo(cell, 'bl-tl');
// make it visible
tooltip.style.visibility = 'visible';
},
hideCellRightTooltip: function() {
if (this.cellRightTooltip) {
this.cellRightTooltip.style.visibility = 'hidden';
}
},
isCellRightTooltipHidden: function() {
if (this.cellRightTooltip)
return this.cellRightTooltip.style.visibility == 'hidden';
else
return true;
},
getConfig: function() {
var config = [ ];
for (var i=0; i < 24; i++) {
var hourConfig = [ 0, 0, 0, 0, 0, 0, 0 ];
for (var j=0; j < this.daysOfWeek.length; j++) {
hourConfig[j] = parseInt(this.scheduleCells[this.daysOfWeek[j]][i].currentValue);
}
config.push(hourConfig);
}
return config;
},
setConfig: function(config) {
for (var i=0; i < 24; i++) {
var hourConfig = config[i];
for (var j=0; j < this.daysOfWeek.length; j++) {
var cell = this.scheduleCells[this.daysOfWeek[j]][i];
cell.currentValue = cell.oldValue = hourConfig[j];
this.updateCell(cell);
}
}
}
});
Ext.ns('Deluge.ux.preferences');
Deluge.ux.preferences.SchedulerPage = Ext.extend(Ext.Panel, {
border: false,
title: _('Scheduler'),
layout: 'fit',
initComponent: function() { initComponent: function() {
SchedulerPreferences.superclass.initComponent.call(this); Deluge.ux.preferences.SchedulerPage.superclass.initComponent.call(this);
this.form = this.add({ this.form = this.add({
xtype: 'form', xtype: 'form',
@ -51,56 +509,116 @@ SchedulerPreferences = Ext.extend(Ext.Panel, {
autoHeight: true autoHeight: true
}); });
this.schedule = this.form.add(new ScheduleSelectPanel()); this.schedule = this.form.add(new Deluge.ux.ScheduleSelector());
this.slowSettings = this.form.add({ this.slowSettings = this.form.add({
xtype: 'fieldset', xtype: 'fieldset',
title: _('Slow Settings'), border: false,
title: _('Throttled Settings'),
autoHeight: true, autoHeight: true,
defaultType: 'uxspinner' defaultType: 'spinnerfield',
defaults: {
minValue: -1,
maxValue: 99999
},
style: 'margin-top: 5px; margin-bottom: 0px; padding-bottom: 0px;',
labelWidth: 200
}); });
this.downloadLimit = this.slowSettings.add({ this.downloadLimit = this.slowSettings.add({
fieldLabel: _('Download Limit'), fieldLabel: _('Maximum Download Speed (KiB/s)'),
name: 'download_limit' name: 'download_limit',
width: 80,
value: -1,
decimalPrecision: 0
}); });
this.uploadLimit = this.slowSettings.add({ this.uploadLimit = this.slowSettings.add({
fieldLabel: _('Upload Limit'), fieldLabel: _('Maximum Upload Speed (KiB/s)'),
name: 'upload_limit' name: 'upload_limit',
width: 80,
value: -1,
decimalPrecision: 0
}); });
this.activeTorrents = this.slowSettings.add({ this.activeTorrents = this.slowSettings.add({
fieldLabel: _('Active Torrents'), fieldLabel: _('Active Torrents'),
name: 'active_torrents' name: 'active_torrents',
width: 80,
value: -1,
decimalPrecision: 0
}); });
this.activeDownloading = this.slowSettings.add({
fieldLabel: _('Active Downloading'),
name: 'active_downloading',
width: 80,
value: -1,
decimalPrecision: 0
});
this.activeSeeding = this.slowSettings.add({
fieldLabel: _('Active Seeding'),
name: 'active_seeding',
width: 80,
value: -1,
decimalPrecision: 0
});
deluge.preferences.on('show', this.updateConfig, this);
}, },
onRender: function(ct, position) { onRender: function(ct, position) {
SchedulerPreferences.superclass.onRender.call(this, ct, position); Deluge.ux.preferences.SchedulerPage.superclass.onRender.call(this, ct, position);
this.form.layout = new Ext.layout.FormLayout(); this.form.layout = new Ext.layout.FormLayout();
this.form.layout.setContainer(this); this.form.layout.setContainer(this);
this.form.doLayout(); this.form.doLayout();
}, },
onShow: function() { onApply: function() {
SchedulerPreferences.superclass.onShow.call(this); // build settings object
var config = { }
config['button_state'] = this.schedule.getConfig();
config['low_down'] = this.downloadLimit.getValue();
config['low_up'] = this.uploadLimit.getValue();
config['low_active'] = this.activeTorrents.getValue();
config['low_active_down'] = this.activeDownloading.getValue();
config['low_active_up'] = this.activeSeeding.getValue();
deluge.client.scheduler.set_config(config);
},
onOk: function() {
this.onApply();
},
afterRender: function() {
Deluge.ux.preferences.SchedulerPage.superclass.afterRender.call(this);
this.updateConfig();
},
updateConfig: function() {
deluge.client.scheduler.get_config({
success: function(config) {
this.schedule.setConfig(config['button_state']);
this.downloadLimit.setValue(config['low_down']);
this.uploadLimit.setValue(config['low_up']);
this.activeTorrents.setValue(config['low_active']);
this.activeDownloading.setValue(config['low_active_down']);
this.activeSeeding.setValue(config['low_active_up']);
},
scope: this
});
} }
}); });
SchedulerPlugin = Ext.extend(Deluge.Plugin, { Deluge.plugins.SchedulerPlugin = Ext.extend(Deluge.Plugin, {
constructor: function(config) {
config = Ext.apply({ name: 'Scheduler',
name: "Scheduler"
}, config);
SchedulerPlugin.superclass.constructor.call(this, config);
},
onDisable: function() { onDisable: function() {
Deluge.Preferences.removePage(this.prefsPage); deluge.preferences.removePage(this.prefsPage);
}, },
onEnable: function() { onEnable: function() {
this.prefsPage = new SchedulerPreferences(); this.prefsPage = deluge.preferences.addPage(new Deluge.ux.preferences.SchedulerPage());
this.prefsPage = Deluge.Preferences.addPage(this.prefsPage);
} }
}); });
new SchedulerPlugin(); Deluge.registerPlugin('Scheduler', Deluge.plugins.SchedulerPlugin);

View File

@ -48,3 +48,4 @@ log = logging.getLogger(__name__)
class WebUI(WebPluginBase): class WebUI(WebPluginBase):
scripts = [get_resource("scheduler.js")] scripts = [get_resource("scheduler.js")]
debug_scripts = scripts