diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj
index 9569bf6f3..00f5f96e3 100644
--- a/src/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/src/NzbDrone.Api/NzbDrone.Api.csproj
@@ -152,6 +152,7 @@
+
diff --git a/src/NzbDrone.Api/Series/SeriesEditorModule.cs b/src/NzbDrone.Api/Series/SeriesEditorModule.cs
new file mode 100644
index 000000000..75a174b90
--- /dev/null
+++ b/src/NzbDrone.Api/Series/SeriesEditorModule.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.Remoting.Messaging;
+using Nancy;
+using NzbDrone.Api.Extensions;
+using NzbDrone.Api.Mapping;
+using NzbDrone.Core.Tv;
+
+namespace NzbDrone.Api.Series
+{
+ public class SeriesEditorModule : NzbDroneApiModule
+ {
+ private readonly ISeriesService _seriesService;
+
+ public SeriesEditorModule(ISeriesService seriesService)
+ : base("/series/editor")
+ {
+ _seriesService = seriesService;
+ Put["/"] = series => SaveAll();
+ }
+
+ private Response SaveAll()
+ {
+ //Read from request
+ var series = Request.Body.FromJson>().InjectTo>();
+
+ return _seriesService.UpdateSeries(series)
+ .InjectTo>()
+ .AsResponse(HttpStatusCode.Accepted);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 313d67d3a..42a514e8c 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -212,6 +212,7 @@
+
diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs
new file mode 100644
index 000000000..9522472e9
Binary files /dev/null and b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs differ
diff --git a/src/NzbDrone.Core/Tv/SeriesService.cs b/src/NzbDrone.Core/Tv/SeriesService.cs
index c3767eaf6..18b67a732 100644
--- a/src/NzbDrone.Core/Tv/SeriesService.cs
+++ b/src/NzbDrone.Core/Tv/SeriesService.cs
@@ -24,6 +24,7 @@ public interface ISeriesService
void DeleteSeries(int seriesId, bool deleteFiles);
List GetAllSeries();
Series UpdateSeries(Series series);
+ List UpdateSeries(List series);
bool SeriesPathExists(string folder);
}
@@ -138,6 +139,22 @@ public Series UpdateSeries(Series series)
return _seriesRepository.Update(series);
}
+ public List UpdateSeries(List series)
+ {
+ foreach (var s in series)
+ {
+ if (!String.IsNullOrWhiteSpace(s.RootFolderPath))
+ {
+ var folderName = new DirectoryInfo(s.Path).Name;
+ s.Path = Path.Combine(s.RootFolderPath, folderName);
+ }
+ }
+
+ _seriesRepository.UpdateMany(series);
+
+ return series;
+ }
+
public bool SeriesPathExists(string folder)
{
return _seriesRepository.SeriesPathExists(folder);
diff --git a/src/NzbDrone.Integration.Test/Client/SeriesClient.cs b/src/NzbDrone.Integration.Test/Client/SeriesClient.cs
index 1f0a572f9..01ec8bfc7 100644
--- a/src/NzbDrone.Integration.Test/Client/SeriesClient.cs
+++ b/src/NzbDrone.Integration.Test/Client/SeriesClient.cs
@@ -19,6 +19,13 @@ public List Lookup(string term)
return Get>(request);
}
+ public List Editor(List series)
+ {
+ var request = BuildRequest("editor");
+ request.AddBody(series);
+ return Put>(request);
+ }
+
public SeriesResource Get(string slug, HttpStatusCode statusCode = HttpStatusCode.OK)
{
var request = BuildRequest(slug);
diff --git a/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj b/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
index b1ee17afc..c984ef79c 100644
--- a/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
+++ b/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj
@@ -102,6 +102,7 @@
+
diff --git a/src/NzbDrone.Integration.Test/SeriesEditorIntegrationTest.cs b/src/NzbDrone.Integration.Test/SeriesEditorIntegrationTest.cs
new file mode 100644
index 000000000..c0634ea0c
--- /dev/null
+++ b/src/NzbDrone.Integration.Test/SeriesEditorIntegrationTest.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Net;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Api.Series;
+using System.Linq;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Integration.Test
+{
+ [TestFixture]
+ public class SeriesEditorIntegrationTest : IntegrationTest
+ {
+ private void GivenExistingSeries()
+ {
+ foreach (var title in new[] { "90210", "Dexter" })
+ {
+ var newSeries = Series.Lookup(title).First();
+
+ newSeries.QualityProfileId = 1;
+ newSeries.Path = String.Format(@"C:\Test\{0}", title).AsOsAgnostic();
+
+ Series.Post(newSeries);
+ }
+ }
+
+ [Test]
+ public void should_be_able_to_update_multiple_series()
+ {
+ GivenExistingSeries();
+
+ var series = Series.All();
+
+ foreach (var s in series)
+ {
+ s.QualityProfileId = 2;
+ }
+
+ var result = Series.Editor(series);
+
+ result.Should().HaveCount(2);
+ result.TrueForAll(s => s.QualityProfileId == 2).Should().BeTrue();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Integration.Test/SeriesIntegrationTest.cs b/src/NzbDrone.Integration.Test/SeriesIntegrationTest.cs
index df6dce79d..eef706a78 100644
--- a/src/NzbDrone.Integration.Test/SeriesIntegrationTest.cs
+++ b/src/NzbDrone.Integration.Test/SeriesIntegrationTest.cs
@@ -45,7 +45,6 @@ public void should_be_able_to_add_and_delete_series()
Series.All().Should().BeEmpty();
}
-
[Test]
public void should_be_able_to_find_series_by_id()
{
@@ -61,7 +60,6 @@ public void should_be_able_to_find_series_by_id()
Series.Get(series.Id).Should().NotBeNull();
}
-
[Test]
public void invalid_id_should_return_404()
{
diff --git a/src/UI/AppLayout.js b/src/UI/AppLayout.js
index b063516f3..a0988a243 100644
--- a/src/UI/AppLayout.js
+++ b/src/UI/AppLayout.js
@@ -1,20 +1,22 @@
define(
[
'marionette',
- 'Shared/Modal/ModalRegion'
- ], function (Marionette, ModalRegion) {
+ 'Shared/Modal/ModalRegion',
+ 'Shared/ControlPanel/ControlPanelRegion'
+ ], function (Marionette, ModalRegion, ControlPanelRegion) {
'use strict';
var Layout = Marionette.Layout.extend({
regions: {
- navbarRegion: '#nav-region',
- mainRegion : '#main-region'
+ navbarRegion : '#nav-region',
+ mainRegion : '#main-region'
},
initialize: function () {
this.addRegions({
- modalRegion: ModalRegion
+ modalRegion : ModalRegion,
+ controlPanelRegion: ControlPanelRegion
});
}
});
diff --git a/src/UI/Cells/SeasonFolderCell.js b/src/UI/Cells/SeasonFolderCell.js
new file mode 100644
index 000000000..5c9cc6d1b
--- /dev/null
+++ b/src/UI/Cells/SeasonFolderCell.js
@@ -0,0 +1,16 @@
+'use strict';
+define(
+ [
+ 'backgrid'
+ ], function (Backgrid) {
+ return Backgrid.Cell.extend({
+
+ className : 'season-folder-cell',
+
+ render: function () {
+ var seasonFolder = this.model.get('seasonFolder');
+ this.$el.html(seasonFolder.toString());
+ return this;
+ }
+ });
+ });
diff --git a/src/UI/Cells/cells.less b/src/UI/Cells/cells.less
index 6efd806ad..b2ba4d140 100644
--- a/src/UI/Cells/cells.less
+++ b/src/UI/Cells/cells.less
@@ -117,4 +117,8 @@ td.delete-episode-file-cell {
i {
.clickable();
}
+}
+
+.series-status-cell {
+ width: 16px;
}
\ No newline at end of file
diff --git a/src/UI/Content/Backgrid/selectall.less b/src/UI/Content/Backgrid/selectall.less
index 86f6e0107..322853304 100644
--- a/src/UI/Content/Backgrid/selectall.less
+++ b/src/UI/Content/Backgrid/selectall.less
@@ -6,8 +6,7 @@
Licensed under the MIT @license.
*/
-.backgrid {
- .select-row-cell, .select-all-header-cell {
- text-align: center;
- }
+.select-row-cell, .select-all-header-cell {
+ text-align: center;
+ width: 16px;
}
\ No newline at end of file
diff --git a/src/UI/Content/Overrides/messenger.less b/src/UI/Content/Overrides/messenger.less
new file mode 100644
index 000000000..dec17fe5a
--- /dev/null
+++ b/src/UI/Content/Overrides/messenger.less
@@ -0,0 +1,5 @@
+body.control-panel-visible {
+ ul.messenger.messenger-fixed.messenger-on-bottom {
+ bottom: 95px;
+ }
+}
\ No newline at end of file
diff --git a/src/UI/Content/overrides.less b/src/UI/Content/overrides.less
index 040b76e92..1c0ec7e73 100644
--- a/src/UI/Content/overrides.less
+++ b/src/UI/Content/overrides.less
@@ -2,3 +2,4 @@
@import "Overrides/browser";
@import "Overrides/bootstrap.toggle-switch";
@import "Overrides/fullcalendar";
+@import "Overrides/messenger";
diff --git a/src/UI/Content/theme.less b/src/UI/Content/theme.less
index eb99df003..8bf71358e 100644
--- a/src/UI/Content/theme.less
+++ b/src/UI/Content/theme.less
@@ -69,6 +69,12 @@
color : white;
}
+.control-panel-visible {
+ #scroll-up {
+ bottom: 100px;
+ }
+}
+
.label-large {
padding : 4px 6px;
font-size : 16px;
@@ -104,7 +110,7 @@ body {
}
}
-footer {
+.footer {
font-size : 13px;
font-weight : lighter;
padding-top : 0px;
@@ -192,4 +198,19 @@ footer {
.file-path {
.mono-space();
+}
+
+.control-panel {
+ .card(#333333);
+
+ color: #f5f5f5;
+ background-color: #333333;
+ margin: 0px;
+ margin-bottom: -100px;
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ height: 55px;
+ opacity: 0;
}
\ No newline at end of file
diff --git a/src/UI/Controller.js b/src/UI/Controller.js
index bd5be1819..db0b13e2b 100644
--- a/src/UI/Controller.js
+++ b/src/UI/Controller.js
@@ -12,7 +12,8 @@ define(
'Release/ReleaseLayout',
'System/SystemLayout',
'SeasonPass/SeasonPassLayout',
- 'System/Update/UpdateLayout'
+ 'System/Update/UpdateLayout',
+ 'Series/Editor/SeriesEditorLayout'
], function (NzbDroneController,
AppLayout,
Marionette,
@@ -24,7 +25,8 @@ define(
ReleaseLayout,
SystemLayout,
SeasonPassLayout,
- UpdateLayout) {
+ UpdateLayout,
+ SeriesEditorLayout) {
return NzbDroneController.extend({
addSeries: function (action) {
@@ -72,7 +74,13 @@ define(
update: function () {
this.setTitle('Updates');
this.showMainRegion(new UpdateLayout());
+ },
+
+ seriesEditor: function () {
+ this.setTitle('Series Editor');
+ this.showMainRegion(new SeriesEditorLayout());
}
+
});
});
diff --git a/src/UI/Router.js b/src/UI/Router.js
index d8532756f..f2927787a 100644
--- a/src/UI/Router.js
+++ b/src/UI/Router.js
@@ -21,6 +21,7 @@ define(
'system' : 'system',
'system/:action' : 'system',
'seasonpass' : 'seasonPass',
+ 'serieseditor' : 'seriesEditor',
':whatever' : 'showNotFound'
}
});
diff --git a/src/UI/Series/Editor/SeriesEditorFooterView.js b/src/UI/Series/Editor/SeriesEditorFooterView.js
new file mode 100644
index 000000000..42b2b85f4
--- /dev/null
+++ b/src/UI/Series/Editor/SeriesEditorFooterView.js
@@ -0,0 +1,156 @@
+'use strict';
+define(
+ [
+ 'underscore',
+ 'marionette',
+ 'backgrid',
+ 'vent',
+ 'Series/SeriesCollection',
+ 'Quality/QualityProfileCollection',
+ 'AddSeries/RootFolders/Collection',
+ 'Shared/Toolbar/ToolbarLayout',
+ 'AddSeries/RootFolders/Layout',
+ 'Config'
+ ], function (_,
+ Marionette,
+ Backgrid,
+ vent,
+ SeriesCollection,
+ QualityProfiles,
+ RootFolders,
+ ToolbarLayout,
+ RootFolderLayout,
+ Config) {
+ return Marionette.ItemView.extend({
+ template: 'Series/Editor/SeriesEditorFooterViewTemplate',
+
+ ui: {
+ monitored : '.x-monitored',
+ qualityProfile: '.x-quality-profiles',
+ seasonFolder : '.x-season-folder',
+ rootFolder : '.x-root-folder',
+ selectedCount : '.x-selected-count',
+ saveButton : '.x-save',
+ container : '.series-editor-footer'
+ },
+
+ events: {
+ 'click .x-save' : '_updateAndSave',
+ 'change .x-root-folder': '_rootFolderChanged'
+ },
+
+ templateHelpers: function () {
+ return {
+ qualityProfiles: QualityProfiles,
+ rootFolders : RootFolders.toJSON()
+ };
+ },
+
+ initialize: function (options) {
+ RootFolders.fetch().done(function () {
+ RootFolders.synced = true;
+ });
+
+ this.editorGrid = options.editorGrid;
+ this.listenTo(SeriesCollection, 'backgrid:selected', this._updateInfo);
+ this.listenTo(RootFolders, 'all', this.render);
+ },
+
+ onRender: function () {
+ this._updateInfo();
+ },
+
+ _updateAndSave: function () {
+ var selected = this.editorGrid.getSelectedModels();
+
+ var monitored = this.ui.monitored.val();
+ var profile = this.ui.qualityProfile.val();
+ var seasonFolder = this.ui.seasonFolder.val();
+ var rootFolder = this.ui.rootFolder.val();
+
+ _.each(selected, function (model) {
+ if (monitored === 'true') {
+ model.set('monitored', true);
+ }
+
+ else if (monitored === 'false') {
+ model.set('monitored', false);
+ }
+
+ if (profile !== 'noChange') {
+ model.set('qualityProfileId', parseInt(profile, 10));
+ }
+
+ if (seasonFolder === 'true') {
+ model.set('seasonFolder', true);
+ }
+
+ else if (seasonFolder === 'false') {
+ model.set('seasonFolder', false);
+ }
+
+ if (rootFolder !== 'noChange') {
+ var rootFolderPath = RootFolders.get(parseInt(rootFolder, 10));
+
+ model.set('rootFolderPath', rootFolderPath.get('path'));
+ }
+ });
+
+ SeriesCollection.save();
+
+ this.listenTo(SeriesCollection, 'save', this._afterSave);
+ },
+
+ _updateInfo: function () {
+ var selected = this.editorGrid.getSelectedModels();
+ var selectedCount = selected.length;
+
+ this.ui.selectedCount.html('{0} series selected'.format(selectedCount));
+
+ if (selectedCount === 0) {
+ this.ui.monitored.attr('disabled', '');
+ this.ui.qualityProfile.attr('disabled', '');
+ this.ui.seasonFolder.attr('disabled', '');
+ this.ui.rootFolder.attr('disabled', '');
+ this.ui.saveButton.attr('disabled', '');
+ }
+
+ else {
+ this.ui.monitored.removeAttr('disabled', '');
+ this.ui.qualityProfile.removeAttr('disabled', '');
+ this.ui.seasonFolder.removeAttr('disabled', '');
+ this.ui.rootFolder.removeAttr('disabled', '');
+ this.ui.saveButton.removeAttr('disabled', '');
+ }
+ },
+
+ _rootFolderChanged: function () {
+ var rootFolderValue = this.ui.rootFolder.val();
+ if (rootFolderValue === 'addNew') {
+ var rootFolderLayout = new RootFolderLayout();
+ this.listenToOnce(rootFolderLayout, 'folderSelected', this._setRootFolder);
+ vent.trigger(vent.Commands.OpenModalCommand, rootFolderLayout);
+ }
+ else {
+ Config.setValue(Config.Keys.DefaultRootFolderId, rootFolderValue);
+ }
+ },
+
+ _setRootFolder: function (options) {
+ vent.trigger(vent.Commands.CloseModalCommand);
+ this.ui.rootFolder.val(options.model.id);
+ this._rootFolderChanged();
+ },
+
+ _afterSave: function () {
+ this.ui.monitored.val('noChange');
+ this.ui.qualityProfile.val('noChange');
+ this.ui.seasonFolder.val('noChange');
+ this.ui.rootFolder.val('noChange');
+
+ SeriesCollection.each(function (model) {
+ model.trigger('backgrid:select', model, false);
+ });
+ }
+ });
+ });
diff --git a/src/UI/Series/Editor/SeriesEditorFooterViewTemplate.html b/src/UI/Series/Editor/SeriesEditorFooterViewTemplate.html
new file mode 100644
index 000000000..ad5b16e14
--- /dev/null
+++ b/src/UI/Series/Editor/SeriesEditorFooterViewTemplate.html
@@ -0,0 +1,50 @@
+
\ No newline at end of file
diff --git a/src/UI/Series/Editor/SeriesEditorLayout.js b/src/UI/Series/Editor/SeriesEditorLayout.js
new file mode 100644
index 000000000..e82149fa9
--- /dev/null
+++ b/src/UI/Series/Editor/SeriesEditorLayout.js
@@ -0,0 +1,148 @@
+'use strict';
+define(
+ [
+ 'vent',
+ 'marionette',
+ 'backgrid',
+ 'Series/Index/EmptyView',
+ 'Series/SeriesCollection',
+ 'Cells/SeriesTitleCell',
+ 'Cells/QualityProfileCell',
+ 'Cells/SeriesStatusCell',
+ 'Cells/SeasonFolderCell',
+ 'Shared/Toolbar/ToolbarLayout',
+ 'Series/Editor/SeriesEditorFooterView'
+ ], function (vent,
+ Marionette,
+ Backgrid,
+ EmptyView,
+ SeriesCollection,
+ SeriesTitleCell,
+ QualityProfileCell,
+ SeriesStatusCell,
+ SeasonFolderCell,
+ ToolbarLayout,
+ FooterView) {
+ return Marionette.Layout.extend({
+ template: 'Series/Editor/SeriesEditorLayoutTemplate',
+
+ regions: {
+ seriesRegion: '#x-series-editor',
+ toolbar : '#x-toolbar'
+ },
+
+ ui: {
+ monitored : '.x-monitored',
+ qualityProfiles: '.x-quality-profiles',
+ rootFolder : '.x-root-folder',
+ selectedCount : '.x-selected-count'
+ },
+
+ events: {
+ 'click .x-save' : '_updateAndSave',
+ 'change .x-root-folder': '_rootFolderChanged'
+ },
+
+ columns:
+ [
+ {
+ name : '',
+ cell : 'select-row',
+ headerCell: 'select-all',
+ sortable : false
+ },
+ {
+ name : 'statusWeight',
+ label : '',
+ cell : SeriesStatusCell
+ },
+ {
+ name : 'title',
+ label : 'Title',
+ cell : SeriesTitleCell,
+ cellValue: 'this'
+ },
+ {
+ name : 'qualityProfileId',
+ label: 'Quality',
+ cell : QualityProfileCell
+ },
+ {
+ name : 'monitored',
+ label: 'Season Folder',
+ cell : SeasonFolderCell
+ },
+ {
+ name : 'path',
+ label: 'Path',
+ cell : 'string'
+ }
+ ],
+
+ leftSideButtons: {
+ type : 'default',
+ storeState: false,
+ items :
+ [
+ {
+ title : 'Season Pass',
+ icon : 'icon-bookmark',
+ route : 'seasonpass'
+ },
+ {
+ title : 'Update Library',
+ icon : 'icon-refresh',
+ command : 'refreshseries',
+ successMessage: 'Library was updated!',
+ errorMessage : 'Library update failed!'
+ }
+ ]
+ },
+
+ onRender: function () {
+ this._showToolbar();
+ this._showTable();
+
+ this._fetchCollection();
+ },
+
+ onClose: function () {
+ vent.trigger(vent.Commands.CloseControlPanelCommand);
+ },
+
+ _showTable: function () {
+ if (SeriesCollection.length === 0) {
+ this.seriesRegion.show(new EmptyView());
+ this.toolbar.close();
+ return;
+ }
+
+ this.editorGrid = new Backgrid.Grid({
+ collection: SeriesCollection,
+ columns : this.columns,
+ className : 'table table-hover'
+ });
+
+ this.seriesRegion.show(this.editorGrid);
+ this._showFooter();
+ },
+
+ _fetchCollection: function () {
+ SeriesCollection.fetch();
+ },
+
+ _showToolbar: function () {
+ this.toolbar.show(new ToolbarLayout({
+ left :
+ [
+ this.leftSideButtons
+ ],
+ context: this
+ }));
+ },
+
+ _showFooter: function () {
+ vent.trigger(vent.Commands.OpenControlPanelCommand, new FooterView({ editorGrid: this.editorGrid }));
+ }
+ });
+ });
diff --git a/src/UI/Series/Editor/SeriesEditorLayoutTemplate.html b/src/UI/Series/Editor/SeriesEditorLayoutTemplate.html
new file mode 100644
index 000000000..470e663eb
--- /dev/null
+++ b/src/UI/Series/Editor/SeriesEditorLayoutTemplate.html
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/src/UI/Series/Index/SeriesIndexLayout.js b/src/UI/Series/Index/SeriesIndexLayout.js
index b2716cfd4..4d794cf07 100644
--- a/src/UI/Series/Index/SeriesIndexLayout.js
+++ b/src/UI/Series/Index/SeriesIndexLayout.js
@@ -109,6 +109,11 @@ define(
icon : 'icon-bookmark',
route : 'seasonpass'
},
+ {
+ title : 'Series Editor',
+ icon : 'icon-nd-edit',
+ route : 'serieseditor'
+ },
{
title : 'RSS Sync',
icon : 'icon-rss',
diff --git a/src/UI/Series/SeriesCollection.js b/src/UI/Series/SeriesCollection.js
index b7458ef52..204a8759b 100644
--- a/src/UI/Series/SeriesCollection.js
+++ b/src/UI/Series/SeriesCollection.js
@@ -1,10 +1,11 @@
'use strict';
define(
[
+ 'underscore',
'backbone',
'Series/SeriesModel',
'api!series'
- ], function (Backbone, SeriesModel, SeriesData) {
+ ], function (_, Backbone, SeriesModel, SeriesData) {
var Collection = Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/series',
model: SeriesModel,
@@ -16,6 +17,31 @@ define(
state: {
sortKey: 'title',
order : -1
+ },
+
+ save: function () {
+ var self = this;
+
+ var proxy = _.extend( new Backbone.Model(),
+ {
+ id: '',
+
+ url: self.url + '/editor',
+
+ toJSON: function()
+ {
+ return self.filter(function (model) {
+ return model.hasChanged();
+ });
+ }
+ });
+
+ this.listenTo(proxy, 'sync', function (proxyModel, models) {
+ this.add(models, { merge: true });
+ this.trigger('save', this);
+ });
+
+ return proxy.save();
}
});
diff --git a/src/UI/Series/series.less b/src/UI/Series/series.less
index fb116379e..098c2c091 100644
--- a/src/UI/Series/series.less
+++ b/src/UI/Series/series.less
@@ -288,4 +288,17 @@
font-size : 24px;
margin-top : 3px;
}
+}
+
+//Editor
+
+.series-editor-footer {
+ width: 1100px;
+ color: #f5f5f5;
+ margin-left: auto;
+ margin-right: auto;
+
+ .selected-count {
+ margin-right: 10px;
+ }
}
\ No newline at end of file
diff --git a/src/UI/Shared/ControlPanel/ControlPanelController.js b/src/UI/Shared/ControlPanel/ControlPanelController.js
new file mode 100644
index 000000000..4e2a1100c
--- /dev/null
+++ b/src/UI/Shared/ControlPanel/ControlPanelController.js
@@ -0,0 +1,24 @@
+'use strict';
+define(
+ [
+ 'vent',
+ 'AppLayout',
+ 'marionette'
+ ], function (vent, AppLayout, Marionette) {
+
+ return Marionette.AppRouter.extend({
+
+ initialize: function () {
+ vent.on(vent.Commands.OpenControlPanelCommand, this._openControlPanel, this);
+ vent.on(vent.Commands.CloseControlPanelCommand, this._closeControlPanel, this);
+ },
+
+ _openControlPanel: function (view) {
+ AppLayout.controlPanelRegion.show(view);
+ },
+
+ _closeControlPanel: function () {
+ AppLayout.controlPanelRegion.closePanel();
+ }
+ });
+ });
diff --git a/src/UI/Shared/ControlPanel/ControlPanelRegion.js b/src/UI/Shared/ControlPanel/ControlPanelRegion.js
new file mode 100644
index 000000000..3b4ac55d5
--- /dev/null
+++ b/src/UI/Shared/ControlPanel/ControlPanelRegion.js
@@ -0,0 +1,35 @@
+'use strict';
+define(
+ [
+ 'jquery',
+ 'backbone',
+ 'marionette'
+ ], function ($,Backbone, Marionette) {
+ var region = Marionette.Region.extend({
+ el: '#control-panel-region',
+
+ constructor: function () {
+ Backbone.Marionette.Region.prototype.constructor.apply(this, arguments);
+ this.on('show', this.showPanel, this);
+ },
+
+ getEl: function (selector) {
+ var $el = $(selector);
+
+ return $el;
+ },
+
+ showPanel: function () {
+ $('body').addClass('control-panel-visible');
+ this.$el.animate({ 'margin-bottom': 0, 'opacity': 1 }, { queue: false, duration: 300 });
+ },
+
+ closePanel: function () {
+ $('body').removeClass('control-panel-visible');
+ this.$el.animate({ 'margin-bottom': -100, 'opacity': 0 }, { queue: false, duration: 300 });
+ this.reset();
+ }
+ });
+
+ return region;
+ });
diff --git a/src/UI/Shared/Modal/ModalController.js b/src/UI/Shared/Modal/ModalController.js
index 177092de3..9fcedea9a 100644
--- a/src/UI/Shared/Modal/ModalController.js
+++ b/src/UI/Shared/Modal/ModalController.js
@@ -15,8 +15,8 @@ define(
return Marionette.AppRouter.extend({
initialize: function () {
- vent.on(vent.Commands.OpenModalCommand, this._openModal, this);
- vent.on(vent.Commands.CloseModalCommand, this._closeModal, this);
+ vent.on(vent.Commands.OpenModalCommand, this._openControlPanel, this);
+ vent.on(vent.Commands.CloseModalCommand, this._closeControlPanel, this);
vent.on(vent.Commands.EditSeriesCommand, this._editSeries, this);
vent.on(vent.Commands.DeleteSeriesCommand, this._deleteSeries, this);
vent.on(vent.Commands.ShowEpisodeDetails, this._showEpisode, this);
@@ -25,12 +25,12 @@ define(
vent.on(vent.Commands.ShowRenamePreview, this._showRenamePreview, this);
},
- _openModal: function (view) {
+ _openControlPanel: function (view) {
AppLayout.modalRegion.show(view);
},
- _closeModal: function () {
- AppLayout.modalRegion.closeModal();
+ _closeControlPanel: function () {
+ AppLayout.modalRegion.closePanel();
},
_editSeries: function (options) {
diff --git a/src/UI/Shared/Modal/ModalRegion.js b/src/UI/Shared/Modal/ModalRegion.js
index f2a776050..414138ee7 100644
--- a/src/UI/Shared/Modal/ModalRegion.js
+++ b/src/UI/Shared/Modal/ModalRegion.js
@@ -11,7 +11,7 @@ define(
constructor: function () {
Backbone.Marionette.Region.prototype.constructor.apply(this, arguments);
- this.on('show', this.showModal, this);
+ this.on('show', this.showPanel, this);
},
getEl: function (selector) {
@@ -20,7 +20,7 @@ define(
return $el;
},
- showModal: function () {
+ showPanel: function () {
this.$el.addClass('modal hide fade');
//need tab index so close on escape works
@@ -32,7 +32,7 @@ define(
'backdrop': 'static'});
},
- closeModal: function () {
+ closePanel: function () {
$(this.el).modal('hide');
this.reset();
}
diff --git a/src/UI/app.js b/src/UI/app.js
index 51a0ee0e4..4edde0dc1 100644
--- a/src/UI/app.js
+++ b/src/UI/app.js
@@ -209,13 +209,15 @@ define(
'Series/SeriesController',
'Router',
'Shared/Modal/ModalController',
+ 'Shared/ControlPanel/ControlPanelController',
'System/StatusModel',
'Instrumentation/StringFormat',
'LifeCycle'
- ], function ($, Backbone, Marionette, RouteBinder, SignalRBroadcaster, NavbarView, AppLayout, SeriesController, Router, ModalController, serverStatusModel) {
+ ], function ($, Backbone, Marionette, RouteBinder, SignalRBroadcaster, NavbarView, AppLayout, SeriesController, Router, ModalController, ControlPanelController, serverStatusModel) {
new SeriesController();
new ModalController();
+ new ControlPanelController();
new Router();
var app = new Marionette.Application();
diff --git a/src/UI/index.html b/src/UI/index.html
index 84603cd00..e04003bae 100644
--- a/src/UI/index.html
+++ b/src/UI/index.html
@@ -48,7 +48,7 @@
-