signalr cleanup

This commit is contained in:
kay.one 2013-09-10 23:33:47 -07:00 committed by Keivan Beigi
parent feda4a9b67
commit 25e2c98c45
219 changed files with 2035 additions and 1495 deletions

View file

@ -1,9 +1,9 @@
{{#if folder.path}}
<div class="row well unmapped-folder-path">
<div class="span11">
{{folder.path}}
</div>
<div class="row well unmapped-folder-path">
<div class="span11">
{{folder.path}}
</div>
</div>
{{/if}}
<div class="row x-search-bar">
<div class="input-prepend nz-input-large add-series-search span11">

View file

@ -1,4 +1,4 @@
'use strict';
'use strict';
define(
[
'backbone',
@ -8,10 +8,19 @@ define(
var CommandCollection = Backbone.Collection.extend({
url : window.ApiRoot + '/command',
model: CommandModel
model: CommandModel,
findCommand: function (command) {
return this.find(function (model) {
return model.isSameCommand(command);
});
}
});
var collection = new CommandCollection().bindSignalR();
collection.fetch();
return collection;
});

View file

@ -1,16 +1,21 @@
'use strict';
define({
Execute: function (name, properties) {
var data = { command: name };
define(
[
'Commands/CommandModel',
'Commands/CommandCollection',
'underscore'
], function (CommandModel, CommandCollection, _) {
if (properties) {
$.extend(data, properties);
return{
Execute: function (name, properties) {
var attr = _.extend({name: name.toLocaleLowerCase()}, properties);
var commandModel = new CommandModel(attr);
return commandModel.save().success(function () {
CommandCollection.add(commandModel);
});
}
return $.ajax({
type: 'POST',
url : window.ApiRoot + '/command',
data: JSON.stringify(data)
});
}
});

View file

@ -0,0 +1,15 @@
'use strict';
define(
[
'app',
'marionette',
'Commands/CommandCollection',
'Commands/CommandMessengerItemView'
], function (App, Marionette, commandCollection, CommandMessengerItemView) {
var CollectionView = Marionette.CollectionView.extend({
itemView : CommandMessengerItemView
});
new CollectionView({collection: commandCollection});
});

View file

@ -0,0 +1,48 @@
'use strict';
define(
[
'app',
'marionette',
'Shared/Messenger'
], function (App, Marionette, Messenger) {
return Marionette.ItemView.extend({
initialize: function () {
this.listenTo(this.model, 'change', this.render);
},
render: function () {
if (!this.model.get('message')) {
return;
}
var message = {
type : 'info',
message : '[{0}] {1}'.format(this.model.get('name'), this.model.get('message')),
id : this.model.id,
hideAfter: 0
};
switch (this.model.get('state')) {
case 'completed':
message.hideAfter = 4;
break;
case 'failed':
message.hideAfter = 4;
message.type = 'error';
break;
default :
message.hideAfter = 0;
}
Messenger.show(message);
console.log(message.message);
}
});
});

View file

@ -4,5 +4,30 @@ define(
'backbone'
], function (Backbone) {
return Backbone.Model.extend({
url: window.ApiRoot + '/command',
parse: function (response) {
response.name = response.name.toLocaleLowerCase();
return response;
},
isActive: function () {
return this.get('state') !== 'completed' && this.get('state') !== 'failed';
},
isSameCommand: function (command) {
if (command.name.toLocaleLowerCase() != this.get('name').toLocaleLowerCase()) {
return false;
}
for (var key in command) {
if (key !== 'name' && command[key] !== this.get(key)) {
return false;
}
}
return true;
}
});
});

View file

@ -10,7 +10,6 @@ define(
'Series/Details/SeriesDetailsLayout',
'Series/SeriesCollection',
'Missing/MissingLayout',
'Series/SeriesModel',
'Calendar/CalendarLayout',
'Logs/Layout',
'Logs/Files/Layout',
@ -19,7 +18,7 @@ define(
'SeasonPass/Layout',
'Shared/NotFoundView',
'Shared/Modal/Region'
], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, SeriesCollection, MissingLayout, SeriesModel, CalendarLayout,
], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, SeriesCollection, MissingLayout, CalendarLayout,
LogsLayout, LogFileLayout, ReleaseLayout, SystemLayout, SeasonPassLayout, NotFoundView) {
return Marionette.Controller.extend({

View file

@ -7,11 +7,9 @@ define(
'Episode/Search/ManualLayout',
'Release/Collection',
'Series/SeriesCollection',
'Shared/LoadingView',
'Shared/Messenger',
'Shared/Actioneer',
'Shared/FormatHelpers'
], function (App, Marionette, ButtonsView, ManualSearchLayout, ReleaseCollection, SeriesCollection, LoadingView, Messenger, Actioneer, FormatHelpers) {
'Commands/CommandController',
'Shared/LoadingView'
], function (App, Marionette, ButtonsView, ManualSearchLayout, ReleaseCollection, SeriesCollection,CommandController, LoadingView) {
return Marionette.Layout.extend({
template: 'Episode/Search/LayoutTemplate',
@ -39,18 +37,8 @@ define(
e.preventDefault();
}
var series = SeriesCollection.get(this.model.get('seriesId'));
var seriesTitle = series.get('title');
var season = this.model.get('seasonNumber');
var episode = this.model.get('episodeNumber');
var message = seriesTitle + ' - ' + season + 'x' + FormatHelpers.pad(episode, 2);
Actioneer.ExecuteCommand({
command : 'episodeSearch',
properties: {
episodeId: this.model.get('id')
},
errorMessage: 'Search failed for: ' + message
CommandController.Execute('episodeSearch', {
episodeId: this.model.get('id')
});
App.vent.trigger(App.Commands.CloseModalCommand);

View file

@ -5,59 +5,29 @@ define(
], function () {
_.extend(Backbone.Collection.prototype, {
bindSignalR: function (options) {
bindSignalR: function () {
if (!options) {
options = {};
}
var collection = this;
if (!options.url) {
console.assert(this.url, 'url must be provided or collection must have url');
options.url = this.url.replace('api', 'signalr');
}
var processMessage = function (options) {
var self = this;
var _getStatus = function (status) {
switch (status) {
case 0:
return 'connecting';
case 1:
return 'connected';
case 2:
return 'reconnecting';
case 4:
return 'disconnected';
default:
throw 'invalid status ' + status;
}
var model = new collection.model(options.resource, {parse: true});
collection.add(model, {merge: true});
console.log(options.action + ": %O", options.resource);
};
this.signalRconnection = $.connection(options.url);
this.signalRconnection.stateChanged(function (change) {
console.debug('{0} [{1}]'.format(options.url, _getStatus(change.newState)));
});
this.signalRconnection.received(function (message) {
console.debug(message);
self.fetch();
});
this.signalRconnection.start({ transport:
require(
[
'longPolling'
] });
'app'
], function (app) {
collection.listenTo(app.vent, 'server:' + collection.url.replace('/api/', ''), processMessage)
});
return this;
},
unbindSignalR: function () {
if(this.signalRconnection){
this.signalRconnection.stop();
delete this.signalRconnection;
}
}});
});

View file

@ -1,31 +1,36 @@
'use strict';
'use strict';
define(
[
'app',
'backbone',
'ProgressMessaging/ProgressMessageModel',
'Shared/Messenger',
'Mixins/backbone.signalr.mixin'
], function (Backbone, ProgressMessageModel, Messenger) {
], function (App, Backbone, Messenger) {
var ProgressMessageCollection = Backbone.Collection.extend({
url : window.ApiRoot + '/progressmessage',
model: ProgressMessageModel
model: Backbone.Model,
initialize: function(){
}
});
var collection = new ProgressMessageCollection().bindSignalR();
var collection = new ProgressMessageCollection();//.bindSignalR();
collection.signalRconnection.received(function (message) {
/* collection.signalRconnection.received(function (message) {
var type = getMessengerType(message.status);
var hideAfter = type === 'info' ? 60 : 5;
var type = getMessengerType(message.status);
var hideAfter = type === 'info' ? 60 :5;
Messenger.show({
id : message.commandId,
message : message.message,
type : type,
hideAfter: hideAfter
});
});
Messenger.show({
id : message.commandId,
message : message.message,
type : type,
hideAfter: hideAfter
});
});*/
var getMessengerType = function (status) {
switch (status) {

View file

@ -1,8 +0,0 @@
'use strict';
define(
[
'backbone'
], function (Backbone) {
return Backbone.Model.extend({
});
});

View file

@ -6,11 +6,12 @@ require(
'Controller',
'Series/SeriesCollection',
'ProgressMessaging/ProgressMessageCollection',
'Commands/CommandMessengerCollectionView',
'Shared/Actioneer',
'Navbar/NavbarView',
'jQuery/RouteBinder',
'jquery'
], function (App, Marionette, Controller, SeriesCollection, ProgressMessageCollection, Actioneer, NavbarView, RouterBinder, $) {
], function (App, Marionette, Controller, SeriesCollection, ProgressMessageCollection, CommandMessengerCollectionView, Actioneer, NavbarView, RouterBinder, $) {
var Router = Marionette.AppRouter.extend({
@ -40,11 +41,11 @@ require(
App.Router = new Router();
SeriesCollection.fetch().done(function () {
Backbone.history.start({ pushState: true });
RouterBinder.bind(App.Router);
App.navbarRegion.show(new NavbarView());
$('body').addClass('started');
});
Backbone.history.start({ pushState: true });
RouterBinder.bind(App.Router);
App.navbarRegion.show(new NavbarView());
$('body').addClass('started');
});
});
return App.Router;

View file

@ -3,8 +3,8 @@ define(
[
'marionette',
'Series/SeasonCollection',
'Shared/Actioneer'
], function (Marionette, SeasonCollection, Actioneer) {
'Cells/ToggleCell'
], function (Marionette, Backgrid, SeasonCollection, ToggleCell) {
return Marionette.Layout.extend({
template: 'SeasonPass/SeriesLayoutTemplate',

View file

@ -1,35 +1,41 @@
<div class="series-season" id="season-{{seasonNumber}}">
<h2>
<i class="x-season-monitored clickable" title="Toggle season monitored status"/>
{{#if seasonNumber}}
Season {{seasonNumber}}
Season {{seasonNumber}}
{{else}}
Specials
Specials
{{/if}}
{{#if_eq episodeCount compare=0}}
<i class="icon-nd-status season-status status-primary" title="No aired episodes"/>
<i class="icon-nd-status season-status status-primary" title="No aired episodes"/>
{{else}}
{{#if_eq percentOfEpisodes compare=100}}
<i class="icon-nd-status season-status status-success" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/>
{{else}}
<i class="icon-nd-status season-status status-danger" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/>
{{/if_eq}}
{{#if_eq percentOfEpisodes compare=100}}
<i class="icon-nd-status season-status status-success" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/>
{{else}}
<i class="icon-nd-status season-status status-danger" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/>
{{/if_eq}}
{{/if_eq}}
<span class="season-actions pull-right">
<i class="icon-nd-rename x-season-rename" title="Rename all episodes in season {{seasonNumber}}"/>
<i class="icon-search x-season-search" title="Search for all episodes in season {{seasonNumber}}"/>
<div class="x-season-rename">
<i class="icon-nd-rename" title="Rename all episodes in season {{seasonNumber}}"/>
</div>
<div class="x-season-search">
<i class="icon-search" title="Search for all episodes in season {{seasonNumber}}"/>
</div>
</span>
</h2>
<div class="x-episode-grid"></div>
<div class="show-hide-episodes x-show-hide-episodes">
<h4>
{{#if showingEpisodes}}
<i class="icon-chevron-sign-up"/> Hide Episodes
<i class="icon-chevron-sign-up"/>
Hide Episodes
{{else}}
<i class="icon-chevron-sign-down"/> Show Episodes
<i class="icon-chevron-sign-down"/>
Show Episodes
{{/if}}
</h4>
</div>

View file

@ -8,27 +8,20 @@ define(
'Series/SeasonCollection',
'Series/Details/SeasonCollectionView',
'Series/Details/InfoView',
'Commands/CommandController',
'Shared/LoadingView',
'Shared/Actioneer',
'backstrech',
'Mixins/backbone.signalr.mixin'
], function (App,
Marionette,
EpisodeCollection,
EpisodeFileCollection,
SeasonCollection,
SeasonCollectionView,
InfoView,
LoadingView,
Actioneer) {
], function (App, Marionette, EpisodeCollection, EpisodeFileCollection, SeasonCollection, SeasonCollectionView, InfoView, CommandController, LoadingView, Actioneer) {
return Marionette.Layout.extend({
itemViewContainer: '.x-series-seasons',
template : 'Series/Details/SeriesDetailsTemplate',
regions: {
seasons : '#seasons',
info : '#info'
seasons: '#seasons',
info : '#info'
},
ui: {
@ -73,6 +66,32 @@ define(
this._showSeasons();
this._setMonitoredState();
this._showInfo();
},
onRender: function(){
Actioneer.bindToCommand({
element: this.ui.refresh,
command: {
name : 'refreshSeries'
}
});
Actioneer.bindToCommand({
element: this.ui.search,
command: {
name : 'seriesSearch'
}
});
Actioneer.bindToCommand({
element: this.ui.rename,
command: {
name : 'renameSeries'
}
});
},
_getFanArt: function () {
@ -127,15 +146,9 @@ define(
},
_refreshSeries: function () {
Actioneer.ExecuteCommand({
command : 'refreshSeries',
properties: {
seriesId: this.model.get('id')
},
element : this.ui.refresh,
leaveIcon : true,
context : this,
onSuccess : this._showSeasons
CommandController.Execute('refreshSeries', {
name : 'refreshSeries',
seriesId: this.model.id
});
},
@ -147,27 +160,18 @@ define(
},
_renameSeries: function () {
Actioneer.ExecuteCommand({
command : 'renameSeries',
properties : {
seriesId: this.model.get('id')
},
element : this.ui.rename,
context : this,
onSuccess : this._refetchEpisodeFiles,
errorMessage: 'Series search failed'
CommandController.Execute('renameSeries', {
name : 'renameSeries',
seriesId: this.model.id
});
},
_seriesSearch: function () {
Actioneer.ExecuteCommand({
command : 'seriesSearch',
properties : {
seriesId: this.model.get('id')
},
element : this.ui.search,
errorMessage: 'Series search failed',
startMessage: 'Search for {0} started'.format(this.model.get('title'))
CommandController.Execute('seriesSearch', {
name : 'seriesSearch',
seriesId: this.model.id
});
},
@ -187,15 +191,10 @@ define(
series : self.model
});
App.reqres.setHandler(App.Reqres.GetEpisodeFileById, function(episodeFileId){
App.reqres.setHandler(App.Reqres.GetEpisodeFileById, function (episodeFileId) {
return self.episodeFileCollection.get(episodeFileId);
});
/* self.episodeCollection.bindSignalR({
onReceived: seasonCollectionView.onEpisodeGrabbed,
context : seasonCollectionView
});*/
self.seasons.show(seasonCollectionView);
});
},
@ -208,7 +207,7 @@ define(
this.episodeFileCollection.fetch();
},
_onSeasonRenamed: function(event) {
_onSeasonRenamed: function (event) {
if (this.model.get('id') === event.series.get('id')) {
this._refetchEpisodeFiles();
}

View file

@ -4,12 +4,21 @@
<h1>
<i class="icon-bookmark x-monitored clickable" title="Toggle monitored state for entire series"/>
{{title}}
<span class="series-actions pull-right">
<i class="icon-refresh x-refresh" title="Update series info and scan disk"/>
<i class="icon-nd-rename x-rename" title="Rename all episodes in this series"/>
<i class="icon-search x-search" title="Search for all episodes in this series"/>
<i class="icon-nd-edit x-edit" title="Edit series"/>
</span>
<div class="series-actions pull-right">
<div class="x-refresh">
<i class="icon-refresh icon-can-spin" title="Update series info and scan disk"/>
</div>
<div class="x-rename">
<i class="icon-nd-rename" title="Rename all episodes in this series"/>
</div>
<div class="x-search">
<i class="icon-search" title="Search for all episodes in this series"/>
</div>
<div>
<i class="icon-nd-edit" title="Edit series"/>
</div>
</div>
</h1>
</div>
<div class="row series-detail-overview">

View file

@ -6,18 +6,18 @@
overflow : visible;
.series-poster {
padding-left: 20px;
width : 168px;
padding-left : 20px;
width : 168px;
}
.form-horizontal {
margin-top: 10px;
margin-top : 10px;
}
}
.delete-series-modal {
.path {
margin-left: 30px;
margin-left : 30px;
}
}
@ -54,7 +54,7 @@
.show-hide-episodes {
.clickable();
text-align: center;
text-align : center;
i {
.clickable();
@ -84,24 +84,24 @@
text-align : center;
.progress {
text-align: left;
text-align : left;
margin-top : 5px;
left: 0px;
width: 170px;
left : 0px;
width : 170px;
.progressbar-front-text {
width: 170px;
width : 170px;
}
}
}
.title-container {
position: relative;
position : relative;
.title {
position : absolute;
top : -100px;
opacity: 0.0;
top : -100px;
opacity : 0.0;
}
}
@ -188,7 +188,7 @@
.episode-detail-modal {
.episode-info {
margin-bottom: 10px;
margin-bottom : 10px;
}
.episode-overview {
@ -221,35 +221,41 @@
}
.season-actions, .series-actions {
font-size : 24px;
div {
display : inline-block
}
text-transform : none;
i {
.clickable;
font-size : 24px;
padding-left : 5px;
}
}
.series-stats {
font-size: 11px;
font-size : 11px;
}
.series-legend {
padding-top: 5px;
padding-top : 5px;
}
.seasonpass-series {
.card;
margin : 20px 0px;
margin : 20px 0px;
.title {
font-weight: 300;
font-size: 24px;
line-height: 30px;
margin-left: 5px;
font-weight : 300;
font-size : 24px;
line-height : 30px;
margin-left : 5px;
}
.season-select {
margin-bottom: 0px;
margin-bottom : 0px;
}
.expander {
@ -260,11 +266,11 @@
}
.season-grid {
margin-top: 10px;
margin-top : 10px;
}
}
.season-status {
font-size: 16px;
vertical-align: middle !important;
}
font-size : 16px;
vertical-align : middle !important;
}

View file

@ -3,17 +3,55 @@ define(
[
'Commands/CommandController',
'Commands/CommandCollection',
'Shared/Messenger'],
function(CommandController, CommandCollection, Messenger) {
'Shared/Messenger',
'jQuery/jquery.spin'
], function (CommandController, CommandCollection, Messenger) {
var actioneer = Marionette.AppRouter.extend({
initialize: function () {
this.trackedCommands = [];
this.trackedCommands =
[
];
CommandCollection.fetch();
this.listenTo(CommandCollection, 'sync', this._handleCommands);
},
bindToCommand: function (options) {
var self = this;
options.idleIcon = this._getIconClass(options.element);
var existingCommand = CommandCollection.findCommand(options.command);
if (existingCommand) {
this._bindToCommandModel.call(this, existingCommand, options);
}
this.listenTo(CommandCollection, 'add sync', function (model) {
if (model.isSameCommand(options.command)) {
self._bindToCommandModel.call(self, model, options);
}
});
},
_bindToCommandModel: function bindToCommand(model, options) {
if (!model.isActive()) {
options.element.stopSpin();
return;
}
this.listenTo(model, 'change:state', function (model) {
if (!model.isActive()) {
options.element.stopSpin();
}
});
options.element.startSpin();
},
ExecuteCommand: function (options) {
options.iconClass = this._getIconClass(options.element);
@ -62,7 +100,7 @@ define(
_handleCommands: function () {
var self = this;
_.each(this.trackedCommands, function (trackedCommand){
_.each(this.trackedCommands, function (trackedCommand) {
if (trackedCommand.completed === true) {
return;
}
@ -95,12 +133,12 @@ define(
});
},
_getIconClass: function(element) {
_getIconClass: function (element) {
if (!element) {
return '';
}
return element.attr('class').match(/(?:^|\s)icon\-.+?(?:$|\s)/)[0];
return element.find('i').attr('class').match(/(?:^|\s)icon\-.+?(?:$|\s)/)[0];
},
_setSpinnerOnElement: function (options) {
@ -195,4 +233,4 @@ define(
});
return new actioneer();
});
});

View file

@ -1,4 +1,4 @@
'use strict';
'use strict';
define(
[
'marionette'

View file

@ -1,13 +1,14 @@
'use strict';
define(function () {
return {
show: function (options) {
if (!options.type) {
options.type = 'info';
}
if (!options.hideAfter) {
if (options.hideAfter === undefined) {
switch (options.type) {
case 'info':
options.hideAfter = 5;
@ -18,7 +19,7 @@ define(function () {
break;
default :
options.hideAfter = 0;
options.hideAfter = 5;
}
}
@ -31,6 +32,7 @@ define(function () {
});
},
monitor: function (options) {
if (!options.promise) {

View file

@ -0,0 +1,51 @@
'use strict';
define(
[
'app',
'signalR'
], function () {
return {
appInitializer: function () {
console.log('starting signalR');
var getStatus = function (status) {
switch (status) {
case 0:
return 'connecting';
case 1:
return 'connected';
case 2:
return 'reconnecting';
case 4:
return 'disconnected';
default:
throw 'invalid status ' + status;
}
};
this.signalRconnection = $.connection("/signalr");
this.signalRconnection.stateChanged(function (change) {
console.debug('SignalR: [{0}]'.format(getStatus(change.newState)));
});
this.signalRconnection.received(function (message) {
require(
[
'app'
], function (app) {
app.vent.trigger('server:' + message.name, message.body);
})
});
this.signalRconnection.start({ transport:
[
'longPolling'
] });
return this;
}
};
});

View file

@ -3,9 +3,9 @@ define(
[
'app',
'marionette',
'Shared/Actioneer',
'Shared/Messenger'
], function (App, Marionette, Actioneer, Messenger) {
'Commands/CommandController',
'Shared/Actioneer'
], function (App, Marionette, CommandController, Actioneer) {
return Marionette.ItemView.extend({
template : 'Shared/Toolbar/ButtonTemplate',
@ -15,13 +15,8 @@ define(
'click': 'onClick'
},
ui: {
icon: '.x-icon'
},
initialize: function () {
this.storageKey = this.model.get('menuKey') + ':' + this.model.get('key');
this.idle = true;
},
onRender: function () {
@ -30,33 +25,34 @@ define(
this.invokeCallback();
}
if(!this.model.get('title')){
if (!this.model.get('title')) {
this.$el.addClass('btn-icon-only');
}
var command = this.model.get('command');
if (command) {
Actioneer.bindToCommand({
command: {name: command},
element: this.$el
});
}
},
onClick: function () {
if (this.idle) {
this.invokeCallback();
this.invokeRoute();
this.invokeCommand();
if (this.$el.hasClass('disabled')) {
return;
}
this.invokeCallback();
this.invokeRoute();
this.invokeCommand();
},
invokeCommand: function () {
var command = this.model.get('command');
if (command) {
this.idle = false;
Actioneer.ExecuteCommand({
command : command,
button : this.$el,
element : this.ui.icon,
errorMessage : this.model.get('errorMessage'),
successMessage: this.model.get('successMessage'),
always : this._commandAlways,
context : this
});
CommandController.Execute(command);
}
},
@ -83,12 +79,6 @@ define(
if (callback) {
callback.call(this.model.ownerContext);
}
},
_commandAlways: function () {
if (!this.isClosed) {
this.idle = true;
}
}
});
});

View file

@ -1,4 +1,4 @@
'use strict';
'use strict';
define(
[
'app',
@ -6,15 +6,13 @@ define(
'System/StatusModel',
'System/About/View',
'Logs/Layout',
'Shared/Toolbar/ToolbarLayout',
'Shared/LoadingView'
'Shared/Toolbar/ToolbarLayout'
], function (App,
Marionette,
StatusModel,
AboutView,
LogsLayout,
ToolbarLayout,
LoadingView) {
ToolbarLayout) {
return Marionette.Layout.extend({
template: 'System/LayoutTemplate',
@ -38,21 +36,7 @@ define(
title : 'Check for Update',
icon : 'icon-nd-update',
command: 'applicationUpdate'
},
// {
// title : 'Restart',
// icon : 'icon-repeat',
// command : 'restart',
// successMessage: 'NzbDrone restart has been triggered',
// errorMessage : 'Failed to restart NzbDrone'
// },
// {
// title : 'Shutdown',
// icon : 'icon-power-off',
// command : 'shutdown',
// successMessage: 'NzbDrone shutdown has started',
// errorMessage : 'Failed to shutdown NzbDrone'
// }
}
]
},

View file

@ -181,8 +181,9 @@ require.config({
define(
[
'marionette',
'Shared/SignalRBroadcaster',
'Instrumentation/StringFormat'
], function (Marionette) {
], function (Marionette, SignalRBroadcaster) {
var app = new Marionette.Application();
@ -210,6 +211,8 @@ define(
console.log('starting application');
});
app.addInitializer(SignalRBroadcaster.appInitializer, {app: app});
app.addRegions({
navbarRegion: '#nav-region',
mainRegion : '#main-region',

45
UI/jQuery/jquery.spin.js Normal file
View file

@ -0,0 +1,45 @@
define(
[
'jquery'
], function ($) {
'use strict';
$.fn.startSpin = function () {
var icon = this.find('i');
var iconClasses = icon.attr('class').match(/(?:^|\s)icon\-.+?(?:$|\s)/);
if (iconClasses.length === 0) {
return this;
}
var iconClass = $.trim(iconClasses[0]);
this.addClass('disabled');
if (icon.hasClass('icon-can-spin')) {
icon.addClass('icon-spin');
}
else {
icon.attr('data-idle-icon', iconClass);
icon.removeClass(iconClass);
icon.addClass('icon-nd-spinner');
}
return this;
};
$.fn.stopSpin = function () {
var icon = this.find('i');
this.removeClass('disabled');
icon.removeClass('icon-spin icon-nd-spinner');
var idleIcon = icon.attr('data-idle-icon');
if (idleIcon) {
icon.addClass(idleIcon);
}
return this;
};
});