1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-17 21:26:22 -04:00

Moved source code under src folder - massive change

This commit is contained in:
Mark McDowall
2013-10-02 18:01:32 -07:00
parent 2fc8123d6b
commit 5bf0e197ec
1499 changed files with 1054 additions and 1444 deletions

View File

@@ -0,0 +1,26 @@
'use strict';
define(
[
'backbone',
'Series/SeriesModel'
], function (Backbone, SeriesModel) {
return Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/series/lookup',
model: SeriesModel,
parse: function (response) {
var self = this;
_.each(response, function (model) {
model.id = undefined;
if (self.unmappedFolderModel) {
model.path = self.unmappedFolderModel.get('folder').path;
}
});
return response;
}
});
});

View File

@@ -0,0 +1,62 @@
'use strict';
define(
[
'app',
'marionette',
'AddSeries/RootFolders/Layout',
'AddSeries/Existing/AddExistingSeriesCollectionView',
'AddSeries/AddSeriesView',
'Quality/QualityProfileCollection',
'AddSeries/RootFolders/Collection',
'Series/SeriesCollection'
], function (App,
Marionette,
RootFolderLayout,
ExistingSeriesCollectionView,
AddSeriesView,
QualityProfileCollection,
RootFolderCollection) {
return Marionette.Layout.extend({
template: 'AddSeries/AddSeriesLayoutTemplate',
regions: {
workspace: '#add-series-workspace'
},
events: {
'click .x-import': '_importSeries',
'click .x-add-new': '_addSeries'
},
attributes: {
id: 'add-series-screen'
},
initialize: function () {
QualityProfileCollection.fetch();
RootFolderCollection.promise = RootFolderCollection.fetch();
},
onShow: function () {
this.workspace.show(new AddSeriesView());
},
_folderSelected: function (options) {
App.vent.trigger(App.Commands.CloseModalCommand);
this.workspace.show(new ExistingSeriesCollectionView({model: options.model}));
},
_importSeries: function () {
this.rootFolderLayout = new RootFolderLayout();
this.rootFolderLayout.on('folderSelected', this._folderSelected, this);
App.modalRegion.show(this.rootFolderLayout);
},
_addSeries: function () {
this.workspace.show(new AddSeriesView());
}
});
});

View File

@@ -0,0 +1,22 @@
<div class="row operations-row">
<div class="btn-group btn-block">
<div class="btn btn-large add-series-import-btn x-import">
<i class="icon-hdd"/>
Import existing series on disk
</div>
<button class="btn btn-large btn-icon-only dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="add-new x-add-new">
Add new series
</li>
</ul>
</div>
<!--<div class="btn btn-block btn-large add-series-import-btn x-import">-->
<!--<i class="icon-hdd"/>-->
<!--Import existing series on disk-->
<!--</div>-->
</div>
<div id="add-series-workspace"/>

View File

@@ -0,0 +1,139 @@
'use strict';
define(
[
'app',
'marionette',
'AddSeries/AddSeriesCollection',
'AddSeries/SearchResultCollectionView',
'AddSeries/NotFoundView',
'Shared/LoadingView',
'underscore'
], function (App, Marionette, AddSeriesCollection, SearchResultCollectionView, NotFoundView, LoadingView, _) {
return Marionette.Layout.extend({
template: 'AddSeries/AddSeriesViewTemplate',
regions: {
searchResult: '#search-result'
},
ui: {
seriesSearch: '.x-series-search',
searchBar : '.x-search-bar',
loadMore : '.x-load-more'
},
events: {
'click .x-load-more': '_onLoadMore'
},
initialize: function (options) {
this.isExisting = options.isExisting;
this.collection = new AddSeriesCollection();
if (this.isExisting) {
this.collection.unmappedFolderModel = this.model;
}
if (this.isExisting) {
this.className = 'existing-series';
}
else {
this.className = 'new-series';
}
this.listenTo(App.vent, App.Events.SeriesAdded, this._onSeriesAdded);
this.listenTo(this.collection, 'sync', this._showResults);
this.resultCollectionView = new SearchResultCollectionView({
collection: this.collection,
isExisting: this.isExisting
});
this.throttledSearch = _.debounce(this.search, 1000, {trailing: true}).bind(this);
},
onRender: function () {
var self = this;
this.$el.addClass(this.className);
this.ui.seriesSearch.keyup(function () {
self.searchResult.close();
self._abortExistingSearch();
self.throttledSearch({
term: self.ui.seriesSearch.val()
});
});
if (this.isExisting) {
this.ui.searchBar.hide();
}
},
onShow: function () {
this.searchResult.show(this.resultCollectionView);
this.ui.seriesSearch.focus();
},
search: function (options) {
this.collection.reset();
if (!options.term || options.term === this.collection.term) {
return $.Deferred().resolve();
}
this.searchResult.show(new LoadingView());
this.collection.term = options.term;
this.currentSearchPromise = this.collection.fetch({
data: { term: options.term }
});
return this.currentSearchPromise;
},
_onSeriesAdded: function (options) {
if (this.isExisting && options.series.get('path') === this.model.get('folder').path) {
this.close();
}
else if (!this.isExisting) {
this.collection.reset();
this.searchResult.close();
this.ui.seriesSearch.val('');
this.ui.seriesSearch.focus();
}
},
_onLoadMore: function () {
var showingAll = this.resultCollectionView.showMore();
this.ui.searchBar.show();
if (showingAll) {
this.ui.loadMore.hide();
}
},
_showResults: function () {
if (!this.isClosed) {
if (this.collection.length === 0) {
this.searchResult.show(new NotFoundView({term: this.collection.term}));
}
else {
this.searchResult.show(this.resultCollectionView);
if (!this.showingAll && this.isExisting) {
this.ui.loadMore.show();
}
}
}
},
_abortExistingSearch: function () {
if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) {
console.log('aborting previous pending search request.');
this.currentSearchPromise.abort();
}
}
});
});

View File

@@ -0,0 +1,23 @@
{{#if folder.path}}
<div class="row 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">
<i class="add-on icon-search"/>
{{#if folder}}
<input type="text" class="input-block-level x-series-search" value="{{folder.name}}">
{{else}}
<input type="text" class="input-block-level x-series-search" placeholder="Start typing the name of series you want to add ...">
{{/if}}
</div>
</div>
<div class="row">
<div id="search-result" class="result-list span12"/>
</div>
<div class="btn btn-block text-center new-series-loadmore x-load-more" style="display: none;">
<i class="icon-angle-down"/>
more
</div>{{debug}}

View File

@@ -0,0 +1,45 @@
'use strict';
define(
[
'marionette',
'AddSeries/AddSeriesView',
'AddSeries/Existing/UnmappedFolderCollection'
], function (Marionette, AddSeriesView, UnmappedFolderCollection) {
return Marionette.CollectionView.extend({
itemView: AddSeriesView,
initialize: function () {
this.collection = new UnmappedFolderCollection();
this.collection.importItems(this.model);
},
showCollection: function () {
this._showAndSearch(0);
},
_showAndSearch: function (index) {
var self = this;
var model = this.collection.at(index);
if (model) {
var currentIndex = index;
var folderName = model.get('folder').name;
this.addItemView(model, this.getItemView(), index);
this.children.findByModel(model)
.search({term: folderName})
.always(function () {
if (!self.isClosed) {
self._showAndSearch(currentIndex + 1);
}
});
}
},
itemViewOptions: {
isExisting: true
}
});
});

View File

@@ -0,0 +1,23 @@
'use strict';
define(
[
'backbone',
'AddSeries/Existing/UnmappedFolderModel'
], function (Backbone, UnmappedFolderModel) {
return Backbone.Collection.extend({
model: UnmappedFolderModel,
importItems: function (rootFolderModel) {
this.reset();
var rootFolder = rootFolderModel;
_.each(rootFolderModel.get('unmappedFolders'), function (folder) {
this.push(new UnmappedFolderModel({
rootFolder: rootFolder,
folder : folder
}));
}, this);
}
});
});

View File

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

View File

@@ -0,0 +1,8 @@
<div class="text-center span12">
<h3>
Sorry. We couldn't find any series matching '{{term}}'
</h3>
<a href="https://github.com/NzbDrone/NzbDrone/wiki/FAQ#why-cant-i-add-a-new-show-to-nzbdrone-its-on-thetvdb">Why can't I find my show?</a>
</div>
{{debug}}

View File

@@ -0,0 +1,20 @@
'use strict';
define(
[
'marionette'
], function (Marionette) {
return Marionette.CompositeView.extend({
template: 'AddSeries/NotFoundTemplate',
initialize: function (options) {
this.options = options;
},
templateHelpers: function () {
return this.options;
}
});
});

View File

@@ -0,0 +1,17 @@
'use strict';
define(
[
'backbone',
'AddSeries/RootFolders/Model',
'Mixins/backbone.signalr.mixin'
], function (Backbone, RootFolderModel) {
var RootFolderCollection = Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/rootfolder',
model: RootFolderModel
});
//var collection = new RootFolderCollection().bindSignalR();
return new RootFolderCollection();
});

View File

@@ -0,0 +1,16 @@
'use strict';
define(
[
'marionette',
'AddSeries/RootFolders/ItemView'
], function (Marionette, RootFolderItemView) {
return Marionette.CollectionView.extend({
itemView: RootFolderItemView,
tagName : 'table',
className: 'table table-hover'
});
});

View File

@@ -0,0 +1,37 @@
'use strict';
define(
[
'marionette'
], function (Marionette) {
return Marionette.ItemView.extend({
template: 'AddSeries/RootFolders/ItemViewTemplate',
tagName : 'tr',
initialize: function () {
this.listenTo(this.model, 'change', this.render);
},
events: {
'click .x-delete': 'removeFolder',
'click .x-folder': 'folderSelected'
},
removeFolder: function () {
var self = this;
this.model.destroy()
.success(function(){
self.close();
});
},
folderSelected: function () {
this.trigger('folderSelected', this.model);
}
});
});

View File

@@ -0,0 +1,10 @@
<td class="span10 x-folder folder-path">
{{path}}
</td>
<td class="span3 x-folder folder-free-space">
<span>{{Bytes freeSpace}}</span>
</td>
<td class="span1 nz-row-action">
<div class="btn btn-small btn-icon-only icon-nd-delete x-delete">
</div>
</td>

View File

@@ -0,0 +1,71 @@
'use strict';
define(
[
'marionette',
'AddSeries/RootFolders/CollectionView',
'AddSeries/RootFolders/Collection',
'AddSeries/RootFolders/Model',
'Shared/LoadingView',
'Mixins/AsValidatedView',
'Mixins/AutoComplete'
], function (Marionette, RootFolderCollectionView, RootFolderCollection, RootFolderModel, LoadingView, AsValidatedView) {
var layout = Marionette.Layout.extend({
template: 'AddSeries/RootFolders/LayoutTemplate',
ui: {
pathInput: '.x-path input'
},
regions: {
currentDirs: '#current-dirs'
},
events: {
'click .x-add': '_addFolder'
},
initialize: function () {
this.collection = RootFolderCollection;
this.rootfolderListView = new RootFolderCollectionView({ collection: RootFolderCollection });
this.listenTo(this.rootfolderListView, 'itemview:folderSelected', this._onFolderSelected);
},
onRender: function () {
var self = this;
this.currentDirs.show(new LoadingView());
RootFolderCollection.promise.done(function () {
self.currentDirs.show(self.rootfolderListView);
});
this.ui.pathInput.autoComplete('/directories');
},
_onFolderSelected: function (options) {
this.trigger('folderSelected', options);
},
_addFolder: function () {
var self = this;
var newDir = new RootFolderModel({
Path: this.ui.pathInput.val()
});
this.bindToModelValidation(newDir);
newDir.save().done(function () {
RootFolderCollection.add(newDir);
self.trigger('folderSelected', {model: newDir});
});
}
});
return AsValidatedView.apply(layout);
});

View File

@@ -0,0 +1,21 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Select Folder</h3>
</div>
<div class="modal-body root-folders-modal">
<div class="validation-errors"></div>
<div class="input-prepend input-append x-path control-group">
<span class="add-on">&nbsp;<i class="icon-folder-open"></i></span>
<input class="span9" type="text" validation-name="path" placeholder="Enter path to folder that contains your shows">
<button class="btn btn-success x-add">
<i class="icon-ok"/>
</button>
</div>
{{#if items}}
<h4>Recent Folders</h4>
{{/if}}
<div id="current-dirs" class="root-folders-list"></div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">close</button>
</div>

View File

@@ -0,0 +1,12 @@
'use strict';
define(
[
'backbone'
], function (Backbone) {
return Backbone.Model.extend({
urlRoot : window.NzbDrone.ApiRoot + '/rootfolder',
defaults: {
freeSpace: 0
}
});
});

View File

@@ -0,0 +1,12 @@
<div>
<select class="span4 x-root-folder" validation-name="RootFolderPath">
{{#if this}}
{{#each this}}
<option value="{{id}}">{{path}}</option>
{{/each}}
{{else}}
<option value="">Select Path</option>
{{/if}}
<option value="addNew">Add a different path</option>
</select>
</div>

View File

@@ -0,0 +1,9 @@
<select class="span2 x-starting-season">
{{#each this}}
{{#if_eq seasonNumber compare="0"}}
<option value="{{seasonNumber}}">Specials</option>
{{else}}
<option value="{{seasonNumber}}">Season {{seasonNumber}}</option>
{{/if_eq}}
{{/each}}
</select>

View File

@@ -0,0 +1,35 @@
'use strict';
define(
[
'marionette',
'AddSeries/SearchResultView'
], function (Marionette, SearchResultView) {
return Marionette.CollectionView.extend({
itemView: SearchResultView,
initialize: function (options) {
this.isExisting = options.isExisting;
this.showing = 1;
},
showAll: function () {
this.showingAll = true;
this.render();
},
showMore: function () {
this.showing += 5;
this.render();
return this.showing >= this.collection.length;
},
appendHtml: function (collectionView, itemView, index) {
if (!this.isExisting || index < this.showing || index === 0) {
collectionView.$el.append(itemView.el);
}
}
});
});

View File

@@ -0,0 +1,161 @@
'use strict';
define(
[
'app',
'underscore',
'marionette',
'Quality/QualityProfileCollection',
'AddSeries/RootFolders/Collection',
'AddSeries/RootFolders/Layout',
'Series/SeriesCollection',
'Config',
'Shared/Messenger',
'Mixins/AsValidatedView',
'jquery.dotdotdot'
], function (App, _, Marionette, QualityProfiles, RootFolders, RootFolderLayout, SeriesCollection, Config, Messenger, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'AddSeries/SearchResultViewTemplate',
ui: {
qualityProfile: '.x-quality-profile',
rootFolder : '.x-root-folder',
addButton : '.x-add',
overview : '.x-overview',
startingSeason: '.x-starting-season'
},
events: {
'click .x-add' : '_addSeries',
'change .x-quality-profile': '_qualityProfileChanged',
'change .x-root-folder' : '_rootFolderChanged'
},
initialize: function () {
if (!this.model) {
throw 'model is required';
}
this.templateHelpers = {};
this._configureTemplateHelpers();
this.listenTo(App.vent, Config.Events.ConfigUpdatedEvent, this._onConfigUpdated);
this.listenTo(this.model, 'change', this.render);
this.listenTo(RootFolders, 'all', this.render);
this.rootFolderLayout = new RootFolderLayout();
this.listenTo(this.rootFolderLayout, 'folderSelected', this._setRootFolder);
},
onRender: function () {
var defaultQuality = Config.getValue(Config.Keys.DefaultQualityProfileId);
var defaultRoot = Config.getValue(Config.Keys.DefaultRootFolderId);
if (QualityProfiles.get(defaultQuality)) {
this.ui.qualityProfile.val(defaultQuality);
}
if (RootFolders.get(defaultRoot)) {
this.ui.rootFolder.val(defaultRoot);
}
var minSeasonNotZero = _.min(_.reject(this.model.get('seasons'), { seasonNumber: 0 }), 'seasonNumber');
if (minSeasonNotZero) {
this.ui.startingSeason.val(minSeasonNotZero.seasonNumber);
}
//TODO: make this work via onRender, FM?
//works with onShow, but stops working after the first render
this.ui.overview.dotdotdot({
height: 120
});
},
_configureTemplateHelpers: function () {
var existingSeries = SeriesCollection.where({tvdbId: this.model.get('tvdbId')});
if (existingSeries.length > 0) {
this.templateHelpers.existing = existingSeries[0].toJSON();
}
this.templateHelpers.qualityProfiles = QualityProfiles.toJSON();
if (!this.model.get('isExisting')) {
this.templateHelpers.rootFolders = RootFolders.toJSON();
}
},
_onConfigUpdated: function (options) {
if (options.key === Config.Keys.DefaultQualityProfileId) {
this.ui.qualityProfile.val(options.value);
}
else if (options.key === Config.Keys.DefaultRootFolderId) {
this.ui.rootFolder.val(options.value);
}
},
_qualityProfileChanged: function () {
Config.setValue(Config.Keys.DefaultQualityProfileId, this.ui.qualityProfile.val());
},
_rootFolderChanged: function () {
var rootFolderValue = this.ui.rootFolder.val();
if (rootFolderValue === 'addNew') {
App.modalRegion.show(this.rootFolderLayout);
}
else {
Config.setValue(Config.Keys.DefaultRootFolderId, rootFolderValue);
}
},
_setRootFolder: function (options) {
App.vent.trigger(App.Commands.CloseModalCommand);
this.ui.rootFolder.val(options.model.id);
this._rootFolderChanged();
},
_addSeries: function () {
var icon = this.ui.addButton.find('icon');
icon.removeClass('icon-plus').addClass('icon-spin icon-spinner disabled');
var quality = this.ui.qualityProfile.val();
var rootFolderPath = this.ui.rootFolder.children(':selected').text();
var startingSeason = this.ui.startingSeason.val();
this.model.set('qualityProfileId', quality);
this.model.set('rootFolderPath', rootFolderPath);
this.model.setSeasonPass(startingSeason);
var self = this;
SeriesCollection.add(this.model);
var promise = this.model.save();
promise.done(function () {
self.close();
icon.removeClass('icon-spin icon-spinner disabled').addClass('icon-search');
Messenger.show({
message: 'Added: ' + self.model.get('title')
});
App.vent.trigger(App.Events.SeriesAdded, { series: self.model });
});
promise.fail(function () {
icon.removeClass('icon-spin icon-spinner disabled').addClass('icon-search');
});
}
});
AsValidatedView.apply(view);
return view;
});

View File

@@ -0,0 +1,38 @@
<div class="search-item {{#unless isExisting}}search-item-new{{/unless}}">
<div class="row">
<div class="span2">
<a href="{{traktUrl}}" target="_blank">
<img class="new-series-poster" src="{{remotePoster}}"
{{defaultImg}} >
</a>
</div>
<div class="span9">
<div class="row">
<h2>{{titleWithYear}}</h2>
</div>
<div class="row new-series-overview x-overview">
{{overview}}
</div>
<div class="row">
<form class="form-inline">
{{#if existing}}
<div class="btn add-series disabled pull-right">
Already Exists
</div>
{{else}}
{{#unless path}}
{{> RootFolderSelectionPartial rootFolders}}
{{/unless}}
{{> StartingSeasonSelectionPartial seasons}}
{{> QualityProfileSelectionPartial qualityProfiles}}
<span class="btn btn-success x-add add-series pull-right"> Add
<i class="icon-plus"></i>
</span>
{{/if}}
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,125 @@
@import "../Shared/Styles/card.less";
@import "../Shared/Styles/clickable.less";
#add-series-screen {
.operations-row {
margin-left : 0px;
}
.existing-series {
.card();
margin : 30px 0px;
.add-series-search {
width : 970px;
}
.unmapped-folder-path {
padding: 20px;
margin-left : 0px;
font-weight : 100;
font-size : 25px;
text-align : center;
}
.new-series-loadmore {
font-size : 30px;
font-weight : 300;
padding-top : 10px;
padding-bottom : 10px;
}
}
.new-series {
.search-item {
.card();
margin : 40px 0px;
}
}
.add-series-search {
margin-top : 20px;
margin-bottom : 20px;
padding-left : 20px;
*[class*='icon-'] {
font-size : 28px;
height : 30px;
width : 40px;
padding-top : 14px;
}
input {
height : 50px;
}
}
.search-item {
.new-series-overview {
overflow : hidden;
height : 120px;
}
.new-series-poster {
min-width : 138px;
min-height : 203px;
max-width : 138px;
max-height : 203px;
margin : 10px;
}
padding-bottom : 20px;
a {
color : #343434;
}
a:hover {
text-decoration : none;
}
select {
font-size : 16px;
}
.add-series {
margin-left : 20px;
}
}
}
li.add-new {
.clickable;
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 20px;
color: rgb(51, 51, 51);
white-space: nowrap;
}
li.add-new:hover {
text-decoration: none;
color: rgb(255, 255, 255);
background-color: rgb(0, 129, 194);
}
.btn-group {
.dropdown-menu {
right: 0px;
}
.add-series-import-btn {
width: 93.3%;
}
}
.root-folders-modal {
overflow: visible;
.root-folders-list {
overflow: auto;
max-height: 300px;
}
}