diff --git a/Gruntfile.js b/Gruntfile.js
index ab50e35..c0bccc8 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -8,9 +8,41 @@ module.exports = function(grunt){
var config = {
tmp: '.tmp',
base: 'couchpotato',
- css_dest: 'couchpotato/static/style/combined.min.css'
+ css_dest: 'couchpotato/static/style/combined.min.css',
+ scripts_vendor_dest: 'couchpotato/static/scripts/combined.vendor.min.js',
+ scripts_base_dest: 'couchpotato/static/scripts/combined.base.min.js',
+ scripts_plugins_dest: 'couchpotato/static/scripts/combined.plugins.min.js'
};
+ var vendor_scripts_files = [
+ 'couchpotato/static/scripts/vendor/mootools.js',
+ 'couchpotato/static/scripts/vendor/mootools_more.js',
+ 'couchpotato/static/scripts/vendor/form_replacement/form_check.js',
+ 'couchpotato/static/scripts/vendor/form_replacement/form_radio.js',
+ 'couchpotato/static/scripts/vendor/form_replacement/form_dropdown.js',
+ 'couchpotato/static/scripts/vendor/form_replacement/form_selectoption.js',
+ 'couchpotato/static/scripts/vendor/Array.stableSort.js',
+ 'couchpotato/static/scripts/vendor/history.js',
+ 'couchpotato/static/scripts/vendor/dynamics.js'
+ ];
+
+ var scripts_files = [
+ 'couchpotato/static/scripts/library/uniform.js',
+ 'couchpotato/static/scripts/library/question.js',
+ 'couchpotato/static/scripts/library/scrollspy.js',
+ 'couchpotato/static/scripts/couchpotato.js',
+ 'couchpotato/static/scripts/api.js',
+ 'couchpotato/static/scripts/page.js',
+ 'couchpotato/static/scripts/block.js',
+ 'couchpotato/static/scripts/block/navigation.js',
+ 'couchpotato/static/scripts/block/header.js',
+ 'couchpotato/static/scripts/block/footer.js',
+ 'couchpotato/static/scripts/block/menu.js',
+ 'couchpotato/static/scripts/page/home.js',
+ 'couchpotato/static/scripts/page/settings.js',
+ 'couchpotato/static/scripts/page/about.js'
+ ];
+
grunt.initConfig({
// Project settings
@@ -26,7 +58,8 @@ module.exports = function(grunt){
},
all: [
'<%= config.base %>/{,**/}*.js',
- '!<%= config.base %>/static/scripts/vendor/{,**/}*.js'
+ '!<%= config.base %>/static/scripts/vendor/{,**/}*.js',
+ '!<%= config.base %>/static/scripts/combined.*.js'
]
},
@@ -76,6 +109,30 @@ module.exports = function(grunt){
}
},
+ uglify: {
+ options: {
+ mangle: false,
+ compress: false,
+ beautify: true,
+ screwIE8: true
+ },
+ vendor: {
+ files: {
+ '<%= config.scripts_vendor_dest %>': vendor_scripts_files
+ }
+ },
+ base: {
+ files: {
+ '<%= config.scripts_base_dest %>': scripts_files
+ }
+ },
+ plugins: {
+ files: {
+ '<%= config.scripts_plugins_dest %>': ['<%= config.base %>/core/**/*.js']
+ }
+ }
+ },
+
shell: {
runCouchPotato: {
command: 'python CouchPotato.py',
@@ -91,16 +148,20 @@ module.exports = function(grunt){
},
js: {
files: [
- '<%= config.base %>/**/*.js'
+ '<%= config.base %>/**/*.js',
+ '!<%= config.base %>/static/scripts/combined.*.js'
],
- tasks: ['jshint']
+ tasks: ['uglify:base', 'uglify:plugins', 'jshint']
},
livereload: {
options: {
livereload: 35729
},
files: [
- '<%= config.css_dest %>'
+ '<%= config.css_dest %>',
+ '<%= config.scripts_vendor_dest %>',
+ '<%= config.scripts_base_dest %>',
+ '<%= config.scripts_plugins_dest %>'
]
}
},
@@ -114,6 +175,6 @@ module.exports = function(grunt){
});
- grunt.registerTask('default', ['sass:server', 'autoprefixer', 'cssmin', 'concurrent']);
+ grunt.registerTask('default', ['sass:server', 'autoprefixer', 'cssmin', 'uglify:vendor', 'uglify:base', 'uglify:plugins', 'concurrent']);
};
diff --git a/couchpotato/core/_base/clientscript.py b/couchpotato/core/_base/clientscript.py
index fd3b616..69f35f3 100644
--- a/couchpotato/core/_base/clientscript.py
+++ b/couchpotato/core/_base/clientscript.py
@@ -22,29 +22,9 @@ class ClientScript(Plugin):
'style/combined.min.css',
],
'script': [
- 'scripts/vendor/mootools.js',
- 'scripts/vendor/mootools_more.js',
- 'scripts/vendor/form_replacement/form_check.js',
- 'scripts/vendor/form_replacement/form_radio.js',
- 'scripts/vendor/form_replacement/form_dropdown.js',
- 'scripts/vendor/form_replacement/form_selectoption.js',
- 'scripts/vendor/Array.stableSort.js',
- 'scripts/vendor/history.js',
- 'scripts/vendor/dynamics.js',
- 'scripts/library/uniform.js',
- 'scripts/library/question.js',
- 'scripts/library/scrollspy.js',
- 'scripts/couchpotato.js',
- 'scripts/api.js',
- 'scripts/page.js',
- 'scripts/block.js',
- 'scripts/block/navigation.js',
- 'scripts/block/header.js',
- 'scripts/block/footer.js',
- 'scripts/block/menu.js',
- 'scripts/page/home.js',
- 'scripts/page/settings.js',
- 'scripts/page/about.js',
+ 'scripts/combined.vendor.min.js',
+ 'scripts/combined.base.min.js',
+ 'scripts/combined.plugins.min.js',
],
}
diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js
index 2f747b7..265d682 100644
--- a/couchpotato/core/media/movie/_base/static/movie.actions.js
+++ b/couchpotato/core/media/movie/_base/static/movie.actions.js
@@ -931,11 +931,11 @@ MA.Files = new Class({
getDetails: function(){
var self = this;
- if(!self.movie.data.releases || self.movie.data.releases.length == 0)
+ if(!self.movie.data.releases || self.movie.data.releases.length === 0)
return;
if(!self.files_container){
- self.files_container = new Element('div.files.table')
+ self.files_container = new Element('div.files.table');
// Header
new Element('div.item.head').adopt(
diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py
index 57a31e2..2a60794 100644
--- a/couchpotato/core/plugins/base.py
+++ b/couchpotato/core/plugins/base.py
@@ -32,7 +32,7 @@ class Plugin(object):
plugin_path = None
enabled_option = 'enabled'
- auto_register_static = True
+ auto_register_static = False
_needs_shutdown = False
_running = None
diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js
new file mode 100644
index 0000000..790a6ce
--- /dev/null
+++ b/couchpotato/static/scripts/combined.base.min.js
@@ -0,0 +1,2259 @@
+var Uniform = new Class({
+ Implements: [ Options ],
+ options: {
+ focusedClass: "focused",
+ holderClass: "ctrlHolder"
+ },
+ initialize: function(options) {
+ this.setOptions(options);
+ var focused = this.options.focusedClass;
+ var holder = "." + this.options.holderClass;
+ $(document.body).addEvents({
+ "focus:relay(input, select, textarea)": function() {
+ var parent = this.getParent(holder);
+ if (parent) parent.addClass(focused);
+ },
+ "blur:relay(input, select, textarea)": function() {
+ var parent = this.getParent(holder);
+ if (parent) parent.removeClass(focused);
+ }
+ });
+ }
+});
+
+var Question = new Class({
+ initialize: function(question, hint, answers) {
+ var self = this;
+ self.question = question;
+ self.hint = hint;
+ self.answers = answers;
+ self.createQuestion();
+ self.answers.each(function(answer) {
+ self.createAnswer(answer);
+ });
+ },
+ createQuestion: function() {
+ var self = this, h3, hint;
+ self.container = new Element("div.mask.question").grab(self.inner = new Element("div.inner").adopt(h3 = new Element("h3", {
+ html: this.question
+ }), hint = this.hint ? new Element("div.hint", {
+ html: this.hint
+ }) : null)).inject(document.body);
+ setTimeout(function() {
+ self.container.addClass("show");
+ self.inner.getElements("> *").each(function(el, nr) {
+ dynamics.css(el, {
+ opacity: 0,
+ translateY: 50
+ });
+ dynamics.animate(el, {
+ opacity: 1,
+ translateY: 0
+ }, {
+ type: dynamics.spring,
+ frequency: 200,
+ friction: 300,
+ duration: 800,
+ delay: 400 + nr * 100
+ });
+ });
+ }, 10);
+ },
+ createAnswer: function(options) {
+ var self = this;
+ var answer = new Element("a", Object.merge(options, {
+ class: "answer button " + (options["class"] || "") + (options.cancel ? " cancel" : "")
+ })).inject(this.inner);
+ if (options.cancel) {
+ answer.addEvent("click", self.close.bind(self));
+ } else if (options.request) {
+ answer.addEvent("click", function(e) {
+ e.stop();
+ new Request(Object.merge(options, {
+ url: options.href,
+ onComplete: function() {
+ (options.onComplete || function() {})();
+ self.close();
+ }
+ })).send();
+ });
+ }
+ },
+ close: function() {
+ var self = this;
+ var ended = function() {
+ self.container.dispose();
+ self.container.removeEventListener("transitionend", ended);
+ };
+ self.container.addEventListener("transitionend", ended, false);
+ self.inner.getElements("> *").reverse().each(function(el, nr) {
+ dynamics.css(el, {
+ opacity: 1,
+ translateY: 0
+ });
+ dynamics.animate(el, {
+ opacity: 0,
+ translateY: 50
+ }, {
+ type: dynamics.spring,
+ frequency: 200,
+ friction: 300,
+ duration: 800,
+ anticipationSize: 175,
+ anticipationStrength: 400,
+ delay: nr * 100
+ });
+ });
+ dynamics.setTimeout(function() {
+ self.container.removeClass("show");
+ }, 200);
+ },
+ toElement: function() {
+ return this.container;
+ }
+});
+
+var ScrollSpy = new Class({
+ Implements: [ Options, Events ],
+ options: {
+ container: window,
+ max: 0,
+ min: 0,
+ mode: "vertical"
+ },
+ initialize: function(options) {
+ this.setOptions(options);
+ this.container = document.id(this.options.container);
+ this.enters = this.leaves = 0;
+ this.inside = false;
+ var self = this;
+ this.listener = function(e) {
+ var position = self.container.getScroll(), xy = position[self.options.mode == "vertical" ? "y" : "x"], min = typeOf(self.options.min) == "function" ? self.options.min() : self.options.min, max = typeOf(self.options.max) == "function" ? self.options.max() : self.options.max;
+ if (xy >= min && (max === 0 || xy <= max)) {
+ if (!self.inside) {
+ self.inside = true;
+ self.enters++;
+ self.fireEvent("enter", [ position, self.enters, e ]);
+ }
+ self.fireEvent("tick", [ position, self.inside, self.enters, self.leaves, e ]);
+ } else if (self.inside) {
+ self.inside = false;
+ self.leaves++;
+ self.fireEvent("leave", [ position, self.leaves, e ]);
+ }
+ self.fireEvent("scroll", [ position, self.inside, self.enters, self.leaves, e ]);
+ };
+ this.addListener();
+ },
+ start: function() {
+ this.container.addEvent("scroll", this.listener);
+ },
+ stop: function() {
+ this.container.removeEvent("scroll", this.listener);
+ },
+ addListener: function() {
+ this.start();
+ }
+});
+
+var CouchPotato = new Class({
+ Implements: [ Events, Options ],
+ defaults: {
+ page: "home",
+ action: "index",
+ params: {}
+ },
+ pages: [],
+ block: [],
+ initialize: function() {
+ var self = this;
+ self.global_events = {};
+ },
+ setup: function(options) {
+ var self = this;
+ self.setOptions(options);
+ self.c = $(document.body);
+ self.createLayout();
+ self.createPages();
+ if (window.location.hash) History.handleInitialState(); else self.openPage(window.location.pathname);
+ History.addEvent("change", self.openPage.bind(self));
+ self.c.addEvent("click:relay(.header a, .navigation a, .movie_details a, .list_list .movie)", self.ripple.bind(self));
+ self.c.addEvent("click:relay(a[href^=/]:not([target]))", self.pushState.bind(self));
+ self.c.addEvent("click:relay(a[href^=http])", self.openDerefered.bind(self));
+ self.touch_device = "ontouchstart" in window || navigator.msMaxTouchPoints;
+ if (self.touch_device) self.c.addClass("touch_enabled");
+ window.addEvent("resize", self.resize.bind(self));
+ self.resize();
+ },
+ resize: function() {
+ var self = this;
+ self.mobile_screen = Math.max(document.documentElement.clientWidth, window.innerWidth || 0) <= 480;
+ self.c[self.mobile_screen ? "addClass" : "removeClass"]("mobile");
+ },
+ ripple: function(e, el) {
+ var self = this, button = el.getCoordinates(), x = e.page.x - button.left, y = e.page.y - button.top, ripple = new Element("div.ripple", {
+ styles: {
+ left: x,
+ top: y
+ }
+ });
+ ripple.inject(el);
+ setTimeout(function() {
+ ripple.addClass("animate");
+ }, 0);
+ setTimeout(function() {
+ ripple.dispose();
+ }, 2100);
+ },
+ getOption: function(name) {
+ try {
+ return this.options[name];
+ } catch (e) {
+ return null;
+ }
+ },
+ pushState: function(e) {
+ var self = this;
+ if (!e.meta && self.isMac() || !e.control && !self.isMac()) {
+ e.preventDefault();
+ var url = e.target.get("href");
+ if (e.event && e.event.button == 1) window.open(url); else if (History.getPath() != url) History.push(url);
+ }
+ },
+ isMac: function() {
+ return Browser.platform == "mac";
+ },
+ createLayout: function() {
+ var self = this;
+ self.block.header = new BlockBase();
+ self.c.adopt($(self.block.header).addClass("header").adopt(self.block.navigation = new BlockHeader(self, {}), self.block.search = new BlockSearch(self, {}), self.block.more = new BlockMenu(self, {
+ button_class: "icon-settings"
+ })), new Element("div.corner_background"), self.content = new Element("div.content").adopt(self.pages_container = new Element("div.pages"), self.block.footer = new BlockFooter(self, {})));
+ var setting_links = [ new Element("a", {
+ text: "About CouchPotato",
+ href: App.createUrl("settings/about")
+ }), new Element("a", {
+ text: "Check for Updates",
+ events: {
+ click: self.checkForUpdate.bind(self, null)
+ }
+ }), new Element("a", {
+ text: "Settings",
+ href: App.createUrl("settings/general")
+ }), new Element("a", {
+ text: "Logs",
+ href: App.createUrl("log")
+ }), new Element("a", {
+ text: "Restart",
+ events: {
+ click: self.restartQA.bind(self)
+ }
+ }), new Element("a", {
+ text: "Shutdown",
+ events: {
+ click: self.shutdownQA.bind(self)
+ }
+ }) ];
+ setting_links.each(function(a) {
+ self.block.more.addLink(a);
+ });
+ new ScrollSpy({
+ min: 10,
+ onLeave: function() {
+ $(self.block.header).removeClass("with_shadow");
+ },
+ onEnter: function() {
+ $(self.block.header).addClass("with_shadow");
+ }
+ });
+ },
+ createPages: function() {
+ var self = this;
+ var pages = [];
+ Object.each(Page, function(page_class, class_name) {
+ var pg = new Page[class_name](self, {
+ level: 1
+ });
+ self.pages[class_name] = pg;
+ pages.include({
+ order: pg.order,
+ name: class_name,
+ class: pg
+ });
+ });
+ pages.stableSort(self.sortPageByOrder).each(function(page) {
+ page["class"].load();
+ self.fireEvent("load" + page.name);
+ $(page["class"]).inject(self.getPageContainer());
+ });
+ self.fireEvent("load");
+ },
+ sortPageByOrder: function(a, b) {
+ return (a.order || 100) - (b.order || 100);
+ },
+ openPage: function(url) {
+ var self = this, route = new Route(self.defaults);
+ route.parse(rep(History.getPath()));
+ var page_name = route.getPage().capitalize(), action = route.getAction(), params = route.getParams(), current_url = route.getCurrentUrl(), page;
+ if (current_url == self.current_url) return;
+ if (self.current_page) self.current_page.hide();
+ try {
+ page = self.pages[page_name] || self.pages.Home;
+ page.open(action, params, current_url);
+ page.show();
+ } catch (e) {
+ console.error("Can't open page:" + url, e);
+ }
+ self.current_page = page;
+ self.current_url = current_url;
+ },
+ getBlock: function(block_name) {
+ return this.block[block_name];
+ },
+ getPage: function(name) {
+ return this.pages[name];
+ },
+ getPageContainer: function() {
+ return this.pages_container;
+ },
+ shutdown: function() {
+ var self = this;
+ self.blockPage("You have shutdown. This is what is supposed to happen ;)");
+ Api.request("app.shutdown", {
+ onComplete: self.blockPage.bind(self)
+ });
+ self.checkAvailable(1e3);
+ },
+ shutdownQA: function() {
+ var self = this;
+ var q = new Question("Are you sure you want to shutdown CouchPotato?", "", [ {
+ text: "Shutdown",
+ class: "shutdown red",
+ events: {
+ click: function(e) {
+ e.preventDefault();
+ self.shutdown();
+ q.close.delay(100, q);
+ }
+ }
+ }, {
+ text: "No, nevah!",
+ cancel: true
+ } ]);
+ },
+ restart: function(message, title) {
+ var self = this;
+ self.blockPage(message || "Restarting... please wait. If this takes too long, something must have gone wrong.", title);
+ Api.request("app.restart");
+ self.checkAvailable(1e3);
+ },
+ restartQA: function(e, message, title) {
+ var self = this;
+ var q = new Question("Are you sure you want to restart CouchPotato?", "", [ {
+ text: "Restart",
+ class: "restart orange",
+ events: {
+ click: function(e) {
+ e.preventDefault();
+ self.restart(message, title);
+ q.close.delay(100, q);
+ }
+ }
+ }, {
+ text: "No, nevah!",
+ cancel: true
+ } ]);
+ },
+ checkForUpdate: function(onComplete) {
+ var self = this;
+ Updater.check(onComplete);
+ self.blockPage("Please wait. If this takes too long, something must have gone wrong.", "Checking for updates");
+ self.checkAvailable(3e3);
+ },
+ checkAvailable: function(delay, onAvailable) {
+ var self = this;
+ (function() {
+ var onFailure = function() {
+ self.checkAvailable.delay(1e3, self, [ delay, onAvailable ]);
+ self.fireEvent("unload");
+ };
+ var request = Api.request("app.available", {
+ timeout: 2e3,
+ onTimeout: function() {
+ request.cancel();
+ onFailure();
+ },
+ onFailure: onFailure,
+ onSuccess: function() {
+ if (onAvailable) onAvailable();
+ self.unBlockPage();
+ self.fireEvent("reload");
+ }
+ });
+ }).delay(delay || 0);
+ },
+ blockPage: function(message, title) {
+ var self = this;
+ self.unBlockPage();
+ self.mask = new Element("div.mask.with_message").adopt(new Element("div.message").adopt(new Element("h1", {
+ text: title || "Unavailable"
+ }), new Element("div", {
+ text: message || "Something must have crashed.. check the logs ;)"
+ }))).inject(document.body);
+ createSpinner(self.mask);
+ setTimeout(function() {
+ self.mask.addClass("show");
+ }, 10);
+ },
+ unBlockPage: function() {
+ var self = this;
+ if (self.mask) self.mask.get("tween").start("opacity", 0).chain(function() {
+ this.element.destroy();
+ });
+ },
+ createUrl: function(action, params) {
+ return this.options.base_url + (action ? action + "/" : "") + (params ? "?" + Object.toQueryString(params) : "");
+ },
+ openDerefered: function(e, el) {
+ var self = this;
+ e.stop();
+ var url = "http://www.dereferer.org/?" + el.get("href");
+ if (el.get("target") == "_blank" || e.meta && self.isMac() || e.control && !self.isMac()) window.open(url); else window.location = url;
+ },
+ createUserscriptButtons: function() {
+ var host_url = window.location.protocol + "//" + window.location.host;
+ return new Element("div.group_userscript").adopt(new Element("a.userscript.button", {
+ text: "Install extension",
+ href: "https://couchpota.to/extension/",
+ target: "_blank"
+ }), new Element("span.or[text=or]"), new Element("span.bookmarklet").adopt(new Element("a.button.orange", {
+ text: "+CouchPotato",
+ href: "javascript:void((function(){var e=document.createElement('script');e.setAttribute('type','text/javascript');e.setAttribute('charset','UTF-8');e.setAttribute('src','" + host_url + Api.createUrl("userscript.bookmark") + "?host=" + encodeURI(host_url + Api.createUrl("userscript.get") + randomString() + "/") + "&r='+Math.random()*99999999);document.body.appendChild(e)})());",
+ target: "",
+ events: {
+ click: function(e) {
+ e.stop();
+ alert("Drag it to your bookmark ;)");
+ }
+ }
+ }), new Element("span", {
+ text: "⇽ Drag this to your bookmarks"
+ })));
+ },
+ on: function(name, handle) {
+ var self = this;
+ if (!self.global_events[name]) self.global_events[name] = [];
+ self.global_events[name].push(handle);
+ },
+ trigger: function(name, args, on_complete) {
+ var self = this;
+ if (!self.global_events[name]) {
+ return;
+ }
+ if (!on_complete && typeOf(args) == "function") {
+ on_complete = args;
+ args = [];
+ }
+ self.global_events[name].each(function(handle) {
+ setTimeout(function() {
+ var results = handle.apply(handle, args || []);
+ if (on_complete) on_complete(results);
+ }, 0);
+ });
+ },
+ off: function(name, handle) {
+ var self = this;
+ if (!self.global_events[name]) return;
+ if (handle) {
+ self.global_events[name] = self.global_events[name].erase(handle);
+ } else {
+ self.global_events[name] = [];
+ }
+ }
+});
+
+window.App = new CouchPotato();
+
+var Route = new Class({
+ defaults: null,
+ page: "",
+ action: "index",
+ params: {},
+ initialize: function(defaults) {
+ var self = this;
+ self.defaults = defaults || {};
+ },
+ parse: function(path) {
+ var self = this;
+ if (path == "/" && location.hash) {
+ path = rep(location.hash.replace("#", "/"));
+ }
+ self.current = path.replace(/^\/+|\/+$/g, "");
+ var url = self.current.split("/");
+ self.page = url.length > 0 ? url.shift() : self.defaults.page;
+ self.action = url.length > 0 ? url.join("/") : self.defaults.action;
+ self.params = Object.merge({}, self.defaults.params);
+ if (url.length > 1) {
+ var key;
+ url.each(function(el, nr) {
+ if (nr % 2 === 0) key = el; else if (key) {
+ self.params[key] = el;
+ key = null;
+ }
+ });
+ } else if (url.length == 1) {
+ self.params[url] = true;
+ }
+ return self;
+ },
+ getPage: function() {
+ return this.page;
+ },
+ getAction: function() {
+ return this.action;
+ },
+ getParams: function() {
+ return this.params;
+ },
+ getCurrentUrl: function() {
+ return this.current;
+ },
+ get: function(param) {
+ return this.params[param];
+ }
+});
+
+var p = function() {
+ if (typeof console !== "undefined" && console !== null) console.log(arguments);
+};
+
+(function() {
+ var events;
+ var check = function(e) {
+ var target = $(e.target);
+ var parents = target.getParents();
+ events.each(function(item) {
+ var element = item.element;
+ if (element != target && !parents.contains(element)) item.fn.call(element, e);
+ });
+ };
+ Element.Events.outerClick = {
+ onAdd: function(fn) {
+ if (!events) {
+ document.addEvent("click", check);
+ events = [];
+ }
+ events.push({
+ element: this,
+ fn: fn
+ });
+ },
+ onRemove: function(fn) {
+ events = events.filter(function(item) {
+ return item.element != this || item.fn != fn;
+ }, this);
+ if (!events.length) {
+ document.removeEvent("click", check);
+ events = null;
+ }
+ }
+ };
+})();
+
+function randomString(length, extra) {
+ var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz" + (extra ? "-._!@#$%^&*()+=" : ""), string_length = length || 8, random_string = "";
+ for (var i = 0; i < string_length; i++) {
+ var rnum = Math.floor(Math.random() * chars.length);
+ random_string += chars.charAt(rnum);
+ }
+ return random_string;
+}
+
+(function() {
+ var keyPaths = [];
+ var saveKeyPath = function(path) {
+ keyPaths.push({
+ sign: path[0] === "+" || path[0] === "-" ? parseInt(path.shift() + 1) : 1,
+ path: path
+ });
+ };
+ var valueOf = function(object, path) {
+ var ptr = object;
+ path.each(function(key) {
+ ptr = ptr[key];
+ });
+ return ptr;
+ };
+ var comparer = function(a, b) {
+ for (var i = 0, l = keyPaths.length; i < l; i++) {
+ var aVal = valueOf(a, keyPaths[i].path), bVal = valueOf(b, keyPaths[i].path);
+ if (aVal > bVal) return keyPaths[i].sign;
+ if (aVal < bVal) return -keyPaths[i].sign;
+ }
+ return 0;
+ };
+ Array.implement({
+ sortBy: function() {
+ keyPaths.empty();
+ Array.each(arguments, function(argument) {
+ switch (typeOf(argument)) {
+ case "array":
+ saveKeyPath(argument);
+ break;
+
+ case "string":
+ saveKeyPath(argument.match(/[+-]|[^.]+/g));
+ break;
+ }
+ });
+ return this.stableSort(comparer);
+ }
+ });
+})();
+
+var createSpinner = function(container) {
+ var spinner = new Element("div.spinner");
+ container.grab(spinner);
+ return spinner;
+};
+
+var rep = function(pa) {
+ return pa.replace(Api.getOption("url"), "/").replace(App.getOption("base_url"), "/");
+};
+
+var ApiClass = new Class({
+ setup: function(options) {
+ var self = this;
+ self.options = options;
+ },
+ request: function(type, options) {
+ var self = this, r_type = self.options.is_remote ? "JSONP" : "JSON";
+ return new Request[r_type](Object.merge({
+ callbackKey: "callback_func",
+ method: "get",
+ url: self.createUrl(type, {
+ t: randomString()
+ })
+ }, options)).send();
+ },
+ createUrl: function(action, params) {
+ return this.options.url + (action || "default") + "/" + (params ? "?" + Object.toQueryString(params) : "");
+ },
+ getOption: function(name) {
+ return this.options[name];
+ }
+});
+
+window.Api = new ApiClass();
+
+var PageBase = new Class({
+ Implements: [ Options, Events ],
+ options: {},
+ order: 1,
+ has_tab: true,
+ name: "",
+ icon: null,
+ parent_page: null,
+ sub_pages: null,
+ initialize: function(parent_page, options) {
+ var self = this;
+ self.parent_page = parent_page;
+ self.setOptions(options);
+ self.el = new Element("div", {
+ class: "page " + self.getPageClass() + (" level_" + (options.level || 0))
+ });
+ App.addEvent("load", function() {
+ setTimeout(function() {
+ if (!App.mobile_screen) {
+ self.el.addEvent("scroll", self.preventHover.bind(self));
+ }
+ }, 100);
+ });
+ },
+ load: function() {
+ var self = this;
+ if (self.has_tab) {
+ var nav;
+ if (self.parent_page && self.parent_page.navigation) {
+ nav = self.parent_page.navigation;
+ } else {
+ nav = App.getBlock("navigation");
+ }
+ self.tab = nav.addTab(self.name, {
+ href: App.createUrl(self.getPageUrl()),
+ title: self.title,
+ text: self.name.capitalize(),
+ class: self.icon ? "icon-" + self.icon : null
+ });
+ }
+ if (self.sub_pages) {
+ self.loadSubPages();
+ }
+ },
+ loadSubPages: function() {
+ var self = this;
+ var sub_pages = self.sub_pages;
+ self.sub_pages = [];
+ sub_pages.each(function(class_name) {
+ var pg = new (window[self.name.capitalize() + class_name])(self, {
+ level: 2
+ });
+ self.sub_pages[class_name] = pg;
+ self.sub_pages.include({
+ order: pg.order,
+ name: class_name,
+ class: pg
+ });
+ });
+ self.sub_pages.stableSort(self.sortPageByOrder).each(function(page) {
+ page["class"].load();
+ self.fireEvent("load" + page.name);
+ $(page["class"]).inject(App.getPageContainer());
+ });
+ },
+ open: function(action, params) {
+ var self = this;
+ try {
+ var elements;
+ if (!self[action + "Action"]) {
+ elements = self.defaultAction(action, params);
+ } else {
+ elements = self[action + "Action"](params);
+ }
+ if (elements !== undefined) {
+ self.el.empty();
+ self.el.adopt(elements);
+ }
+ App.getBlock("navigation").activate(self.name);
+ self.fireEvent("opened");
+ } catch (e) {
+ self.errorAction(e);
+ self.fireEvent("error");
+ }
+ },
+ openUrl: function(url) {
+ if (History.getPath() != url) History.push(url);
+ },
+ getPageUrl: function() {
+ var self = this;
+ return (self.parent_page && self.parent_page.getPageUrl ? self.parent_page.getPageUrl() + "/" : "") + self.name;
+ },
+ getPageClass: function() {
+ var self = this;
+ return (self.parent_page && self.parent_page.getPageClass ? self.parent_page.getPageClass() + "_" : "") + self.name;
+ },
+ errorAction: function(e) {
+ p("Error, action not found", e);
+ },
+ getName: function() {
+ return this.name;
+ },
+ show: function() {
+ this.el.addClass("active");
+ },
+ hide: function() {
+ var self = this;
+ self.el.removeClass("active");
+ if (self.sub_pages) {
+ self.sub_pages.each(function(sub_page) {
+ sub_page["class"].hide();
+ });
+ }
+ },
+ preventHover: function() {
+ var self = this;
+ if (self.hover_timer) clearTimeout(self.hover_timer);
+ self.el.addClass("disable_hover");
+ self.hover_timer = setTimeout(function() {
+ self.el.removeClass("disable_hover");
+ }, 200);
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+var Page = {};
+
+var BlockBase = new Class({
+ Implements: [ Options, Events ],
+ options: {},
+ initialize: function(parent, options) {
+ var self = this;
+ self.setOptions(options);
+ self.page = parent;
+ self.create();
+ },
+ create: function() {
+ this.el = new Element("div.block");
+ },
+ getParent: function() {
+ return this.page;
+ },
+ hide: function() {
+ this.el.hide();
+ },
+ show: function() {
+ this.el.show();
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+var BlockNavigation = new Class({
+ Extends: BlockBase,
+ create: function() {
+ var self = this;
+ self.el = new Element("div.navigation").grab(self.nav = new Element("ul"));
+ },
+ addTab: function(name, tab) {
+ var self = this;
+ return new Element("li.tab_" + (name || "unknown")).grab(new Element("a", tab)).inject(self.nav);
+ },
+ activate: function(name) {
+ var self = this;
+ self.nav.getElements(".active").removeClass("active");
+ self.nav.getElements(".tab_" + name).addClass("active");
+ }
+});
+
+var BlockHeader = new Class({
+ Extends: BlockNavigation,
+ create: function() {
+ var self = this, animation_options = {
+ type: dynamics.spring
+ }, couch, potato;
+ self.parent();
+ self.el.adopt(self.logo = new Element("a.logo", {
+ href: App.createUrl(""),
+ events: {
+ mouseenter: function() {
+ dynamics.animate(couch, {
+ opacity: 0,
+ translateX: -50
+ }, animation_options);
+ dynamics.animate(potato, {
+ opacity: 1,
+ translateX: 0
+ }, animation_options);
+ },
+ mouseleave: function() {
+ dynamics.animate(couch, {
+ opacity: 1,
+ translateX: 0
+ }, animation_options);
+ dynamics.animate(potato, {
+ opacity: 0,
+ translateX: 50
+ }, animation_options);
+ }
+ }
+ }).adopt(couch = new Element("span[text=Couch]"), potato = new Element("span[text=Potato]")), self.nav);
+ }
+});
+
+var BlockFooter = new Class({
+ Extends: BlockBase,
+ create: function() {
+ var self = this;
+ self.el = new Element("div.footer");
+ }
+});
+
+var BlockMenu = new Class({
+ Extends: BlockBase,
+ options: {
+ class: "menu"
+ },
+ lis: null,
+ create: function() {
+ var self = this;
+ self.lis = [];
+ self.shown = false;
+ self.el = new Element("div", {
+ class: "more_menu " + self.options["class"]
+ }).adopt(self.wrapper = new Element("div.wrapper").adopt(self.more_option_ul = new Element("ul")), self.button = new Element("a" + (self.options.button_class ? "." + self.options.button_class : ""), {
+ text: self.options.button_text || "",
+ events: {
+ click: function() {
+ if (!self.shown) {
+ dynamics.css(self.wrapper, {
+ opacity: 0,
+ scale: .1,
+ display: "block"
+ });
+ dynamics.animate(self.wrapper, {
+ opacity: 1,
+ scale: 1
+ }, {
+ type: dynamics.spring,
+ frequency: 200,
+ friction: 270,
+ duration: 800
+ });
+ if (self.lis === null) self.lis = self.more_option_ul.getElements("> li").slice(0, 10);
+ self.lis.each(function(li, nr) {
+ dynamics.css(li, {
+ opacity: 0,
+ translateY: 20
+ });
+ dynamics.animate(li, {
+ opacity: 1,
+ translateY: 0
+ }, {
+ type: dynamics.spring,
+ frequency: 300,
+ friction: 435,
+ duration: 1e3,
+ delay: 100 + nr * 40
+ });
+ });
+ self.shown = true;
+ } else {
+ self.hide();
+ }
+ self.fireEvent(self.shown ? "open" : "close");
+ if (self.shown) {
+ self.el.addEvent("outerClick", self.removeOuterClick.bind(self));
+ this.addEvent("outerClick", function(e) {
+ if (e.target.get("tag") != "input") self.removeOuterClick();
+ });
+ } else {
+ self.removeOuterClick();
+ }
+ }
+ }
+ }));
+ },
+ hide: function() {
+ var self = this;
+ dynamics.animate(self.wrapper, {
+ opacity: 0,
+ scale: .1
+ }, {
+ type: dynamics.easeInOut,
+ duration: 300,
+ friction: 100,
+ complete: function() {
+ dynamics.css(self.wrapper, {
+ display: "none"
+ });
+ }
+ });
+ self.shown = false;
+ },
+ removeOuterClick: function() {
+ var self = this;
+ self.hide();
+ self.el.removeClass("show");
+ self.el.removeEvents("outerClick");
+ self.button.removeEvents("outerClick");
+ },
+ addLink: function(tab, position) {
+ var self = this, li = new Element("li").adopt(tab).inject(self.more_option_ul, position || "bottom");
+ self.lis = null;
+ return li;
+ }
+});
+
+Page.Home = new Class({
+ Extends: PageBase,
+ name: "home",
+ title: "Manage new stuff for things and such",
+ icon: "home",
+ indexAction: function() {
+ var self = this;
+ if (self.soon_list) {
+ self.available_list.update();
+ if (self.late_list) self.late_list.update();
+ return;
+ }
+ self.chain = new Chain();
+ self.chain.chain(self.createAvailable.bind(self), self.createSoon.bind(self), self.createSuggestions.bind(self), self.createCharts.bind(self), self.createLate.bind(self));
+ self.chain.callChain();
+ },
+ createAvailable: function() {
+ var self = this;
+ self.available_list = new MovieList({
+ navigation: false,
+ identifier: "snatched",
+ load_more: false,
+ view: "list",
+ actions: [ MA.IMDB, MA.Release, MA.Trailer, MA.Refresh, MA.Readd, MA.Delete, MA.Category, MA.Profile ],
+ title: "Snatched & Available",
+ description: "These movies have been snatched or have finished downloading",
+ on_empty_element: new Element("div").adopt(new Element("h2", {
+ text: "Snatched & Available"
+ }), new Element("span.no_movies", {
+ html: 'No snatched movies or anything!? Damn.. Maybe add a movie.',
+ events: {
+ click: function(e) {
+ e.preventDefault();
+ $(document.body).getElement(".search_form .icon-search").click();
+ }
+ }
+ })),
+ filter: {
+ release_status: "snatched,missing,available,downloaded,done,seeding",
+ with_tags: "recent"
+ },
+ limit: null,
+ onLoaded: function() {
+ self.chain.callChain();
+ },
+ onMovieAdded: function(notification) {
+ var after_search = function(data) {
+ if (notification.data._id != data.data._id) return;
+ self.available_list.update();
+ App.off("movie.searcher.ended", after_search);
+ };
+ App.on("movie.searcher.ended", after_search);
+ }
+ });
+ $(self.available_list).inject(self.el);
+ },
+ createSoon: function() {
+ var self = this;
+ self.soon_list = new MovieList({
+ navigation: false,
+ identifier: "soon",
+ limit: 12,
+ title: "Available soon",
+ description: "These are being searched for and should be available soon as they will be released on DVD in the next few weeks.",
+ filter: {
+ random: true
+ },
+ actions: [ MA.IMDB, MA.Release, MA.Trailer, MA.Refresh, MA.Delete, MA.Category, MA.Profile ],
+ load_more: false,
+ view: "thumb",
+ force_view: true,
+ api_call: "dashboard.soon",
+ onLoaded: function() {
+ self.chain.callChain();
+ }
+ });
+ $(self.soon_list).inject(self.el);
+ },
+ createSuggestions: function() {
+ var self = this;
+ self.suggestions_list = new MovieList({
+ navigation: false,
+ identifier: "suggest",
+ limit: 12,
+ title: "Suggestions",
+ description: "Based on your current wanted and managed items",
+ actions: [ MA.Add, MA.SuggestIgnore, MA.SuggestSeen, MA.IMDB, MA.Trailer ],
+ load_more: false,
+ view: "thumb",
+ force_view: true,
+ api_call: "suggestion.view",
+ onLoaded: function() {
+ self.chain.callChain();
+ }
+ });
+ $(self.suggestions_list).inject(self.el);
+ },
+ createCharts: function() {
+ var self = this;
+ self.charts_list = new Charts({
+ onCreated: function() {
+ self.chain.callChain();
+ }
+ });
+ $(self.charts_list).inject(self.el);
+ },
+ createLate: function() {
+ var self = this;
+ self.late_list = new MovieList({
+ navigation: false,
+ identifier: "late",
+ limit: 50,
+ title: "Still not available",
+ description: 'Try another quality profile or maybe add more providers in Settings.',
+ filter: {
+ late: true
+ },
+ loader: false,
+ load_more: false,
+ view: "list",
+ actions: [ MA.IMDB, MA.Trailer, MA.Refresh, MA.Delete, MA.Category, MA.Profile ],
+ api_call: "dashboard.soon",
+ onLoaded: function() {
+ self.chain.callChain();
+ }
+ });
+ $(self.late_list).inject(self.el);
+ }
+});
+
+Page.Settings = new Class({
+ Extends: PageBase,
+ order: 50,
+ name: "settings",
+ title: "Change settings.",
+ wizard_only: false,
+ tabs: {},
+ lists: {},
+ current: "about",
+ has_tab: false,
+ open: function(action, params) {
+ var self = this;
+ self.action = action == "index" ? self.default_action : action;
+ self.params = params;
+ if (!self.data) self.getData(self.create.bind(self)); else {
+ self.openTab(action);
+ }
+ App.getBlock("navigation").activate(self.name);
+ },
+ openTab: function(action) {
+ var self = this;
+ action = (action == "index" ? "about" : action) || self.action;
+ if (self.current) self.toggleTab(self.current, true);
+ var tab = self.toggleTab(action);
+ self.current = tab == self.tabs.general ? "general" : action;
+ },
+ toggleTab: function(tab_name, hide) {
+ var self = this;
+ var a = hide ? "removeClass" : "addClass";
+ var c = "active";
+ tab_name = tab_name.split("/")[0];
+ var t = self.tabs[tab_name] || self.tabs[self.action] || self.tabs.general;
+ var subtab = null;
+ Object.each(self.params, function(param, subtab_name) {
+ subtab = param;
+ });
+ self.el.getElements("li." + c + " , .tab_content." + c).each(function(active) {
+ active.removeClass(c);
+ });
+ if (t.subtabs[subtab]) {
+ t.tab[a](c);
+ t.subtabs[subtab].tab[a](c);
+ t.subtabs[subtab].content[a](c);
+ if (!hide) t.subtabs[subtab].content.fireEvent("activate");
+ } else {
+ t.tab[a](c);
+ t.content[a](c);
+ if (!hide) t.content.fireEvent("activate");
+ }
+ return t;
+ },
+ getData: function(onComplete) {
+ var self = this;
+ if (onComplete) Api.request("settings", {
+ useSpinner: true,
+ spinnerOptions: {
+ target: self.el
+ },
+ onComplete: function(json) {
+ self.data = json;
+ onComplete(json);
+ }
+ });
+ return self.data;
+ },
+ getValue: function(section, name) {
+ var self = this;
+ try {
+ return self.data.values[section][name];
+ } catch (e) {
+ return "";
+ }
+ },
+ showAdvanced: function() {
+ var self = this;
+ var c = self.advanced_toggle.checked ? "addClass" : "removeClass";
+ self.el[c]("show_advanced");
+ Cookie.write("advanced_toggle_checked", +self.advanced_toggle.checked, {
+ duration: 365
+ });
+ },
+ sortByOrder: function(a, b) {
+ return (a.order || 100) - (b.order || 100);
+ },
+ create: function(json) {
+ var self = this;
+ self.navigation = new Element("div.navigation").adopt(new Element("h2[text=Settings]"), new Element("div.advanced_toggle").adopt(new Element("span", {
+ text: "Show advanced"
+ }), new Element("label.switch").adopt(self.advanced_toggle = new Element("input[type=checkbox]", {
+ checked: +Cookie.read("advanced_toggle_checked"),
+ events: {
+ change: self.showAdvanced.bind(self)
+ }
+ }), new Element("div.toggle"))));
+ self.tabs_container = new Element("ul.tabs");
+ self.containers = new Element("form.uniForm.containers", {
+ events: {
+ "click:relay(.enabler.disabled h2)": function(e, el) {
+ el.getPrevious().getElements(".check").fireEvent("click");
+ }
+ }
+ });
+ self.showAdvanced();
+ var options = [];
+ Object.each(json.options, function(section, section_name) {
+ section.section_name = section_name;
+ options.include(section);
+ });
+ options.stableSort(self.sortByOrder).each(function(section) {
+ var section_name = section.section_name;
+ section.groups.stableSort(self.sortByOrder).each(function(group) {
+ if (group.hidden) return;
+ if (self.wizard_only && !group.wizard) return;
+ if (!self.tabs[group.tab] || !self.tabs[group.tab].groups) self.createTab(group.tab, {});
+ var content_container = self.tabs[group.tab].content;
+ if (group.subtab) {
+ if (!self.tabs[group.tab].subtabs[group.subtab]) self.createSubTab(group.subtab, group, self.tabs[group.tab], group.tab);
+ content_container = self.tabs[group.tab].subtabs[group.subtab].content;
+ }
+ if (group.list && !self.lists[group.list]) {
+ self.lists[group.list] = self.createList(content_container);
+ }
+ if (!self.tabs[group.tab].groups[group.name]) self.tabs[group.tab].groups[group.name] = self.createGroup(group).inject(group.list ? self.lists[group.list] : content_container).addClass("section_" + section_name);
+ if (group.type && group.type == "list") {
+ if (!self.lists[group.name]) self.lists[group.name] = self.createList(content_container); else self.lists[group.name].inject(self.tabs[group.tab].groups[group.name]);
+ }
+ group.options.stableSort(self.sortByOrder).each(function(option) {
+ if (option.hidden) return;
+ var class_name = (option.type || "string").capitalize();
+ var input = new Option[class_name](section_name, option.name, self.getValue(section_name, option.name), option);
+ input.inject(self.tabs[group.tab].groups[group.name]);
+ input.fireEvent("injected");
+ });
+ });
+ });
+ setTimeout(function() {
+ self.el.adopt(self.navigation, self.tabs_container, self.containers);
+ self.fireEvent("create");
+ self.openTab();
+ }, 0);
+ },
+ createTab: function(tab_name, tab) {
+ var self = this;
+ if (self.tabs[tab_name] && self.tabs[tab_name].tab) return self.tabs[tab_name].tab;
+ var label = tab.label || (tab.name || tab_name).capitalize();
+ var tab_el = new Element("li.t_" + tab_name).adopt(new Element("a", {
+ href: App.createUrl(self.name + "/" + tab_name),
+ text: label
+ }).adopt()).inject(self.tabs_container);
+ if (!self.tabs[tab_name]) self.tabs[tab_name] = {
+ label: label
+ };
+ self.tabs[tab_name] = Object.merge(self.tabs[tab_name], {
+ tab: tab_el,
+ subtabs: {},
+ content: new Element("div.tab_content.tab_" + tab_name).inject(self.containers),
+ groups: {}
+ });
+ return self.tabs[tab_name];
+ },
+ createSubTab: function(tab_name, tab, parent_tab, parent_tab_name) {
+ var self = this;
+ if (parent_tab.subtabs[tab_name]) return parent_tab.subtabs[tab_name];
+ if (!parent_tab.subtabs_el) parent_tab.subtabs_el = new Element("ul.subtabs").inject(parent_tab.tab);
+ var label = tab.subtab_label || tab_name.replace("_", " ").capitalize();
+ var tab_el = new Element("li.t_" + tab_name).adopt(new Element("a", {
+ href: App.createUrl(self.name + "/" + parent_tab_name + "/" + tab_name),
+ text: label
+ }).adopt()).inject(parent_tab.subtabs_el);
+ if (!parent_tab.subtabs[tab_name]) parent_tab.subtabs[tab_name] = {
+ label: label
+ };
+ parent_tab.subtabs[tab_name] = Object.merge(parent_tab.subtabs[tab_name], {
+ tab: tab_el,
+ content: new Element("div.tab_content.tab_" + tab_name).inject(self.containers),
+ groups: {}
+ });
+ return parent_tab.subtabs[tab_name];
+ },
+ createGroup: function(group) {
+ var hint;
+ if (typeOf(group.description) == "array") {
+ hint = new Element("span.hint.more_hint", {
+ html: group.description[0]
+ });
+ createTooltip(group.description[1]).inject(hint, "top");
+ } else {
+ hint = new Element("span.hint", {
+ html: group.description || ""
+ });
+ }
+ var icon;
+ if (group.icon) {
+ icon = new Element("span.icon").grab(new Element("img", {
+ src: "data:image/png;base64," + group.icon
+ }));
+ }
+ var label = new Element("span.group_label", {
+ text: group.label || group.name.capitalize()
+ });
+ return new Element("fieldset", {
+ class: (group.advanced ? "inlineLabels advanced" : "inlineLabels") + " group_" + (group.name || "") + " subtab_" + (group.subtab || "")
+ }).grab(new Element("h2").adopt(icon, label, hint));
+ },
+ createList: function(content_container) {
+ return new Element("div.option_list").inject(content_container);
+ }
+});
+
+var OptionBase = new Class({
+ Implements: [ Options, Events ],
+ klass: "textInput",
+ focused_class: "focused",
+ save_on_change: true,
+ initialize: function(section, name, value, options) {
+ var self = this;
+ self.setOptions(options);
+ self.section = section;
+ self.name = name;
+ self.value = self.previous_value = value;
+ self.createBase();
+ self.create();
+ self.createHint();
+ self.setAdvanced();
+ self.input.addEvents({
+ change: self.changed.bind(self),
+ keyup: self.changed.bind(self)
+ });
+ self.addEvent("injected", self.afterInject.bind(self));
+ },
+ createBase: function() {
+ var self = this;
+ self.el = new Element("div.ctrlHolder." + self.section + "_" + self.name);
+ },
+ create: function() {},
+ createLabel: function() {
+ var self = this;
+ return new Element("label", {
+ text: (self.options.label || self.options.name.replace("_", " ")).capitalize()
+ });
+ },
+ setAdvanced: function() {
+ this.el.addClass(this.options.advanced ? "advanced" : "");
+ },
+ createHint: function() {
+ var self = this;
+ if (self.options.description) {
+ if (typeOf(self.options.description) == "array") {
+ var hint = new Element("p.formHint.more_hint", {
+ html: self.options.description[0]
+ }).inject(self.el);
+ createTooltip(self.options.description[1]).inject(hint, "top");
+ } else {
+ new Element("p.formHint", {
+ html: self.options.description || ""
+ }).inject(self.el);
+ }
+ }
+ },
+ afterInject: function() {},
+ changed: function() {
+ var self = this;
+ if (self.getValue() != self.previous_value) {
+ if (self.save_on_change) {
+ if (self.changed_timer) clearTimeout(self.changed_timer);
+ self.changed_timer = self.save.delay(300, self);
+ }
+ self.fireEvent("change");
+ }
+ },
+ save: function() {
+ var self = this;
+ Api.request("settings.save", {
+ data: {
+ section: self.section,
+ name: self.name,
+ value: self.getValue()
+ },
+ useSpinner: true,
+ spinnerOptions: {
+ target: self.el
+ },
+ onComplete: self.saveCompleted.bind(self)
+ });
+ },
+ saveCompleted: function(json) {
+ var self = this;
+ var sc = json.success ? "save_success" : "save_failed";
+ self.previous_value = self.getValue();
+ self.el.addClass(sc);
+ (function() {
+ self.el.removeClass(sc);
+ }).delay(3e3, self);
+ },
+ setName: function(name) {
+ this.name = name;
+ },
+ postName: function() {
+ var self = this;
+ return self.section + "[" + self.name + "]";
+ },
+ getValue: function() {
+ var self = this;
+ return self.input.get("value");
+ },
+ getSettingValue: function() {
+ return this.value;
+ },
+ inject: function(el, position) {
+ this.el.inject(el, position);
+ return this.el;
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+var Option = {};
+
+Option.String = new Class({
+ Extends: OptionBase,
+ type: "string",
+ create: function() {
+ var self = this;
+ self.el.adopt(self.createLabel(), self.input = new Element("input", {
+ type: "text",
+ name: self.postName(),
+ value: self.getSettingValue(),
+ placeholder: self.getPlaceholder()
+ }));
+ },
+ getPlaceholder: function() {
+ return this.options.placeholder;
+ }
+});
+
+Option.Dropdown = new Class({
+ Extends: OptionBase,
+ create: function() {
+ var self = this;
+ self.el.adopt(self.createLabel(), new Element("div.select_wrapper.icon-dropdown").grab(self.input = new Element("select", {
+ name: self.postName()
+ })));
+ Object.each(self.options.values, function(value) {
+ new Element("option", {
+ text: value[0],
+ value: value[1]
+ }).inject(self.input);
+ });
+ self.input.set("value", self.getSettingValue());
+ }
+});
+
+Option.Checkbox = new Class({
+ Extends: OptionBase,
+ type: "checkbox",
+ create: function() {
+ var self = this;
+ var randomId = "r-" + randomString();
+ self.el.adopt(self.createLabel().set("for", randomId), self.input = new Element("input", {
+ name: self.postName(),
+ type: "checkbox",
+ checked: self.getSettingValue(),
+ id: randomId
+ }));
+ },
+ getValue: function() {
+ var self = this;
+ return +self.input.checked;
+ }
+});
+
+Option.Password = new Class({
+ Extends: Option.String,
+ type: "password",
+ create: function() {
+ var self = this;
+ self.el.adopt(self.createLabel(), self.input = new Element("input", {
+ type: "text",
+ name: self.postName(),
+ value: self.getSettingValue() ? "********" : "",
+ placeholder: self.getPlaceholder()
+ }));
+ self.input.addEvent("focus", function() {
+ self.input.set("value", "");
+ self.input.set("type", "password");
+ });
+ }
+});
+
+Option.Bool = new Class({
+ Extends: Option.Checkbox
+});
+
+Option.Enabler = new Class({
+ Extends: Option.Bool,
+ create: function() {
+ var self = this;
+ self.el.adopt(new Element("label.switch").adopt(self.input = new Element("input", {
+ type: "checkbox",
+ checked: self.getSettingValue(),
+ id: "r-" + randomString()
+ }), new Element("div.toggle")));
+ },
+ changed: function() {
+ this.parent();
+ this.checkState();
+ },
+ checkState: function() {
+ var self = this, enabled = self.getValue();
+ self.parentFieldset[enabled ? "removeClass" : "addClass"]("disabled");
+ },
+ afterInject: function() {
+ var self = this;
+ self.parentFieldset = self.el.getParent("fieldset").addClass("enabler");
+ self.parentList = self.parentFieldset.getParent(".option_list");
+ self.el.inject(self.parentFieldset, "top");
+ self.checkState();
+ }
+});
+
+Option.Int = new Class({
+ Extends: Option.String
+});
+
+Option.Float = new Class({
+ Extends: Option.Int
+});
+
+Option.Directory = new Class({
+ Extends: OptionBase,
+ type: "span",
+ browser: null,
+ save_on_change: false,
+ use_cache: false,
+ current_dir: "",
+ create: function() {
+ var self = this;
+ self.el.adopt(self.createLabel(), self.directory_inlay = new Element("span.directory", {
+ events: {
+ click: self.showBrowser.bind(self)
+ }
+ }).adopt(self.input = new Element("input", {
+ value: self.getSettingValue(),
+ events: {
+ change: self.filterDirectory.bind(self),
+ keydown: function(e) {
+ if (e.key == "enter" || e.key == "tab") e.stop();
+ },
+ keyup: self.filterDirectory.bind(self),
+ paste: self.filterDirectory.bind(self)
+ }
+ })));
+ self.cached = {};
+ },
+ filterDirectory: function(e) {
+ var self = this, value = self.getValue(), path_sep = Api.getOption("path_sep"), active_selector = "li:not(.blur):not(.empty)", first;
+ if (e.key == "enter" || e.key == "tab") {
+ e.stop();
+ first = self.dir_list.getElement(active_selector);
+ if (first) {
+ self.selectDirectory(first.get("data-value"));
+ }
+ } else {
+ if (value.substr(-1) == path_sep) {
+ if (self.current_dir != value) self.selectDirectory(value);
+ } else {
+ var pd = self.getParentDir(value);
+ if (self.current_dir != pd) self.getDirs(pd);
+ var folder_filter = value.split(path_sep).getLast();
+ self.dir_list.getElements("li").each(function(li) {
+ var valid = li.get("text").substr(0, folder_filter.length).toLowerCase() != folder_filter.toLowerCase();
+ li[valid ? "addClass" : "removeClass"]("blur");
+ });
+ first = self.dir_list.getElement(active_selector);
+ if (first) {
+ if (!self.dir_list_scroll) self.dir_list_scroll = new Fx.Scroll(self.dir_list, {
+ transition: "quint:in:out"
+ });
+ self.dir_list_scroll.toElement(first);
+ }
+ }
+ }
+ },
+ selectDirectory: function(dir) {
+ var self = this;
+ self.input.set("value", dir);
+ self.getDirs();
+ },
+ previousDirectory: function() {
+ var self = this;
+ self.selectDirectory(self.getParentDir());
+ },
+ caretAtEnd: function() {
+ var self = this;
+ self.input.focus();
+ if (typeof self.input.selectionStart == "number") {
+ self.input.selectionStart = self.input.selectionEnd = self.input.get("value").length;
+ } else if (typeof el.createTextRange != "undefined") {
+ self.input.focus();
+ var range = self.input.createTextRange();
+ range.collapse(false);
+ range.select();
+ }
+ },
+ showBrowser: function() {
+ var self = this;
+ if (!self.browser || self.browser && !self.browser.isVisible()) self.caretAtEnd();
+ if (!self.browser) {
+ self.browser = new Element("div.directory_list").adopt(new Element("div.pointer"), new Element("div.wrapper").adopt(new Element("div.actions").adopt(self.back_button = new Element("a.back", {
+ html: "",
+ events: {
+ click: self.previousDirectory.bind(self)
+ }
+ }), new Element("label", {
+ text: "Hidden folders"
+ }).adopt(self.show_hidden = new Element("input[type=checkbox]", {
+ events: {
+ change: function() {
+ self.getDirs();
+ }
+ }
+ }))), self.dir_list = new Element("ul", {
+ events: {
+ "click:relay(li:not(.empty))": function(e, el) {
+ e.preventDefault();
+ self.selectDirectory(el.get("data-value"));
+ },
+ mousewheel: function(e) {
+ e.stopPropagation();
+ }
+ }
+ }), new Element("div.actions").adopt(new Element("a.clear.button", {
+ text: "Clear",
+ events: {
+ click: function(e) {
+ self.input.set("value", "");
+ self.hideBrowser(e, true);
+ }
+ }
+ }), new Element("a.cancel", {
+ text: "Cancel",
+ events: {
+ click: self.hideBrowser.bind(self)
+ }
+ }), new Element("span", {
+ text: "or"
+ }), self.save_button = new Element("a.button.save", {
+ text: "Save",
+ events: {
+ click: function(e) {
+ self.hideBrowser(e, true);
+ }
+ }
+ })))).inject(self.directory_inlay, "before");
+ }
+ self.initial_directory = self.input.get("value");
+ self.getDirs();
+ self.browser.show();
+ self.el.addEvent("outerClick", self.hideBrowser.bind(self));
+ },
+ hideBrowser: function(e, save) {
+ var self = this;
+ e.preventDefault();
+ if (save) self.save(); else self.input.set("value", self.initial_directory);
+ self.browser.hide();
+ self.el.removeEvents("outerClick");
+ },
+ fillBrowser: function(json) {
+ var self = this, v = self.getValue();
+ self.data = json;
+ var previous_dir = json.parent;
+ if (v === "") self.input.set("value", json.home);
+ if (previous_dir.length >= 1 && !json.is_root) {
+ var prev_dirname = self.getCurrentDirname(previous_dir);
+ if (previous_dir == json.home) prev_dirname = "Home Folder"; else if (previous_dir == "/" && json.platform == "nt") prev_dirname = "Computer";
+ self.back_button.set("data-value", previous_dir);
+ self.back_button.set("html", "« " + prev_dirname);
+ self.back_button.show();
+ } else {
+ self.back_button.hide();
+ }
+ if (self.use_cache) if (!json) json = self.cached[v]; else self.cached[v] = json;
+ self.dir_list.empty();
+ if (json.dirs.length > 0) json.dirs.each(function(dir) {
+ new Element("li", {
+ "data-value": dir,
+ text: self.getCurrentDirname(dir)
+ }).inject(self.dir_list);
+ }); else new Element("li.empty", {
+ text: "Selected folder is empty"
+ }).inject(self.dir_list);
+ self.dir_list.setStyle("webkitTransform", "scale(1)");
+ self.caretAtEnd();
+ },
+ getDirs: function(dir) {
+ var self = this, c = dir || self.getValue();
+ if (self.cached[c] && self.use_cache) {
+ self.fillBrowser();
+ } else {
+ Api.request("directory.list", {
+ data: {
+ path: c,
+ show_hidden: +self.show_hidden.checked
+ },
+ onComplete: function(json) {
+ self.current_dir = c;
+ self.fillBrowser(json);
+ }
+ });
+ }
+ },
+ getParentDir: function(dir) {
+ var self = this;
+ if (!dir && self.data && self.data.parent) return self.data.parent;
+ var v = dir || self.getValue();
+ var sep = Api.getOption("path_sep");
+ var dirs = v.split(sep);
+ if (dirs.pop() === "") dirs.pop();
+ return dirs.join(sep) + sep;
+ },
+ getCurrentDirname: function(dir) {
+ var dir_split = dir.split(Api.getOption("path_sep"));
+ return dir_split[dir_split.length - 2] || Api.getOption("path_sep");
+ },
+ getValue: function() {
+ var self = this;
+ return self.input.get("value");
+ }
+});
+
+Option.Directories = new Class({
+ Extends: Option.String,
+ directories: [],
+ delimiter: "::",
+ afterInject: function() {
+ var self = this;
+ self.el.setStyle("display", "none");
+ self.directories = [];
+ self.getValue().split(self.delimiter).each(function(value) {
+ self.addDirectory(value);
+ });
+ self.addDirectory();
+ },
+ addDirectory: function(value) {
+ var self = this;
+ var has_empty = false;
+ self.directories.each(function(dir) {
+ if (!dir.getValue()) has_empty = true;
+ });
+ if (has_empty) return;
+ var dir = new Option.Directory(self.section, self.name, value || "", self.options);
+ var parent = self.el.getParent("fieldset");
+ var dirs = parent.getElements(".multi_directory");
+ if (dirs.length === 0) $(dir).inject(parent); else $(dir).inject(dirs.getLast(), "after");
+ dir.save = self.saveItems.bind(self);
+ $(dir).getElement("label").set("text", "Movie Folder");
+ $(dir).getElement(".formHint").destroy();
+ $(dir).addClass("multi_directory");
+ if (!value) $(dir).addClass("is_empty");
+ new Element("a.icon2.delete", {
+ events: {
+ click: self.delItem.bind(self, dir)
+ }
+ }).inject(dir);
+ self.directories.include(dir);
+ },
+ delItem: function(dir) {
+ var self = this;
+ self.directories.erase(dir);
+ $(dir).destroy();
+ self.saveItems();
+ self.addDirectory();
+ },
+ saveItems: function() {
+ var self = this;
+ var dirs = [];
+ self.directories.each(function(dir) {
+ if (dir.getValue()) {
+ $(dir).removeClass("is_empty");
+ dirs.include(dir.getValue());
+ } else $(dir).addClass("is_empty");
+ });
+ self.input.set("value", dirs.join(self.delimiter));
+ self.input.fireEvent("change");
+ self.addDirectory();
+ }
+});
+
+Option.Choice = new Class({
+ Extends: Option.String,
+ afterInject: function() {
+ var self = this;
+ self.tags = [];
+ self.replaceInput();
+ self.select = new Element("select").adopt(new Element("option[text=Add option]")).inject(self.tag_input, "after");
+ var o = self.options.options;
+ Object.each(o.choices, function(label, choice) {
+ new Element("option", {
+ text: label,
+ value: o.pre + choice + o.post
+ }).inject(self.select);
+ });
+ self.select = new Form.Dropdown(self.select, {
+ onChange: self.addSelection.bind(self)
+ });
+ },
+ replaceInput: function() {
+ var self = this;
+ self.initialized = self.initialized ? self.initialized + 1 : 1;
+ var value = self.getValue();
+ var matches = value.match(/<([^>]*)>/g);
+ self.tag_input = new Element("ul", {
+ events: {
+ click: function(e) {
+ if (e.target == self.tag_input) {
+ var input = self.tag_input.getElement("li:last-child input");
+ input.fireEvent("focus");
+ input.focus();
+ input.setCaretPosition(input.get("value").length);
+ }
+ self.el.addEvent("outerClick", function() {
+ self.reset();
+ self.el.removeEvents("outerClick");
+ });
+ }
+ }
+ }).inject(self.input, "after");
+ self.el.addClass("tag_input");
+ var mtches = [];
+ if (matches) matches.each(function(match, mnr) {
+ var pos = value.indexOf(match), msplit = [ value.substr(0, pos), value.substr(pos, match.length), value.substr(pos + match.length) ];
+ msplit.each(function(matchsplit, snr) {
+ if (msplit.length - 1 == snr) {
+ value = matchsplit;
+ if (matches.length - 1 == mnr) mtches.append([ value ]);
+ return;
+ }
+ mtches.append([ value == matchsplit ? match : matchsplit ]);
+ });
+ });
+ if (mtches.length === 0 && value !== "") mtches.include(value);
+ mtches.each(self.addTag.bind(self));
+ self.addLastTag();
+ self.sortable = new Sortables(self.tag_input, {
+ revert: true,
+ handle: "",
+ opacity: .5,
+ onComplete: function() {
+ self.setOrder();
+ self.reset();
+ }
+ });
+ var input_group = self.tag_input.getParent(".tab_content");
+ input_group.addEvent("activate", self.setAllWidth.bind(self));
+ },
+ addLastTag: function() {
+ if (this.tag_input.getElement("li.choice:last-child") || !this.tag_input.getElement("li")) this.addTag("");
+ },
+ addTag: function(tag) {
+ var self = this;
+ tag = new Option.Choice.Tag(tag, {
+ onChange: self.setOrder.bind(self),
+ onBlur: function() {
+ self.addLastTag();
+ },
+ onGoLeft: function() {
+ self.goLeft(this);
+ },
+ onGoRight: function() {
+ self.goRight(this);
+ }
+ });
+ $(tag).inject(self.tag_input);
+ if (self.initialized > 1) tag.setWidth(); else (function() {
+ tag.setWidth();
+ }).delay(10, self);
+ self.tags.include(tag);
+ return tag;
+ },
+ goLeft: function(from_tag) {
+ var self = this;
+ from_tag.blur();
+ var prev_index = self.tags.indexOf(from_tag) - 1;
+ if (prev_index >= 0) self.tags[prev_index].selectFrom("right"); else from_tag.focus();
+ },
+ goRight: function(from_tag) {
+ var self = this;
+ from_tag.blur();
+ var next_index = self.tags.indexOf(from_tag) + 1;
+ if (next_index < self.tags.length) self.tags[next_index].selectFrom("left"); else from_tag.focus();
+ },
+ setOrder: function() {
+ var self = this;
+ var value = "";
+ self.tag_input.getElements("li").each(function(el) {
+ value += el.getElement("span").get("text");
+ });
+ self.addLastTag();
+ self.input.set("value", value);
+ self.input.fireEvent("change");
+ self.setAllWidth();
+ },
+ addSelection: function() {
+ var self = this;
+ var tag = self.addTag(self.el.getElement(".selection input").get("value"));
+ self.sortable.addItems($(tag));
+ self.setOrder();
+ self.setAllWidth();
+ },
+ reset: function() {
+ var self = this;
+ self.tag_input.destroy();
+ self.sortable.detach();
+ self.replaceInput();
+ self.setAllWidth();
+ },
+ setAllWidth: function() {
+ var self = this;
+ self.tags.each(function(tag) {
+ tag.setWidth.delay(10, tag);
+ });
+ }
+});
+
+Option.Choice.Tag = new Class({
+ Implements: [ Options, Events ],
+ options: {
+ pre: "<",
+ post: ">"
+ },
+ initialize: function(tag, options) {
+ var self = this;
+ self.setOptions(options);
+ self.tag = tag;
+ self.is_choice = tag.substr(0, 1) == self.options.pre && tag.substr(-1) == self.options.post;
+ self.create();
+ },
+ create: function() {
+ var self = this;
+ self.el = new Element("li", {
+ class: self.is_choice ? "choice" : "",
+ styles: {
+ border: 0
+ },
+ events: {
+ mouseover: !self.is_choice ? self.fireEvent.bind(self, "focus") : function() {}
+ }
+ }).adopt(self.input = new Element(self.is_choice ? "span" : "input", {
+ text: self.tag,
+ value: self.tag,
+ styles: {
+ width: 0
+ },
+ events: {
+ keyup: self.is_choice ? null : function(e) {
+ var current_caret_pos = self.input.getCaretPosition();
+ if (e.key == "left" && current_caret_pos == self.last_caret_pos) {
+ self.fireEvent("goLeft");
+ } else if (e.key == "right" && self.last_caret_pos === current_caret_pos) {
+ self.fireEvent("goRight");
+ }
+ self.last_caret_pos = self.input.getCaretPosition();
+ self.setWidth();
+ self.fireEvent("change");
+ },
+ focus: self.fireEvent.bind(self, "focus"),
+ blur: self.fireEvent.bind(self, "blur")
+ }
+ }), self.span = !self.is_choice ? new Element("span", {
+ text: self.tag
+ }) : null, self.del_button = new Element("a.delete", {
+ events: {
+ click: self.del.bind(self)
+ }
+ }));
+ self.addEvent("focus", self.setWidth.bind(self));
+ },
+ blur: function() {
+ var self = this;
+ self.input.blur();
+ self.selected = false;
+ self.el.removeClass("selected");
+ self.input.removeEvents("outerClick");
+ },
+ focus: function() {
+ var self = this;
+ if (!self.is_choice) {
+ this.input.focus();
+ } else {
+ if (self.selected) return;
+ self.selected = true;
+ self.el.addClass("selected");
+ self.input.addEvent("outerClick", self.blur.bind(self));
+ var temp_input = new Element("input", {
+ events: {
+ keydown: function(e) {
+ e.stop();
+ if (e.key == "right") {
+ self.fireEvent("goRight");
+ this.destroy();
+ } else if (e.key == "left") {
+ self.fireEvent("goLeft");
+ this.destroy();
+ } else if (e.key == "backspace") {
+ self.del();
+ this.destroy();
+ self.fireEvent("goLeft");
+ }
+ }
+ },
+ styles: {
+ height: 0,
+ width: 0,
+ position: "absolute",
+ top: -200
+ }
+ });
+ self.el.adopt(temp_input);
+ temp_input.focus();
+ }
+ },
+ selectFrom: function(direction) {
+ var self = this;
+ if (!direction || self.is_choice) {
+ self.focus();
+ } else {
+ self.focus();
+ var position = direction == "left" ? 0 : self.input.get("value").length;
+ self.input.setCaretPosition(position);
+ }
+ },
+ setWidth: function() {
+ var self = this;
+ if (self.span && self.input) {
+ self.span.set("text", self.input.get("value"));
+ self.input.setStyle("width", self.span.getSize().x + 2);
+ }
+ },
+ del: function() {
+ var self = this;
+ self.el.destroy();
+ self.fireEvent("change");
+ },
+ getValue: function() {
+ return this.span.get("text");
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+Option.Combined = new Class({
+ Extends: Option.String,
+ afterInject: function() {
+ var self = this;
+ self.fieldset = self.input.getParent("fieldset");
+ self.combined_list = new Element("div.combined_table").inject(self.fieldset.getElement("h2"), "after");
+ self.values = {};
+ self.inputs = {};
+ self.items = [];
+ self.labels = {};
+ self.descriptions = {};
+ self.options.combine.each(function(name) {
+ self.inputs[name] = self.fieldset.getElement("input[name=" + self.section + "[" + name + "]]");
+ var values = self.inputs[name].get("value").split(",");
+ values.each(function(value, nr) {
+ if (!self.values[nr]) self.values[nr] = {};
+ self.values[nr][name] = value.trim();
+ });
+ self.inputs[name].getParent(".ctrlHolder").setStyle("display", "none");
+ self.inputs[name].addEvent("change", self.addEmpty.bind(self));
+ });
+ var head = new Element("div.head").inject(self.combined_list);
+ Object.each(self.inputs, function(input, name) {
+ var _in = input.getNext();
+ self.labels[name] = input.getPrevious().get("text");
+ self.descriptions[name] = _in ? _in.get("text") : "";
+ new Element("abbr", {
+ class: name,
+ text: self.labels[name],
+ title: self.descriptions[name]
+ }).inject(head);
+ });
+ Object.each(self.values, function(item) {
+ self.createItem(item);
+ });
+ self.addEmpty();
+ },
+ add_empty_timeout: 0,
+ addEmpty: function() {
+ var self = this;
+ if (self.add_empty_timeout) clearTimeout(self.add_empty_timeout);
+ var has_empty = 0;
+ self.items.each(function(ctrl_holder) {
+ var empty_count = 0;
+ self.options.combine.each(function(name) {
+ var input = ctrl_holder.getElement("input." + name);
+ if (input.get("value") === "" || input.get("type") == "checkbox") empty_count++;
+ });
+ has_empty += empty_count == self.options.combine.length ? 1 : 0;
+ ctrl_holder[empty_count == self.options.combine.length ? "addClass" : "removeClass"]("is_empty");
+ });
+ if (has_empty > 0) return;
+ self.add_empty_timeout = setTimeout(function() {
+ self.createItem({
+ use: true
+ });
+ }, 10);
+ },
+ createItem: function(values) {
+ var self = this;
+ var item = new Element("div.ctrlHolder").inject(self.combined_list), value_count = 0, value_empty = 0;
+ self.options.combine.each(function(name) {
+ var value = values[name] || "";
+ if (name.indexOf("use") != -1) {
+ var checkbox = new Element("input[type=checkbox]." + name, {
+ checked: +value,
+ events: {
+ click: self.saveCombined.bind(self),
+ change: self.saveCombined.bind(self)
+ }
+ }).inject(item);
+ } else {
+ value_count++;
+ new Element("input[type=text]." + name, {
+ value: value,
+ placeholder: self.labels[name] || name,
+ events: {
+ keyup: self.saveCombined.bind(self),
+ change: self.saveCombined.bind(self)
+ }
+ }).inject(item);
+ if (!value) value_empty++;
+ }
+ });
+ item[value_empty == value_count ? "addClass" : "removeClass"]("is_empty");
+ new Element("a.icon-cancel.delete", {
+ events: {
+ click: self.deleteCombinedItem.bind(self)
+ }
+ }).inject(item);
+ self.items.include(item);
+ },
+ saveCombined: function() {
+ var self = this, temp = {};
+ self.items.each(function(item, nr) {
+ self.options.combine.each(function(name) {
+ var input = item.getElement("input." + name);
+ if (item.hasClass("is_empty")) return;
+ if (!temp[name]) temp[name] = [];
+ temp[name][nr] = input.get("type") == "checkbox" ? +input.get("checked") : input.get("value").trim();
+ });
+ });
+ self.options.combine.each(function(name) {
+ self.inputs[name].set("value", (temp[name] || []).join(","));
+ self.inputs[name].fireEvent("change");
+ });
+ self.addEmpty();
+ },
+ deleteCombinedItem: function(e) {
+ var self = this;
+ e.preventDefault();
+ var item = e.target.getParent();
+ self.items.erase(item);
+ item.destroy();
+ self.saveCombined();
+ }
+});
+
+var createTooltip = function(description) {
+ var tip = new Element("div.tooltip", {
+ events: {
+ mouseenter: function() {
+ tip.addClass("shown");
+ },
+ mouseleave: function() {
+ tip.removeClass("shown");
+ }
+ }
+ }).adopt(new Element("a.icon2.info"), new Element("div.tip", {
+ html: description
+ }));
+ return tip;
+};
+
+var AboutSettingTab = new Class({
+ tab: "",
+ content: "",
+ initialize: function() {
+ var self = this;
+ App.addEvent("loadSettings", self.addSettings.bind(self));
+ },
+ addSettings: function() {
+ var self = this;
+ self.settings = App.getPage("Settings");
+ self.settings.addEvent("create", function() {
+ var tab = self.settings.createTab("about", {
+ label: "About",
+ name: "about"
+ });
+ self.tab = tab.tab;
+ self.content = tab.content;
+ self.createAbout();
+ });
+ self.settings.default_action = "about";
+ },
+ createAbout: function() {
+ var self = this;
+ var millennium = new Date(2008, 7, 16), today = new Date(), one_day = 1e3 * 60 * 60 * 24;
+ self.settings.createGroup({
+ label: "About This CouchPotato",
+ name: "variables"
+ }).inject(self.content).adopt(new Element("dl.info").adopt(new Element("dt[text=Version]"), self.version_text = new Element("dd.version", {
+ text: "Getting version...",
+ events: {
+ click: App.checkForUpdate.bind(App, function(json) {
+ self.fillVersion(json.info);
+ }),
+ mouseenter: function() {
+ this.set("text", "Check for updates");
+ },
+ mouseleave: function() {
+ self.fillVersion(Updater.getInfo());
+ }
+ }
+ }), new Element("dt[text=Updater]"), self.updater_type = new Element("dd.updater"), new Element("dt[text=ID]"), new Element("dd", {
+ text: App.getOption("pid")
+ }), new Element("dt[text=Directories]"), new Element("dd", {
+ text: App.getOption("app_dir")
+ }), new Element("dd", {
+ text: App.getOption("data_dir")
+ }), new Element("dt[text=Startup Args]"), new Element("dd", {
+ html: App.getOption("args")
+ }), new Element("dd", {
+ html: App.getOption("options")
+ })));
+ if (!self.fillVersion(Updater.getInfo())) Updater.addEvent("loaded", self.fillVersion.bind(self));
+ self.settings.createGroup({
+ name: "Help Support CouchPotato"
+ }).inject(self.content).adopt(new Element("div.usenet").adopt(new Element("span", {
+ text: "Help support CouchPotato and save some money for yourself by signing up for an account at"
+ }), new Element("a", {
+ href: "https://usenetserver.com/partners/?a_aid=couchpotato&a_bid=3f357c6f",
+ target: "_blank",
+ text: "UsenetServer"
+ }), new Element("span[text=or]"), new Element("a", {
+ href: "http://www.newshosting.com/partners/?a_aid=couchpotato&a_bid=a0b022df",
+ target: "_blank",
+ text: "Newshosting"
+ }), new Element("span", {
+ text: ". For as low as $7.95 per month, you’ll get:"
+ }), new Element("ul").adopt(new Element("li.icon-ok", {
+ text: Math.ceil((today.getTime() - millennium.getTime()) / one_day) + " days retention"
+ }), new Element("li.icon-ok[text=No speed or download limits]"), new Element("li.icon-ok[text=Free SSL Encrypted connections]"))), new Element("div.donate", {
+ html: "Or support me via:" + ''
+ }));
+ },
+ fillVersion: function(json) {
+ if (!json) return;
+ var self = this;
+ var date = new Date(json.version.date * 1e3);
+ self.version_text.set("text", json.version.hash + (json.version.date ? " (" + date.toLocaleString() + ")" : ""));
+ self.updater_type.set("text", json.version.type != json.branch ? json.version.type + ", " + json.branch : json.branch);
+ }
+});
+
+window.addEvent("domready", function() {
+ new AboutSettingTab();
+});
\ No newline at end of file
diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js
new file mode 100644
index 0000000..0f50f63
--- /dev/null
+++ b/couchpotato/static/scripts/combined.plugins.min.js
@@ -0,0 +1,4004 @@
+var DownloadersBase = new Class({
+ Implements: [ Events ],
+ initialize: function() {
+ var self = this;
+ App.addEvent("loadSettings", self.addTestButtons.bind(self));
+ },
+ addTestButtons: function() {
+ var self = this;
+ var setting_page = App.getPage("Settings");
+ setting_page.addEvent("create", function() {
+ Object.each(setting_page.tabs.downloaders.groups, self.addTestButton.bind(self));
+ });
+ },
+ addTestButton: function(fieldset, plugin_name) {
+ var self = this, button_name = self.testButtonName(fieldset);
+ if (button_name.contains("Downloaders")) return;
+ new Element(".ctrlHolder.test_button").adopt(new Element("a.button", {
+ text: button_name,
+ events: {
+ click: function() {
+ var button = fieldset.getElement(".test_button .button");
+ button.set("text", "Connecting...");
+ Api.request("download." + plugin_name + ".test", {
+ onComplete: function(json) {
+ button.set("text", button_name);
+ var message;
+ if (json.success) {
+ message = new Element("span.success", {
+ text: "Connection successful"
+ }).inject(button, "after");
+ } else {
+ var msg_text = "Connection failed. Check logs for details.";
+ if (json.hasOwnProperty("msg")) msg_text = json.msg;
+ message = new Element("span.failed", {
+ text: msg_text
+ }).inject(button, "after");
+ }
+ (function() {
+ message.destroy();
+ }).delay(3e3);
+ }
+ });
+ }
+ }
+ })).inject(fieldset);
+ },
+ testButtonName: function(fieldset) {
+ var name = fieldset.getElement("h2 .group_label").get("text");
+ return "Test " + name;
+ }
+});
+
+var Downloaders = new DownloadersBase();
+
+var UpdaterBase = new Class({
+ Implements: [ Events ],
+ initialize: function() {
+ var self = this;
+ App.addEvent("load", self.info.bind(self, 2e3));
+ App.addEvent("unload", function() {
+ if (self.timer) clearTimeout(self.timer);
+ });
+ },
+ check: function(onComplete) {
+ var self = this;
+ Api.request("updater.check", {
+ onComplete: function(json) {
+ if (onComplete) onComplete(json);
+ if (json.update_available) self.doUpdate(); else {
+ App.unBlockPage();
+ App.trigger("message", [ "No updates available" ]);
+ }
+ }
+ });
+ },
+ info: function(timeout) {
+ var self = this;
+ if (self.timer) clearTimeout(self.timer);
+ self.timer = setTimeout(function() {
+ Api.request("updater.info", {
+ onComplete: function(json) {
+ self.json = json;
+ self.fireEvent("loaded", [ json ]);
+ if (json.update_version) {
+ self.createMessage(json);
+ } else {
+ if (self.message) self.message.destroy();
+ }
+ }
+ });
+ }, timeout || 0);
+ },
+ getInfo: function() {
+ return this.json;
+ },
+ createMessage: function(data) {
+ var self = this;
+ if (self.message) return;
+ var changelog = "https://github.com/" + data.repo_name + "/compare/" + data.version.hash + "..." + data.branch;
+ if (data.update_version.changelog) changelog = data.update_version.changelog + "#" + data.version.hash + "..." + data.update_version.hash;
+ self.message = new Element("div.message.update").adopt(new Element("span", {
+ text: "A new version is available"
+ }), new Element("a", {
+ href: changelog,
+ text: "see what has changed",
+ target: "_blank"
+ }), new Element("span[text=or]"), new Element("a", {
+ text: "just update, gogogo!",
+ events: {
+ click: self.doUpdate.bind(self)
+ }
+ })).inject(document.body);
+ },
+ doUpdate: function() {
+ var self = this;
+ App.blockPage("Please wait while CouchPotato is being updated with more awesome stuff.", "Updating");
+ Api.request("updater.update", {
+ onComplete: function(json) {
+ if (json.success) self.updating(); else App.unBlockPage();
+ }
+ });
+ },
+ updating: function() {
+ App.checkAvailable.delay(500, App, [ 1e3, function() {
+ window.location.reload();
+ } ]);
+ if (self.message) self.message.destroy();
+ }
+});
+
+var Updater = new UpdaterBase();
+
+var PutIODownloader = new Class({
+ initialize: function() {
+ var self = this;
+ App.addEvent("loadSettings", self.addRegisterButton.bind(self));
+ },
+ addRegisterButton: function() {
+ var self = this;
+ var setting_page = App.getPage("Settings");
+ setting_page.addEvent("create", function() {
+ var fieldset = setting_page.tabs.downloaders.groups.putio, l = window.location;
+ var putio_set = 0;
+ fieldset.getElements("input[type=text]").each(function(el) {
+ putio_set += +(el.get("value") !== "");
+ });
+ new Element(".ctrlHolder").adopt(putio_set > 0 ? [ self.unregister = new Element("a.button.red", {
+ text: 'Unregister "' + fieldset.getElement("input[name*=oauth_token]").get("value") + '"',
+ events: {
+ click: function() {
+ fieldset.getElements("input[name*=oauth_token]").set("value", "").fireEvent("change");
+ self.unregister.destroy();
+ self.unregister_or.destroy();
+ }
+ }
+ }), self.unregister_or = new Element("span[text=or]") ] : null, new Element("a.button", {
+ text: putio_set > 0 ? "Register a different account" : "Register your put.io account",
+ events: {
+ click: function() {
+ Api.request("downloader.putio.auth_url", {
+ data: {
+ host: l.protocol + "//" + l.hostname + (l.port ? ":" + l.port : "")
+ },
+ onComplete: function(json) {
+ window.location = json.url;
+ }
+ });
+ }
+ }
+ })).inject(fieldset.getElement(".test_button"), "before");
+ });
+ }
+});
+
+window.addEvent("domready", function() {
+ new PutIODownloader();
+});
+
+var BlockSearch = new Class({
+ Extends: BlockBase,
+ cache: {},
+ create: function() {
+ var self = this;
+ var focus_timer = 0;
+ self.el = new Element("div.search_form").adopt(new Element("a.icon-search", {
+ events: {
+ click: self.clear.bind(self),
+ touchend: self.clear.bind(self)
+ }
+ }), self.wrapper = new Element("div.wrapper").adopt(self.result_container = new Element("div.results_container", {
+ events: {
+ mousewheel: function(e) {
+ e.stopPropagation();
+ }
+ }
+ }).grab(self.results = new Element("div.results")), new Element("div.input").grab(self.input = new Element("input", {
+ placeholder: "Search & add a new media",
+ events: {
+ input: self.keyup.bind(self),
+ paste: self.keyup.bind(self),
+ change: self.keyup.bind(self),
+ keyup: self.keyup.bind(self),
+ focus: function() {
+ if (focus_timer) clearTimeout(focus_timer);
+ if (this.get("value")) self.hideResults(false);
+ },
+ blur: function() {
+ focus_timer = function() {
+ self.el.removeClass("focused");
+ }.delay(100);
+ }
+ }
+ }))));
+ self.mask = new Element("div.mask").inject(self.result_container);
+ },
+ clear: function(e) {
+ var self = this;
+ e.preventDefault();
+ if (self.last_q === "") {
+ self.input.blur();
+ self.last_q = null;
+ } else {
+ self.last_q = "";
+ self.input.set("value", "");
+ self.el.addClass("focused");
+ self.input.focus();
+ self.media = {};
+ self.results.empty();
+ self.el.removeClass("filled");
+ dynamics.css(self.wrapper, {
+ opacity: 0,
+ scale: .1
+ });
+ dynamics.animate(self.wrapper, {
+ opacity: 1,
+ scale: 1
+ }, {
+ type: dynamics.spring,
+ frequency: 200,
+ friction: 270,
+ duration: 800
+ });
+ }
+ },
+ hideResults: function(bool) {
+ var self = this;
+ if (self.hidden == bool) return;
+ self.el[bool ? "removeClass" : "addClass"]("shown");
+ if (bool) {
+ History.removeEvent("change", self.hideResults.bind(self, !bool));
+ self.el.removeEvent("outerClick", self.hideResults.bind(self, !bool));
+ } else {
+ History.addEvent("change", self.hideResults.bind(self, !bool));
+ self.el.addEvent("outerClick", self.hideResults.bind(self, !bool));
+ }
+ self.hidden = bool;
+ },
+ keyup: function() {
+ var self = this;
+ self.el[self.q() ? "addClass" : "removeClass"]("filled");
+ if (self.q() != self.last_q) {
+ if (self.api_request && self.api_request.isRunning()) self.api_request.cancel();
+ if (self.autocomplete_timer) clearTimeout(self.autocomplete_timer);
+ self.autocomplete_timer = self.autocomplete.delay(300, self);
+ }
+ },
+ autocomplete: function() {
+ var self = this;
+ if (!self.q()) {
+ self.hideResults(true);
+ return;
+ }
+ self.list();
+ },
+ list: function() {
+ var self = this, q = self.q(), cache = self.cache[q];
+ self.hideResults(false);
+ if (!cache) {
+ setTimeout(function() {
+ self.mask.addClass("show");
+ }, 10);
+ if (!self.spinner) self.spinner = createSpinner(self.mask);
+ self.api_request = Api.request("search", {
+ data: {
+ q: q
+ },
+ onComplete: self.fill.bind(self, q)
+ });
+ } else self.fill(q, cache);
+ self.last_q = q;
+ },
+ fill: function(q, json) {
+ var self = this;
+ self.cache[q] = json;
+ self.media = {};
+ self.results.empty();
+ Object.each(json, function(media) {
+ if (typeOf(media) == "array") {
+ Object.each(media, function(me) {
+ var m = new (window["BlockSearch" + me.type.capitalize() + "Item"])(me);
+ $(m).inject(self.results);
+ self.media[m.imdb || "r-" + Math.floor(Math.random() * 1e4)] = m;
+ if (q == m.imdb) m.showOptions();
+ });
+ }
+ });
+ self.mask.removeClass("show");
+ },
+ loading: function(bool) {
+ this.el[bool ? "addClass" : "removeClass"]("loading");
+ },
+ q: function() {
+ return this.input.get("value").trim();
+ }
+});
+
+var MovieDetails = new Class({
+ Extends: BlockBase,
+ sections: null,
+ buttons: null,
+ initialize: function(parent, options) {
+ var self = this;
+ self.sections = {};
+ var category = parent.get("category"), profile = parent.profile;
+ self.el = new Element("div", {
+ class: "page active movie_details level_" + (options.level || 0)
+ }).adopt(self.overlay = new Element("div.overlay", {
+ events: {
+ click: self.close.bind(self)
+ }
+ }).grab(new Element("a.close.icon-left-arrow")), self.content = new Element("div.content").grab(new Element("div.head").adopt(new Element("h1").grab(self.title_dropdown = new BlockMenu(self, {
+ class: "title",
+ button_text: parent.getTitle() + (parent.get("year") ? " (" + parent.get("year") + ")" : ""),
+ button_class: "icon-dropdown"
+ })), self.buttons = new Element("div.buttons"))));
+ self.addSection("description", new Element("div", {
+ text: parent.get("plot")
+ }));
+ var titles = parent.get("info").titles;
+ $(self.title_dropdown).addEvents({
+ "click:relay(li a)": function(e, el) {
+ e.stopPropagation();
+ Api.request("movie.edit", {
+ data: {
+ id: parent.get("_id"),
+ default_title: el.get("text")
+ }
+ });
+ $(self.title_dropdown).getElements(".icon-ok").removeClass("icon-ok");
+ el.addClass("icon-ok");
+ self.title_dropdown.button.set("text", el.get("text") + (parent.get("year") ? " (" + parent.get("year") + ")" : ""));
+ }
+ });
+ titles.each(function(t) {
+ self.title_dropdown.addLink(new Element("a", {
+ text: t,
+ class: parent.get("title") == t ? "icon-ok" : ""
+ }));
+ });
+ },
+ addSection: function(name, section_el) {
+ var self = this;
+ name = name.toLowerCase();
+ self.content.grab(self.sections[name] = new Element("div", {
+ class: "section section_" + name
+ }).grab(section_el));
+ },
+ addButton: function(button) {
+ var self = this;
+ self.buttons.grab(button);
+ },
+ open: function() {
+ var self = this;
+ self.el.addClass("show");
+ if (!App.mobile_screen) {
+ $(self.content).getElements("> .head, > .section").each(function(section, nr) {
+ dynamics.css(section, {
+ opacity: 0,
+ translateY: 100
+ });
+ dynamics.animate(section, {
+ opacity: 1,
+ translateY: 0
+ }, {
+ type: dynamics.spring,
+ frequency: 200,
+ friction: 300,
+ duration: 1200,
+ delay: 500 + nr * 100
+ });
+ });
+ }
+ },
+ close: function() {
+ var self = this;
+ var ended = function() {
+ self.el.dispose();
+ self.overlay.removeEventListener("transitionend", ended);
+ };
+ self.overlay.addEventListener("transitionend", ended, false);
+ if (!App.mobile_screen) {
+ $(self.content).getElements("> .head, > .section").reverse().each(function(section, nr) {
+ dynamics.animate(section, {
+ opacity: 0,
+ translateY: 100
+ }, {
+ type: dynamics.spring,
+ frequency: 200,
+ friction: 300,
+ duration: 1200,
+ delay: nr * 50
+ });
+ });
+ dynamics.setTimeout(function() {
+ self.el.removeClass("show");
+ }, 200);
+ } else {
+ self.el.removeClass("show");
+ }
+ }
+});
+
+var MovieList = new Class({
+ Implements: [ Events, Options ],
+ options: {
+ animated_in: false,
+ navigation: true,
+ limit: 50,
+ load_more: true,
+ loader: true,
+ menu: [],
+ add_new: false,
+ force_view: false
+ },
+ movies: [],
+ movies_added: {},
+ total_movies: 0,
+ letters: {},
+ filter: null,
+ initialize: function(options) {
+ var self = this;
+ self.setOptions(options);
+ self.offset = 0;
+ self.filter = self.options.filter || {
+ starts_with: null,
+ search: null
+ };
+ self.el = new Element("div.movies").adopt(self.title = self.options.title ? new Element("h2", {
+ text: self.options.title,
+ styles: {
+ display: "none"
+ }
+ }) : null, self.description = self.options.description ? new Element("div.description", {
+ html: self.options.description,
+ styles: {
+ display: "none"
+ }
+ }) : null, self.movie_list = new Element("div"), self.load_more = self.options.load_more ? new Element("a.load_more", {
+ events: {
+ click: self.loadMore.bind(self)
+ }
+ }) : null);
+ self.changeView(self.getSavedView() || self.options.view || "thumb");
+ if (self.options.navigation) self.createNavigation();
+ self.getMovies();
+ App.on("movie.added", self.movieAdded.bind(self));
+ App.on("movie.deleted", self.movieDeleted.bind(self));
+ },
+ movieDeleted: function(notification) {
+ var self = this;
+ if (self.movies_added[notification.data._id]) {
+ self.movies.each(function(movie) {
+ if (movie.get("_id") == notification.data._id) {
+ movie.destroy();
+ delete self.movies_added[notification.data._id];
+ self.setCounter(self.counter_count - 1);
+ self.total_movies--;
+ }
+ });
+ }
+ self.checkIfEmpty();
+ },
+ movieAdded: function(notification) {
+ var self = this;
+ self.fireEvent("movieAdded", notification);
+ if (self.options.add_new && !self.movies_added[notification.data._id] && notification.data.status == self.options.status) {
+ window.scroll(0, 0);
+ self.createMovie(notification.data, "top");
+ self.setCounter(self.counter_count + 1);
+ self.checkIfEmpty();
+ }
+ },
+ create: function() {
+ var self = this;
+ if (self.options.load_more) {
+ self.scrollspy = new ScrollSpy({
+ container: self.el.getParent(),
+ min: function() {
+ return self.load_more.getCoordinates().top;
+ },
+ onEnter: self.loadMore.bind(self)
+ });
+ }
+ self.created = true;
+ },
+ addMovies: function(movies, total) {
+ var self = this;
+ if (!self.created) self.create();
+ if (movies.length < self.options.limit && self.scrollspy) {
+ self.load_more.hide();
+ self.scrollspy.stop();
+ }
+ Object.each(movies, function(movie, nr) {
+ self.createMovie(movie, "bottom", nr);
+ });
+ self.total_movies += total;
+ self.setCounter(total);
+ self.calculateSelected();
+ },
+ setCounter: function(count) {
+ var self = this;
+ if (!self.navigation_counter) return;
+ self.counter_count = count;
+ self.navigation_counter.set("text", (count || 0) + " movies");
+ if (self.empty_message) {
+ self.empty_message.destroy();
+ self.empty_message = null;
+ }
+ if (self.total_movies && count === 0 && !self.empty_message) {
+ var message = (self.filter.search ? 'for "' + self.filter.search + '"' : "") + (self.filter.starts_with ? " in " + self.filter.starts_with + "" : "");
+ self.empty_message = new Element(".message", {
+ html: "No movies found " + message + ".
"
+ }).grab(new Element("a", {
+ text: "Reset filter",
+ events: {
+ click: function() {
+ self.filter = {
+ starts_with: null,
+ search: null
+ };
+ self.navigation_search_input.set("value", "");
+ self.reset();
+ self.activateLetter();
+ self.getMovies(true);
+ self.last_search_value = "";
+ }
+ }
+ })).inject(self.movie_list);
+ }
+ },
+ createMovie: function(movie, inject_at, nr) {
+ var self = this, animate = self.options.animated_in && !App.mobile_screen && self.current_view == "thumb" && nr !== undefined;
+ var m = new Movie(self, {
+ actions: self.options.actions,
+ view: self.current_view,
+ onSelect: self.calculateSelected.bind(self)
+ }, movie);
+ var el = $(m);
+ if (animate) {
+ dynamics.css(el, {
+ opacity: 0,
+ translateY: 150
+ });
+ }
+ el.inject(self.movie_list, inject_at || "bottom");
+ m.fireEvent("injected");
+ self.movies.include(m);
+ self.movies_added[movie._id] = true;
+ if (animate) {
+ dynamics.animate(el, {
+ opacity: 1,
+ translateY: 0
+ }, {
+ type: dynamics.spring,
+ frequency: 200,
+ friction: 300,
+ duration: 1200,
+ delay: 100 + nr * 20
+ });
+ }
+ },
+ createNavigation: function() {
+ var self = this;
+ var chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ self.el.addClass("with_navigation");
+ self.navigation = new Element("div.alph_nav").adopt(self.mass_edit_form = new Element("div.mass_edit_form").adopt(new Element("span.select").adopt(self.mass_edit_select = new Element("input[type=checkbox]", {
+ events: {
+ change: self.massEditToggleAll.bind(self)
+ }
+ }), self.mass_edit_selected = new Element("span.count", {
+ text: 0
+ }), self.mass_edit_selected_label = new Element("span", {
+ text: "selected"
+ })), new Element("div.quality").adopt(self.mass_edit_quality = new Element("select"), new Element("a.button.orange", {
+ text: "Change quality",
+ events: {
+ click: self.changeQualitySelected.bind(self)
+ }
+ })), new Element("div.delete").adopt(new Element("span[text=or]"), new Element("a.button.red", {
+ text: "Delete",
+ events: {
+ click: self.deleteSelected.bind(self)
+ }
+ })), new Element("div.refresh").adopt(new Element("span[text=or]"), new Element("a.button.green", {
+ text: "Refresh",
+ events: {
+ click: self.refreshSelected.bind(self)
+ }
+ }))), new Element("div.menus").adopt(self.navigation_counter = new Element("span.counter[title=Total]"), self.filter_menu = new BlockMenu(self, {
+ class: "filter",
+ button_class: "icon-filter"
+ }), self.navigation_actions = new Element("div.actions", {
+ events: {
+ click: function(e, el) {
+ e.preventDefault();
+ var new_view = self.current_view == "list" ? "thumb" : "list";
+ var a = "active";
+ self.navigation_actions.getElements("." + a).removeClass(a);
+ self.changeView(new_view);
+ self.navigation_actions.getElement("[data-view=" + new_view + "]").addClass(a);
+ }
+ }
+ }), self.navigation_menu = new BlockMenu(self, {
+ class: "extra",
+ button_class: "icon-dots"
+ })));
+ Quality.getActiveProfiles().each(function(profile) {
+ new Element("option", {
+ value: profile.get("_id"),
+ text: profile.get("label")
+ }).inject(self.mass_edit_quality);
+ });
+ self.filter_menu.addLink(self.navigation_search_input = new Element("input", {
+ title: "Search through " + self.options.identifier,
+ placeholder: "Search through " + self.options.identifier,
+ events: {
+ keyup: self.search.bind(self),
+ change: self.search.bind(self)
+ }
+ })).addClass("search icon-search");
+ var available_chars;
+ self.filter_menu.addEvent("open", function() {
+ self.navigation_search_input.focus();
+ if (!available_chars && (self.navigation.isDisplayed() || self.navigation.isVisible())) Api.request("media.available_chars", {
+ data: Object.merge({
+ status: self.options.status
+ }, self.filter),
+ onSuccess: function(json) {
+ available_chars = json.chars;
+ available_chars.each(function(c) {
+ self.letters[c.capitalize()].addClass("available");
+ });
+ }
+ });
+ });
+ self.filter_menu.addLink(self.navigation_alpha = new Element("ul.numbers", {
+ events: {
+ "click:relay(li.available)": function(e, el) {
+ self.activateLetter(el.get("data-letter"));
+ self.getMovies(true);
+ }
+ }
+ }));
+ [ "thumb", "list" ].each(function(view) {
+ var current = self.current_view == view;
+ new Element("a", {
+ class: "icon-" + view + (current ? " active " : ""),
+ "data-view": view
+ }).inject(self.navigation_actions, current ? "top" : "bottom");
+ });
+ self.letters.all = new Element("li.letter_all.available.active", {
+ text: "ALL"
+ }).inject(self.navigation_alpha);
+ chars.split("").each(function(c) {
+ self.letters[c] = new Element("li", {
+ text: c,
+ class: "letter_" + c,
+ "data-letter": c
+ }).inject(self.navigation_alpha);
+ });
+ if (self.options.menu.length > 0) self.options.menu.each(function(menu_item) {
+ self.navigation_menu.addLink(menu_item);
+ }); else self.navigation_menu.hide();
+ },
+ calculateSelected: function() {
+ var self = this;
+ var selected = 0, movies = self.movies.length;
+ self.movies.each(function(movie) {
+ selected += movie.isSelected() ? 1 : 0;
+ });
+ var indeterminate = selected > 0 && selected < movies, checked = selected == movies && selected > 0;
+ document.body[selected > 0 ? "addClass" : "removeClass"]("mass_editing");
+ if (self.mass_edit_select) {
+ self.mass_edit_select.set("checked", checked);
+ self.mass_edit_select.indeterminate = indeterminate;
+ self.mass_edit_selected.set("text", selected);
+ }
+ },
+ deleteSelected: function() {
+ var self = this, ids = self.getSelectedMovies(), help_msg = self.identifier == "wanted" ? "If you do, you won't be able to watch them, as they won't get downloaded!" : "Your files will be safe, this will only delete the references in CouchPotato";
+ var qObj = new Question("Are you sure you want to delete " + ids.length + " movie" + (ids.length != 1 ? "s" : "") + "?", help_msg, [ {
+ text: "Yes, delete " + (ids.length != 1 ? "them" : "it"),
+ class: "delete",
+ events: {
+ click: function(e) {
+ e.preventDefault();
+ this.set("text", "Deleting..");
+ Api.request("media.delete", {
+ method: "post",
+ data: {
+ id: ids.join(","),
+ delete_from: self.options.identifier
+ },
+ onSuccess: function() {
+ qObj.close();
+ var erase_movies = [];
+ self.movies.each(function(movie) {
+ if (movie.isSelected()) {
+ $(movie).destroy();
+ erase_movies.include(movie);
+ }
+ });
+ erase_movies.each(function(movie) {
+ self.movies.erase(movie);
+ movie.destroy();
+ self.setCounter(self.counter_count - 1);
+ self.total_movies--;
+ });
+ self.calculateSelected();
+ }
+ });
+ }
+ }
+ }, {
+ text: "Cancel",
+ cancel: true
+ } ]);
+ },
+ changeQualitySelected: function() {
+ var self = this;
+ var ids = self.getSelectedMovies();
+ Api.request("movie.edit", {
+ method: "post",
+ data: {
+ id: ids.join(","),
+ profile_id: self.mass_edit_quality.get("value")
+ },
+ onSuccess: self.search.bind(self)
+ });
+ },
+ refreshSelected: function() {
+ var self = this;
+ var ids = self.getSelectedMovies();
+ Api.request("media.refresh", {
+ method: "post",
+ data: {
+ id: ids.join(",")
+ }
+ });
+ },
+ getSelectedMovies: function() {
+ var self = this;
+ var ids = [];
+ self.movies.each(function(movie) {
+ if (movie.isSelected()) ids.include(movie.get("_id"));
+ });
+ return ids;
+ },
+ massEditToggleAll: function() {
+ var self = this;
+ var select = self.mass_edit_select.get("checked");
+ self.movies.each(function(movie) {
+ movie.select(select);
+ });
+ self.calculateSelected();
+ },
+ reset: function() {
+ var self = this;
+ self.movies = [];
+ if (self.mass_edit_select) self.calculateSelected();
+ if (self.navigation_alpha) self.navigation_alpha.getElements(".active").removeClass("active");
+ self.offset = 0;
+ if (self.scrollspy) {
+ self.scrollspy.start();
+ }
+ },
+ activateLetter: function(letter) {
+ var self = this;
+ self.reset();
+ self.letters[letter || "all"].addClass("active");
+ self.filter.starts_with = letter;
+ },
+ changeView: function(new_view) {
+ var self = this;
+ self.el.removeClass(self.current_view + "_list").addClass(new_view + "_list");
+ self.current_view = new_view;
+ Cookie.write(self.options.identifier + "_view", new_view, {
+ duration: 1e3
+ });
+ },
+ getSavedView: function() {
+ var self = this;
+ return Cookie.read(self.options.identifier + "_view");
+ },
+ search: function() {
+ var self = this;
+ if (self.search_timer) clearTimeout(self.search_timer);
+ self.search_timer = function() {
+ var search_value = self.navigation_search_input.get("value");
+ if (search_value == self.last_search_value) return;
+ self.reset();
+ self.activateLetter();
+ self.filter.search = search_value;
+ self.getMovies(true);
+ self.last_search_value = search_value;
+ }.delay(250);
+ },
+ update: function() {
+ var self = this;
+ self.reset();
+ self.getMovies(true);
+ },
+ getMovies: function(reset) {
+ var self = this;
+ if (self.scrollspy) {
+ self.scrollspy.stop();
+ self.load_more.set("text", "loading...");
+ }
+ if (self.movies.length === 0 && self.options.loader) {
+ self.loader_first = new Element("div.mask.loading.with_message").adopt(new Element("div.message", {
+ text: self.options.title ? "Loading '" + self.options.title + "'" : "Loading..."
+ })).inject(self.el, "top");
+ createSpinner(self.loader_first);
+ var lfc = self.loader_first;
+ setTimeout(function() {
+ lfc.addClass("show");
+ }, 10);
+ self.el.setStyle("min-height", 220);
+ }
+ Api.request(self.options.api_call || "media.list", {
+ data: Object.merge({
+ type: self.options.type || "movie",
+ status: self.options.status,
+ limit_offset: self.options.limit ? self.options.limit + "," + self.offset : null
+ }, self.filter),
+ onSuccess: function(json) {
+ if (reset) self.movie_list.empty();
+ if (self.loader_first) {
+ var lf = self.loader_first;
+ self.loader_first = null;
+ lf.removeClass("show");
+ setTimeout(function() {
+ lf.destroy();
+ }, 1e3);
+ self.el.setStyle("min-height", null);
+ }
+ self.store(json.movies);
+ self.addMovies(json.movies, json.total || json.movies.length);
+ if (self.scrollspy) {
+ self.load_more.set("text", "load more movies");
+ self.scrollspy.start();
+ }
+ self.checkIfEmpty();
+ self.fireEvent("loaded");
+ }
+ });
+ },
+ loadMore: function() {
+ var self = this;
+ if (self.offset >= self.options.limit) self.getMovies();
+ },
+ store: function(movies) {
+ var self = this;
+ self.offset += movies.length;
+ },
+ checkIfEmpty: function() {
+ var self = this;
+ var is_empty = self.movies.length === 0 && (self.total_movies === 0 || self.total_movies === undefined);
+ if (self.title) self.title[is_empty ? "hide" : "show"]();
+ if (self.description) self.description.setStyle("display", [ is_empty ? "none" : "" ]);
+ if (is_empty && self.options.on_empty_element) {
+ self.options.on_empty_element.inject(self.loader_first || self.title || self.movie_list, "after");
+ if (self.navigation) self.navigation.hide();
+ self.empty_element = self.options.on_empty_element;
+ } else if (self.empty_element) {
+ self.empty_element.destroy();
+ if (self.navigation) self.navigation.show();
+ }
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+var MoviesManage = new Class({
+ Extends: PageBase,
+ order: 20,
+ name: "manage",
+ title: "Do stuff to your existing movies!",
+ indexAction: function() {
+ var self = this;
+ if (!self.list) {
+ self.refresh_button = new Element("a", {
+ title: "Rescan your library for new movies",
+ text: "Full library refresh",
+ events: {
+ click: self.refresh.bind(self, true)
+ }
+ });
+ self.refresh_quick = new Element("a", {
+ title: "Just scan for recently changed",
+ text: "Quick library scan",
+ events: {
+ click: self.refresh.bind(self, false)
+ }
+ });
+ self.list = new MovieList({
+ identifier: "manage",
+ filter: {
+ status: "done",
+ release_status: "done",
+ status_or: 1
+ },
+ actions: [ MA.IMDB, MA.Files, MA.Trailer, MA.Readd, MA.Delete ],
+ menu: [ self.refresh_button, self.refresh_quick ],
+ on_empty_element: new Element("div.empty_manage").adopt(new Element("div", {
+ text: "Seems like you don't have anything in your library yet."
+ }), new Element("div", {
+ text: "Add your existing movie folders in "
+ }).adopt(new Element("a", {
+ text: "Settings > Manage",
+ href: App.createUrl("settings/manage")
+ })), new Element("div.after_manage", {
+ text: "When you've done that, hit this button → "
+ }).adopt(new Element("a.button.green", {
+ text: "Hit me, but not too hard",
+ events: {
+ click: self.refresh.bind(self, true)
+ }
+ })))
+ });
+ $(self.list).inject(self.el);
+ self.startProgressInterval();
+ }
+ },
+ refresh: function(full) {
+ var self = this;
+ if (!self.update_in_progress) {
+ Api.request("manage.update", {
+ data: {
+ full: +full
+ }
+ });
+ self.startProgressInterval();
+ }
+ },
+ startProgressInterval: function() {
+ var self = this;
+ self.progress_interval = setInterval(function() {
+ if (self.progress_request && self.progress_request.running) return;
+ self.update_in_progress = true;
+ self.progress_request = Api.request("manage.progress", {
+ onComplete: function(json) {
+ if (!json || !json.progress) {
+ clearInterval(self.progress_interval);
+ self.update_in_progress = false;
+ if (self.progress_container) {
+ self.progress_container.destroy();
+ self.list.update();
+ }
+ } else {
+ var progress = json.progress;
+ if (!self.list.navigation) return;
+ if (!self.progress_container) self.progress_container = new Element("div.progress").inject(self.list.navigation, "after");
+ self.progress_container.empty();
+ var sorted_table = self.parseProgress(json.progress);
+ sorted_table.each(function(folder) {
+ var folder_progress = progress[folder];
+ new Element("div").adopt(new Element("span.folder", {
+ text: folder + (folder_progress.eta > 0 ? ", " + new Date().increment("second", folder_progress.eta).timeDiffInWords().replace("from now", "to go") : "")
+ }), new Element("span.percentage", {
+ text: folder_progress.total ? Math.round((folder_progress.total - folder_progress.to_go) / folder_progress.total * 100) + "%" : "0%"
+ })).inject(self.progress_container);
+ });
+ }
+ }
+ });
+ }, 1e3);
+ },
+ parseProgress: function(progress_object) {
+ var folder, temp_array = [];
+ for (folder in progress_object) {
+ if (progress_object.hasOwnProperty(folder)) {
+ temp_array.push(folder);
+ }
+ }
+ return temp_array.stableSort();
+ }
+});
+
+var MovieAction = new Class({
+ Implements: [ Options ],
+ class_name: "action",
+ label: "UNKNOWN",
+ icon: null,
+ button: null,
+ details: null,
+ detail_button: null,
+ initialize: function(movie, options) {
+ var self = this;
+ self.setOptions(options);
+ self.movie = movie;
+ self.create();
+ if (self.button) {
+ var wrapper = new Element("div", {
+ class: self.class_name
+ });
+ self.button.inject(wrapper);
+ self.button = wrapper;
+ }
+ },
+ create: function() {},
+ getButton: function() {
+ return this.button || null;
+ },
+ getDetails: function() {
+ return this.details || null;
+ },
+ getDetailButton: function() {
+ return this.detail_button || null;
+ },
+ getLabel: function() {
+ return this.label;
+ },
+ disable: function() {
+ if (this.el) this.el.addClass("disable");
+ },
+ enable: function() {
+ if (this.el) this.el.removeClass("disable");
+ },
+ getTitle: function() {
+ var self = this;
+ try {
+ return self.movie.getTitle(true);
+ } catch (e) {
+ try {
+ return self.movie.original_title ? self.movie.original_title : self.movie.titles[0];
+ } catch (e2) {
+ return "Unknown";
+ }
+ }
+ },
+ get: function(key) {
+ var self = this;
+ try {
+ return self.movie.get(key);
+ } catch (e) {
+ return self.movie[key];
+ }
+ },
+ createMask: function() {
+ var self = this;
+ self.mask = new Element("div.mask", {
+ styles: {
+ "z-index": "1"
+ }
+ }).inject(self.movie, "top").fade("hide");
+ },
+ toElement: function() {
+ return this.el || null;
+ }
+});
+
+var MA = {};
+
+MA.IMDB = new Class({
+ Extends: MovieAction,
+ id: null,
+ create: function() {
+ var self = this;
+ self.id = self.movie.getIdentifier ? self.movie.getIdentifier() : self.get("imdb");
+ self.button = self.createButton();
+ self.detail_button = self.createButton();
+ if (!self.id) self.disable();
+ },
+ createButton: function() {
+ var self = this;
+ return new Element("a.imdb", {
+ text: "IMDB",
+ title: "Go to the IMDB page of " + self.getTitle(),
+ href: "http://www.imdb.com/title/" + self.id + "/",
+ target: "_blank"
+ });
+ }
+});
+
+MA.Release = new Class({
+ Extends: MovieAction,
+ label: "Releases",
+ create: function() {
+ var self = this;
+ App.on("movie.searcher.ended", function(notification) {
+ if (self.movie.data._id != notification.data._id) return;
+ self.releases = null;
+ if (self.options_container) {
+ if (self.options_container.isDisplayed()) {
+ self.options_container.destroy();
+ self.getDetails();
+ } else {
+ self.options_container.destroy();
+ self.options_container = null;
+ }
+ }
+ });
+ },
+ getDetails: function(refresh) {
+ var self = this;
+ if (!self.movie.data.releases || self.movie.data.releases.length === 0) return;
+ if (!self.options_container || refresh) {
+ self.options_container = new Element("div.options").grab(self.release_container = new Element("div.releases.table"));
+ new Element("div.item.head").adopt(new Element("span.name", {
+ text: "Release name"
+ }), new Element("span.status", {
+ text: "Status"
+ }), new Element("span.quality", {
+ text: "Quality"
+ }), new Element("span.size", {
+ text: "Size"
+ }), new Element("span.age", {
+ text: "Age"
+ }), new Element("span.score", {
+ text: "Score"
+ }), new Element("span.provider", {
+ text: "Provider"
+ }), new Element("span.actions")).inject(self.release_container);
+ if (self.movie.data.releases) self.movie.data.releases.each(function(release) {
+ var quality = Quality.getQuality(release.quality) || {}, info = release.info || {}, provider = self.get(release, "provider") + (info.provider_extra ? self.get(release, "provider_extra") : "");
+ var release_name = self.get(release, "name");
+ if (release.files && release.files.length > 0) {
+ try {
+ var movie_file = release.files.filter(function(file) {
+ var type = File.Type.get(file.type_id);
+ return type && type.identifier == "movie";
+ }).pick();
+ release_name = movie_file.path.split(Api.getOption("path_sep")).getLast();
+ } catch (e) {}
+ }
+ release.el = new Element("div", {
+ class: "item " + release.status,
+ id: "release_" + release._id
+ }).adopt(new Element("span.name", {
+ text: release_name,
+ title: release_name
+ }), new Element("span.status", {
+ text: release.status,
+ class: "status " + release.status
+ }), new Element("span.quality", {
+ text: quality.label + (release.is_3d ? " 3D" : "") || "n/a"
+ }), new Element("span.size", {
+ text: info.size ? Math.floor(self.get(release, "size")) : "n/a"
+ }), new Element("span.age", {
+ text: self.get(release, "age")
+ }), new Element("span.score", {
+ text: self.get(release, "score")
+ }), new Element("span.provider", {
+ text: provider,
+ title: provider
+ }), new Element("span.actions").adopt(info.detail_url ? new Element("a.icon-info", {
+ href: info.detail_url,
+ target: "_blank"
+ }) : new Element("a"), new Element("a.icon-download", {
+ events: {
+ click: function(e) {
+ e.preventDefault();
+ if (!this.hasClass("completed")) self.download(release);
+ }
+ }
+ }), new Element("a", {
+ class: release.status == "ignored" ? "icon-redo" : "icon-cancel",
+ events: {
+ click: function(e) {
+ e.preventDefault();
+ self.ignore(release);
+ this.toggleClass("icon-redo");
+ this.toggleClass("icon-cancel");
+ }
+ }
+ }))).inject(self.release_container);
+ if (release.status == "ignored" || release.status == "failed" || release.status == "snatched") {
+ if (!self.last_release || self.last_release && self.last_release.status != "snatched" && release.status == "snatched") self.last_release = release;
+ } else if (!self.next_release && release.status == "available") {
+ self.next_release = release;
+ }
+ var update_handle = function(notification) {
+ if (notification.data._id != release._id) return;
+ var q = self.movie.quality.getElement(".q_" + release.quality), new_status = notification.data.status;
+ release.el.set("class", "item " + new_status);
+ var status_el = release.el.getElement(".status");
+ status_el.set("class", "status " + new_status);
+ status_el.set("text", new_status);
+ if (!q && (new_status == "snatched" || new_status == "seeding" || new_status == "done")) q = self.addQuality(release.quality_id);
+ if (q && !q.hasClass(new_status)) {
+ q.removeClass(release.status).addClass(new_status);
+ q.set("title", q.get("title").replace(release.status, new_status));
+ }
+ };
+ App.on("release.update_status", update_handle);
+ });
+ if (self.last_release) self.release_container.getElements("#release_" + self.last_release._id).addClass("last_release");
+ if (self.next_release) self.release_container.getElements("#release_" + self.next_release._id).addClass("next_release");
+ if (self.next_release || self.last_release && [ "ignored", "failed" ].indexOf(self.last_release.status) === false) {
+ self.trynext_container = new Element("div.buttons.try_container").inject(self.release_container, "top");
+ var nr = self.next_release, lr = self.last_release;
+ self.trynext_container.adopt(new Element("span.or", {
+ text: "If anything went wrong, download "
+ }), lr ? new Element("a.orange", {
+ text: "the same release again",
+ events: {
+ click: function() {
+ self.download(lr);
+ }
+ }
+ }) : null, nr && lr ? new Element("span.or", {
+ text: ", "
+ }) : null, nr ? [ new Element("a.green", {
+ text: lr ? "another release" : "the best release",
+ events: {
+ click: function() {
+ self.download(nr);
+ }
+ }
+ }), new Element("span.or", {
+ text: " or pick one below"
+ }) ] : null);
+ }
+ self.last_release = null;
+ self.next_release = null;
+ }
+ return self.options_container;
+ },
+ showHelper: function(e) {
+ var self = this;
+ if (e) e.preventDefault();
+ var has_available = false, has_snatched = false;
+ if (self.movie.data.releases) self.movie.data.releases.each(function(release) {
+ if (has_available && has_snatched) return;
+ if ([ "snatched", "downloaded", "seeding", "done" ].contains(release.status)) has_snatched = true;
+ if ([ "available" ].contains(release.status)) has_available = true;
+ });
+ if (has_available || has_snatched) {
+ self.trynext_container = new Element("div.buttons.trynext").inject(self.movie.info_container);
+ self.trynext_container.adopt(has_available ? [ new Element("a.icon-redo", {
+ text: has_snatched ? "Download another release" : "Download the best release",
+ events: {
+ click: self.tryNextRelease.bind(self)
+ }
+ }), new Element("a.icon-download", {
+ text: "pick one yourself",
+ events: {
+ click: function() {
+ self.movie.quality.fireEvent("click");
+ }
+ }
+ }) ] : null, new Element("a.icon-ok", {
+ text: "mark this movie done",
+ events: {
+ click: self.markMovieDone.bind(self)
+ }
+ }));
+ }
+ },
+ get: function(release, type) {
+ return release.info && release.info[type] !== undefined ? release.info[type] : "n/a";
+ },
+ download: function(release) {
+ var self = this;
+ var release_el = self.release_container.getElement("#release_" + release._id), icon = release_el.getElement(".icon-download");
+ if (icon) icon.addClass("icon spinner").removeClass("download");
+ Api.request("release.manual_download", {
+ data: {
+ id: release._id
+ },
+ onComplete: function(json) {
+ if (icon) icon.removeClass("icon spinner");
+ if (json.success) {
+ if (icon) icon.addClass("completed");
+ release_el.getElement(".release_status").set("text", "snatched");
+ } else if (icon) icon.addClass("attention").set("title", "Something went wrong when downloading, please check logs.");
+ }
+ });
+ },
+ ignore: function(release) {
+ Api.request("release.ignore", {
+ data: {
+ id: release._id
+ }
+ });
+ },
+ markMovieDone: function() {
+ var self = this;
+ Api.request("media.delete", {
+ data: {
+ id: self.movie.get("_id"),
+ delete_from: "wanted"
+ },
+ onComplete: function() {
+ var movie = $(self.movie);
+ movie.set("tween", {
+ duration: 300,
+ onComplete: function() {
+ self.movie.destroy();
+ }
+ });
+ movie.tween("height", 0);
+ }
+ });
+ },
+ tryNextRelease: function() {
+ var self = this;
+ Api.request("movie.searcher.try_next", {
+ data: {
+ media_id: self.movie.get("_id")
+ }
+ });
+ }
+});
+
+MA.Trailer = new Class({
+ Extends: MovieAction,
+ id: null,
+ label: "Trailer",
+ getDetails: function() {
+ var self = this, data_url = 'https://www.googleapis.com/youtube/v3/search?q="{title}" {year} trailer&maxResults=1&type=video&videoDefinition=high&videoEmbeddable=true&part=snippet&key=AIzaSyAT3li1KjfLidaL6Vt8T92MRU7n4VOrjYk';
+ if (!self.player_container) {
+ self.id = "trailer-" + randomString();
+ self.player_container = new Element("div.icon-play[id=" + self.id + "]", {
+ events: {
+ click: self.watch.bind(self)
+ }
+ }).adopt(new Element('span[text="watch"]'), new Element('span[text="trailer"]'));
+ self.container = new Element("div.trailer_container").grab(self.player_container);
+ var url = data_url.substitute({
+ title: encodeURI(self.getTitle()),
+ year: self.get("year")
+ });
+ new Request.JSONP({
+ url: url,
+ onComplete: function(json) {
+ self.video_id = json.items[0].id.videoId;
+ self.container.grab(new Element("div.background", {
+ styles: {
+ "background-image": "url(" + json.items[0].snippet.thumbnails.high.url + ")"
+ }
+ }));
+ }
+ }).send();
+ }
+ return self.container;
+ },
+ watch: function() {
+ var self = this;
+ self.container.set("html", '');
+ }
+});
+
+MA.Category = new Class({
+ Extends: MovieAction,
+ create: function() {
+ var self = this;
+ var category = self.movie.get("category");
+ self.detail_button = new BlockMenu(self, {
+ class: "category",
+ button_text: category ? category.label : "No category",
+ button_class: "icon-dropdown"
+ });
+ var categories = CategoryList.getAll();
+ if (categories.length > 0) {
+ $(self.detail_button).addEvents({
+ "click:relay(li a)": function(e, el) {
+ e.stopPropagation();
+ Api.request("movie.edit", {
+ data: {
+ id: self.movie.get("_id"),
+ category_id: el.get("data-id")
+ }
+ });
+ $(self.detail_button).getElements(".icon-ok").removeClass("icon-ok");
+ el.addClass("icon-ok");
+ self.detail_button.button.set("text", el.get("text"));
+ }
+ });
+ self.detail_button.addLink(new Element("a[text=No category]", {
+ class: !category ? "icon-ok" : "",
+ "data-id": ""
+ }));
+ categories.each(function(c) {
+ self.detail_button.addLink(new Element("a", {
+ text: c.get("label"),
+ class: category && category._id == c.get("_id") ? "icon-ok" : "",
+ "data-id": c.get("_id")
+ }));
+ });
+ } else {
+ $(self.detail_button).hide();
+ }
+ }
+});
+
+MA.Profile = new Class({
+ Extends: MovieAction,
+ create: function() {
+ var self = this;
+ var profile = self.movie.profile;
+ self.detail_button = new BlockMenu(self, {
+ class: "profile",
+ button_text: profile ? profile.get("label") : "No profile",
+ button_class: "icon-dropdown"
+ });
+ var profiles = Quality.getActiveProfiles();
+ if (profiles.length > 0) {
+ $(self.detail_button).addEvents({
+ "click:relay(li a)": function(e, el) {
+ e.stopPropagation();
+ Api.request("movie.edit", {
+ data: {
+ id: self.movie.get("_id"),
+ profile_id: el.get("data-id")
+ }
+ });
+ $(self.detail_button).getElements(".icon-ok").removeClass("icon-ok");
+ el.addClass("icon-ok");
+ self.detail_button.button.set("text", el.get("text"));
+ }
+ });
+ profiles.each(function(pr) {
+ self.detail_button.addLink(new Element("a", {
+ text: pr.get("label"),
+ class: profile && profile.get("_id") == pr.get("_id") ? "icon-ok" : "",
+ "data-id": pr.get("_id")
+ }));
+ });
+ } else {
+ $(self.detail_button).hide();
+ }
+ }
+});
+
+MA.Refresh = new Class({
+ Extends: MovieAction,
+ icon: "refresh",
+ create: function() {
+ var self = this;
+ self.button = self.createButton();
+ self.detail_button = self.createButton();
+ },
+ createButton: function() {
+ var self = this;
+ return new Element("a.refresh", {
+ text: "Refresh",
+ title: "Refresh the movie info and do a forced search",
+ events: {
+ click: self.doRefresh.bind(self)
+ }
+ });
+ },
+ doRefresh: function(e) {
+ var self = this;
+ e.stop();
+ Api.request("media.refresh", {
+ data: {
+ id: self.movie.get("_id")
+ }
+ });
+ }
+});
+
+var SuggestBase = new Class({
+ Extends: MovieAction,
+ getIMDB: function() {
+ return this.movie.data.info.imdb;
+ },
+ refresh: function(json) {
+ var self = this;
+ self.movie.list.addMovies([ json.movie ], 1);
+ var last_added = self.movie.list.movies[self.movie.list.movies.length - 1];
+ $(last_added).inject(self.movie, "before");
+ self.movie.destroy();
+ }
+});
+
+MA.Add = new Class({
+ Extends: SuggestBase,
+ label: "Add",
+ icon: "plus",
+ create: function() {
+ var self = this;
+ self.button = new Element("a.add", {
+ text: "Add",
+ title: "Re-add the movie and mark all previous snatched/downloaded as ignored",
+ events: {
+ click: function() {
+ self.movie.openDetails();
+ }
+ }
+ });
+ },
+ getDetails: function() {
+ var self = this;
+ var m = new BlockSearchMovieItem(self.movie.data.info, {
+ onAdded: function() {
+ Api.request("suggestion.ignore", {
+ data: {
+ imdb: self.movie.data.info.imdb,
+ remove_only: true
+ },
+ onComplete: self.refresh.bind(self)
+ });
+ }
+ });
+ m.showOptions();
+ return m;
+ }
+});
+
+MA.SuggestSeen = new Class({
+ Extends: SuggestBase,
+ icon: "eye",
+ create: function() {
+ var self = this;
+ self.button = self.createButton();
+ self.detail_button = self.createButton();
+ },
+ createButton: function() {
+ var self = this;
+ return new Element("a.add", {
+ text: "Already seen",
+ title: "Already seen it!",
+ events: {
+ click: self.markAsSeen.bind(self)
+ }
+ });
+ },
+ markAsSeen: function(e) {
+ var self = this;
+ e.preventDefault();
+ Api.request("suggestion.ignore", {
+ data: {
+ imdb: self.getIMDB(),
+ mark_seen: 1
+ },
+ onComplete: function(json) {
+ self.refresh(json);
+ if (self.movie.details) {
+ self.movie.details.close();
+ }
+ }
+ });
+ }
+});
+
+MA.SuggestIgnore = new Class({
+ Extends: SuggestBase,
+ icon: "error",
+ create: function() {
+ var self = this;
+ self.button = self.createButton();
+ self.detail_button = self.createButton();
+ },
+ createButton: function() {
+ var self = this;
+ return new Element("a.add", {
+ text: "Ignore",
+ title: "Don't suggest this movie anymore",
+ events: {
+ click: self.markAsIgnored.bind(self)
+ }
+ });
+ },
+ markAsIgnored: function(e) {
+ var self = this;
+ e.preventDefault();
+ Api.request("suggestion.ignore", {
+ data: {
+ imdb: self.getIMDB()
+ },
+ onComplete: function(json) {
+ self.refresh(json);
+ if (self.movie.details) {
+ self.movie.details.close();
+ }
+ }
+ });
+ }
+});
+
+MA.Readd = new Class({
+ Extends: MovieAction,
+ create: function() {
+ var self = this, movie_done = self.movie.data.status == "done", snatched;
+ if (self.movie.data.releases && !movie_done) snatched = self.movie.data.releases.filter(function(release) {
+ return release.status && (release.status == "snatched" || release.status == "seeding" || release.status == "downloaded" || release.status == "done");
+ }).length;
+ if (movie_done || snatched && snatched > 0) self.el = new Element("a.readd", {
+ title: "Re-add the movie and mark all previous snatched/downloaded as ignored",
+ events: {
+ click: self.doReadd.bind(self)
+ }
+ });
+ },
+ doReadd: function(e) {
+ var self = this;
+ e.preventDefault();
+ Api.request("movie.add", {
+ data: {
+ identifier: self.movie.getIdentifier(),
+ ignore_previous: 1
+ }
+ });
+ }
+});
+
+MA.Delete = new Class({
+ Extends: MovieAction,
+ Implements: [ Chain ],
+ create: function() {
+ var self = this;
+ self.button = self.createButton();
+ self.detail_button = self.createButton();
+ },
+ createButton: function() {
+ var self = this;
+ return new Element("a.delete", {
+ text: "Delete",
+ title: "Remove the movie from this CP list",
+ events: {
+ click: self.showConfirm.bind(self)
+ }
+ });
+ },
+ showConfirm: function(e) {
+ var self = this;
+ e.preventDefault();
+ self.question = new Question("Are you sure you want to delete " + self.getTitle() + "?", "", [ {
+ text: "Yes, delete " + self.getTitle(),
+ class: "delete",
+ events: {
+ click: function(e) {
+ e.target.set("text", "Deleting...");
+ self.del();
+ }
+ }
+ }, {
+ text: "Cancel",
+ cancel: true
+ } ]);
+ },
+ del: function() {
+ var self = this;
+ var movie = $(self.movie);
+ Api.request("media.delete", {
+ data: {
+ id: self.movie.get("_id"),
+ delete_from: self.movie.list.options.identifier
+ },
+ onComplete: function() {
+ if (self.question) self.question.close();
+ dynamics.animate(movie, {
+ opacity: 0,
+ scale: 0
+ }, {
+ type: dynamics.bezier,
+ points: [ {
+ x: 0,
+ y: 0,
+ cp: [ {
+ x: .876,
+ y: 0
+ } ]
+ }, {
+ x: 1,
+ y: 1,
+ cp: [ {
+ x: .145,
+ y: 1
+ } ]
+ } ],
+ duration: 400,
+ complete: function() {
+ self.movie.destroy();
+ }
+ });
+ }
+ });
+ }
+});
+
+MA.Files = new Class({
+ Extends: MovieAction,
+ label: "Files",
+ getDetails: function() {
+ var self = this;
+ if (!self.movie.data.releases || self.movie.data.releases.length === 0) return;
+ if (!self.files_container) {
+ self.files_container = new Element("div.files.table");
+ new Element("div.item.head").adopt(new Element("span.name", {
+ text: "File"
+ }), new Element("span.type", {
+ text: "Type"
+ })).inject(self.files_container);
+ if (self.movie.data.releases) Array.each(self.movie.data.releases, function(release) {
+ var rel = new Element("div.release").inject(self.files_container);
+ Object.each(release.files, function(files, type) {
+ Array.each(files, function(file) {
+ new Element("div.file.item").adopt(new Element("span.name", {
+ text: file
+ }), new Element("span.type", {
+ text: type
+ })).inject(rel);
+ });
+ });
+ });
+ }
+ return self.files_container;
+ }
+});
+
+var Movie = new Class({
+ Extends: BlockBase,
+ actions: null,
+ details: null,
+ initialize: function(list, options, data) {
+ var self = this;
+ self.actions = [];
+ self.data = data;
+ self.list = list;
+ var buttons = [];
+ self.el = new Element("a.movie", {
+ events: {
+ click: function(e) {
+ if (e.target.get("tag") != "input") {
+ e.preventDefault();
+ self.openDetails();
+ }
+ },
+ mouseenter: function() {
+ if (self.actions.length <= 0) {
+ self.options.actions.each(function(a) {
+ var action = new a(self), button = action.getButton();
+ if (button) {
+ self.actions_el.grab(button);
+ buttons.push(button);
+ }
+ self.actions.push(action);
+ });
+ }
+ if (App.mobile_screen) return;
+ if (list.current_view == "thumb") {
+ dynamics.css(self.el, {
+ scale: 1,
+ opacity: 1
+ });
+ dynamics.animate(self.el, {
+ scale: .9
+ }, {
+ type: dynamics.bounce
+ });
+ buttons.each(function(el, nr) {
+ dynamics.css(el, {
+ opacity: 0,
+ translateY: 50
+ });
+ dynamics.animate(el, {
+ opacity: 1,
+ translateY: 0
+ }, {
+ type: dynamics.spring,
+ frequency: 200,
+ friction: 300,
+ duration: 800,
+ delay: 100 + nr * 40
+ });
+ });
+ }
+ },
+ mouseleave: function() {
+ if (App.mobile_screen) return;
+ if (list.current_view == "thumb") {
+ dynamics.animate(self.el, {
+ scale: 1
+ }, {
+ type: dynamics.spring
+ });
+ }
+ }
+ }
+ });
+ self.profile = Quality.getProfile(data.profile_id) || {};
+ self.category = CategoryList.getCategory(data.category_id) || {};
+ self.parent(self, options);
+ self.addEvents();
+ },
+ openDetails: function() {
+ var self = this;
+ if (!self.details) {
+ self.details = new MovieDetails(self, {
+ level: 3
+ });
+ self.actions.each(function(action, nr) {
+ var details = action.getDetails();
+ if (details) {
+ self.details.addSection(action.getLabel(), details);
+ } else {
+ var button = action.getDetailButton();
+ if (button) {
+ self.details.addButton(button);
+ }
+ }
+ });
+ }
+ App.getPageContainer().grab(self.details);
+ self.details.open.delay(10, self.details);
+ },
+ addEvents: function() {
+ var self = this;
+ self.global_events = {};
+ self.global_events["movie.update"] = function(notification) {
+ if (self.data._id != notification.data._id) return;
+ self.busy(false);
+ self.update.delay(2e3, self, notification);
+ };
+ App.on("movie.update", self.global_events["movie.update"]);
+ [ "media.busy", "movie.searcher.started" ].each(function(listener) {
+ self.global_events[listener] = function(notification) {
+ if (notification.data && (self.data._id == notification.data._id || typeOf(notification.data._id) == "array" && notification.data._id.indexOf(self.data._id) > -1)) self.busy(true);
+ };
+ App.on(listener, self.global_events[listener]);
+ });
+ self.global_events["movie.searcher.ended"] = function(notification) {
+ if (notification.data && self.data._id == notification.data._id) self.busy(false);
+ };
+ App.on("movie.searcher.ended", self.global_events["movie.searcher.ended"]);
+ self.global_events["release.update_status"] = function(notification) {
+ var data = notification.data;
+ if (data && self.data._id == data.media_id) {
+ if (!self.data.releases) self.data.releases = [];
+ var updated = false;
+ self.data.releases.each(function(release) {
+ if (release._id == data._id) {
+ release.status = data.status;
+ updated = true;
+ }
+ });
+ if (updated) self.updateReleases();
+ }
+ };
+ App.on("release.update_status", self.global_events["release.update_status"]);
+ },
+ destroy: function() {
+ var self = this;
+ self.el.destroy();
+ delete self.list.movies_added[self.get("id")];
+ self.list.movies.erase(self);
+ self.list.checkIfEmpty();
+ if (self.details) self.details.close();
+ Object.each(self.global_events, function(handle, listener) {
+ App.off(listener, handle);
+ });
+ },
+ busy: function(set_busy, timeout) {
+ var self = this;
+ if (!set_busy) {
+ setTimeout(function() {
+ if (self.spinner) {
+ self.mask.fade("out");
+ setTimeout(function() {
+ if (self.mask) self.mask.destroy();
+ if (self.spinner) self.spinner.destroy();
+ self.spinner = null;
+ self.mask = null;
+ }, timeout || 400);
+ }
+ }, timeout || 1e3);
+ } else if (!self.spinner) {
+ self.createMask();
+ self.spinner = createSpinner(self.mask);
+ self.mask.fade("in");
+ }
+ },
+ createMask: function() {
+ var self = this;
+ self.mask = new Element("div.mask", {
+ styles: {
+ "z-index": 4
+ }
+ }).inject(self.el, "top").fade("hide");
+ },
+ update: function(notification) {
+ var self = this;
+ self.actions = [];
+ self.data = notification.data;
+ self.el.empty();
+ self.profile = Quality.getProfile(self.data.profile_id) || {};
+ self.category = CategoryList.getCategory(self.data.category_id) || {};
+ self.create();
+ self.select(self.select_checkbox.get("checked"));
+ self.busy(false);
+ },
+ create: function() {
+ var self = this, d = new Date();
+ self.el.addClass("status_" + self.get("status"));
+ var eta = null, eta_date = null, now = Math.round(+d / 1e3);
+ if (self.data.info.release_date) [ self.data.info.release_date.dvd, self.data.info.release_date.theater ].each(function(timestamp) {
+ if (timestamp > 0 && (eta === null || Math.abs(timestamp - now) < Math.abs(eta - now))) eta = timestamp;
+ });
+ if (eta) {
+ eta_date = new Date(eta * 1e3);
+ if (+eta_date / 1e3 < now) {
+ eta_date = null;
+ } else {
+ eta_date = eta_date.toLocaleString("en-us", {
+ month: "short"
+ }) + (d.getFullYear() != eta_date.getFullYear() ? " " + eta_date.getFullYear() : "");
+ }
+ }
+ var rating, stars;
+ if (self.data.status == "suggested" && self.data.info && self.data.info.rating && self.data.info.rating.imdb) {
+ rating = self.data.info.rating.imdb;
+ stars = [];
+ var half_rating = rating[0] / 2;
+ for (var i = 1; i <= 5; i++) {
+ if (half_rating >= 1) stars.push(new Element("span.icon-star")); else if (half_rating > 0) stars.push(new Element("span.icon-star-half")); else stars.push(new Element("span.icon-star-empty"));
+ half_rating -= 1;
+ }
+ }
+ var thumbnail = new Element("div.poster");
+ if (self.data.files && self.data.files.image_poster && self.data.files.image_poster.length > 0) {
+ thumbnail = new Element("div", {
+ class: "type_image poster",
+ styles: {
+ "background-image": "url(" + Api.createUrl("file.cache") + self.data.files.image_poster[0].split(Api.getOption("path_sep")).pop() + ")"
+ }
+ });
+ } else if (self.data.info && self.data.info.images && self.data.info.images.poster && self.data.info.images.poster.length > 0) {
+ thumbnail = new Element("div", {
+ class: "type_image poster",
+ styles: {
+ "background-image": "url(" + self.data.info.images.poster[0] + ")"
+ }
+ });
+ }
+ self.el.adopt(self.select_checkbox = new Element("input[type=checkbox]", {
+ events: {
+ change: function() {
+ self.fireEvent("select");
+ self.select(self.select_checkbox.get("checked"));
+ }
+ }
+ }), self.thumbnail = thumbnail.grab(self.actions_el = new Element("div.actions", {
+ events: {
+ "click:relay(.action)": function(e) {
+ e.stopPropagation();
+ }
+ }
+ })), self.data_container = new Element("div.data.light").adopt(self.info_container = new Element("div.info").adopt(new Element("div.title").adopt(self.title = new Element("span", {
+ text: self.getTitle() || "n/a"
+ }), self.year = new Element("div.year", {
+ text: self.data.info.year || "n/a"
+ })), self.eta = eta_date && now + 8035200 > eta ? new Element("div.eta", {
+ text: eta_date,
+ title: "ETA"
+ }) : null, self.quality = new Element("div.quality"), self.rating = rating ? new Element("div.rating[title=" + rating[0] + "]").adopt(stars, new Element("span.votes[text=(" + rating[1] + ")][title=Votes]")) : null)));
+ if (!self.thumbnail) self.el.addClass("no_thumbnail");
+ if (self.profile.data) self.profile.getTypes().each(function(type) {
+ var q = self.addQuality(type.get("quality"), type.get("3d"));
+ if ((type.finish === true || type.get("finish")) && !q.hasClass("finish")) {
+ q.addClass("finish");
+ q.set("title", q.get("title") + " Will finish searching for this movie if this quality is found.");
+ }
+ });
+ self.updateReleases();
+ },
+ updateReleases: function() {
+ var self = this;
+ if (!self.data.releases || self.data.releases.length === 0) return;
+ self.data.releases.each(function(release) {
+ var q = self.quality.getElement(".q_" + release.quality + (release.is_3d ? ".is_3d" : ":not(.is_3d)")), status = release.status;
+ if (!q && (status == "snatched" || status == "seeding" || status == "done")) q = self.addQuality(release.quality, release.is_3d || false);
+ if (q && !q.hasClass(status)) {
+ q.addClass(status);
+ q.set("title", (q.get("title") ? q.get("title") : "") + " status: " + status);
+ }
+ });
+ },
+ addQuality: function(quality, is_3d) {
+ var self = this;
+ var q = Quality.getQuality(quality);
+ return new Element("span", {
+ text: q.label + (is_3d ? " 3D" : ""),
+ class: "q_" + q.identifier + (is_3d ? " is_3d" : ""),
+ title: ""
+ }).inject(self.quality);
+ },
+ getTitle: function(prefixed) {
+ var self = this;
+ if (self.data.title) return prefixed ? self.data.title : self.getUnprefixedTitle(self.data.title); else if (self.data.info && self.data.info.titles && self.data.info.titles.length > 0) return prefixed ? self.data.info.titles[0] : self.getUnprefixedTitle(self.data.info.titles[0]);
+ return "Unknown movie";
+ },
+ getUnprefixedTitle: function(t) {
+ if (t.substr(0, 4).toLowerCase() == "the ") t = t.substr(4) + ", The"; else if (t.substr(0, 3).toLowerCase() == "an ") t = t.substr(3) + ", An"; else if (t.substr(0, 2).toLowerCase() == "a ") t = t.substr(2) + ", A";
+ return t;
+ },
+ getIdentifier: function() {
+ var self = this;
+ try {
+ return self.get("identifiers").imdb;
+ } catch (e) {}
+ return self.get("imdb");
+ },
+ get: function(attr) {
+ return this.data[attr] || this.data.info[attr];
+ },
+ select: function(select) {
+ var self = this;
+ self.select_checkbox.set("checked", select);
+ self.el[self.select_checkbox.get("checked") ? "addClass" : "removeClass"]("checked");
+ },
+ isSelected: function() {
+ return this.select_checkbox.get("checked");
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+Page.Movies = new Class({
+ Extends: PageBase,
+ name: "movies",
+ icon: "movie",
+ sub_pages: [ "Wanted", "Manage" ],
+ default_page: "Wanted",
+ current_page: null,
+ initialize: function(parent, options) {
+ var self = this;
+ self.parent(parent, options);
+ self.navigation = new BlockNavigation();
+ $(self.navigation).inject(self.el, "top");
+ },
+ defaultAction: function(action, params) {
+ var self = this;
+ if (self.current_page) {
+ self.current_page.hide();
+ if (self.current_page.list && self.current_page.list.navigation) self.current_page.list.navigation.dispose();
+ }
+ var route = new Route();
+ route.parse(action);
+ var page_name = route.getPage() != "index" ? route.getPage().capitalize() : self.default_page;
+ var page = self.sub_pages.filter(function(page) {
+ return page.name == page_name;
+ }).pick()["class"];
+ page.open(route.getAction() || "index", params);
+ page.show();
+ if (page.list && page.list.navigation) page.list.navigation.inject(self.navigation);
+ self.current_page = page;
+ self.navigation.activate(page_name.toLowerCase());
+ }
+});
+
+var BlockSearchMovieItem = new Class({
+ Implements: [ Options, Events ],
+ initialize: function(info, options) {
+ var self = this;
+ self.setOptions(options);
+ self.info = info;
+ self.alternative_titles = [];
+ self.create();
+ },
+ create: function() {
+ var self = this, info = self.info;
+ self.el = new Element("div.media_result", {
+ id: info.imdb
+ }).adopt(self.thumbnail = info.images && info.images.poster.length > 0 ? new Element("img.thumbnail", {
+ src: info.images.poster[0],
+ height: null,
+ width: null
+ }) : null, self.options_el = new Element("div.options"), self.data_container = new Element("div.data", {
+ events: {
+ click: self.showOptions.bind(self)
+ }
+ }).adopt(self.info_container = new Element("div.info").adopt(new Element("h2", {
+ title: self.getTitle()
+ }).adopt(self.title = new Element("span.title", {
+ text: self.getTitle()
+ }), self.year = info.year ? new Element("span.year", {
+ text: info.year
+ }) : null))));
+ if (info.titles) info.titles.each(function(title) {
+ self.alternativeTitle({
+ title: title
+ });
+ });
+ },
+ alternativeTitle: function(alternative) {
+ var self = this;
+ self.alternative_titles.include(alternative);
+ },
+ getTitle: function() {
+ var self = this;
+ try {
+ return self.info.original_title ? self.info.original_title : self.info.titles[0];
+ } catch (e) {
+ return "Unknown";
+ }
+ },
+ get: function(key) {
+ return this.info[key];
+ },
+ showOptions: function() {
+ var self = this;
+ self.createOptions();
+ self.data_container.addClass("open");
+ self.el.addEvent("outerClick", self.closeOptions.bind(self));
+ },
+ closeOptions: function() {
+ var self = this;
+ self.data_container.removeClass("open");
+ self.el.removeEvents("outerClick");
+ },
+ add: function(e) {
+ var self = this;
+ if (e) e.preventDefault();
+ self.loadingMask();
+ Api.request("movie.add", {
+ data: {
+ identifier: self.info.imdb,
+ title: self.title_select.get("value"),
+ profile_id: self.profile_select.get("value"),
+ category_id: self.category_select.get("value")
+ },
+ onComplete: function(json) {
+ self.options_el.empty();
+ self.options_el.adopt(new Element("div.message", {
+ text: json.success ? "Movie successfully added." : "Movie didn't add properly. Check logs"
+ }));
+ self.mask.fade("out");
+ self.fireEvent("added");
+ },
+ onFailure: function() {
+ self.options_el.empty();
+ self.options_el.adopt(new Element("div.message", {
+ text: "Something went wrong, check the logs for more info."
+ }));
+ self.mask.fade("out");
+ }
+ });
+ },
+ createOptions: function() {
+ var self = this, info = self.info;
+ if (!self.options_el.hasClass("set")) {
+ var in_library;
+ if (info.in_library) {
+ in_library = [];
+ (info.in_library.releases || []).each(function(release) {
+ in_library.include(release.quality);
+ });
+ }
+ self.options_el.grab(new Element("div", {
+ class: info.in_wanted && info.in_wanted.profile_id || in_library ? "in_library_wanted" : ""
+ }).adopt(info.in_wanted && info.in_wanted.profile_id ? new Element("span.in_wanted", {
+ text: "Already in wanted list: " + Quality.getProfile(info.in_wanted.profile_id).get("label")
+ }) : in_library ? new Element("span.in_library", {
+ text: "Already in library: " + in_library.join(", ")
+ }) : null, self.title_select = new Element("select", {
+ name: "title"
+ }), self.profile_select = new Element("select", {
+ name: "profile"
+ }), self.category_select = new Element("select", {
+ name: "category"
+ }).grab(new Element("option", {
+ value: -1,
+ text: "None"
+ })), self.add_button = new Element("a.button", {
+ text: "Add",
+ events: {
+ click: self.add.bind(self)
+ }
+ })));
+ Array.each(self.alternative_titles, function(alt) {
+ new Element("option", {
+ text: alt.title
+ }).inject(self.title_select);
+ });
+ var categories = CategoryList.getAll();
+ if (categories.length === 0) self.category_select.hide(); else {
+ self.category_select.show();
+ categories.each(function(category) {
+ new Element("option", {
+ value: category.data._id,
+ text: category.data.label
+ }).inject(self.category_select);
+ });
+ }
+ var profiles = Quality.getActiveProfiles();
+ if (profiles.length == 1) self.profile_select.hide();
+ profiles.each(function(profile) {
+ new Element("option", {
+ value: profile.get("_id"),
+ text: profile.get("label")
+ }).inject(self.profile_select);
+ });
+ self.options_el.addClass("set");
+ if (categories.length === 0 && self.title_select.getElements("option").length == 1 && profiles.length == 1 && !(self.info.in_wanted && self.info.in_wanted.profile_id || in_library)) self.add();
+ }
+ },
+ loadingMask: function() {
+ var self = this;
+ self.mask = new Element("div.mask").inject(self.el).fade("hide");
+ createSpinner(self.mask);
+ self.mask.fade("in");
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+var MoviesWanted = new Class({
+ Extends: PageBase,
+ order: 10,
+ name: "wanted",
+ title: "Gimmy gimmy gimmy!",
+ folder_browser: null,
+ indexAction: function() {
+ var self = this;
+ if (!self.list) {
+ self.manual_search = new Element("a", {
+ title: "Force a search for the full wanted list",
+ text: "Search all wanted",
+ events: {
+ click: self.doFullSearch.bind(self, true)
+ }
+ });
+ self.scan_folder = new Element("a", {
+ title: "Scan a folder and rename all movies in it",
+ text: "Manual folder scan",
+ events: {
+ click: self.scanFolder.bind(self)
+ }
+ });
+ self.list = new MovieList({
+ identifier: "wanted",
+ status: "active",
+ actions: [ MA.IMDB, MA.Release, MA.Trailer, MA.Refresh, MA.Readd, MA.Delete, MA.Category, MA.Profile ],
+ add_new: true,
+ menu: [ self.manual_search, self.scan_folder ],
+ on_empty_element: App.createUserscriptButtons().addClass("empty_wanted")
+ });
+ $(self.list).inject(self.el);
+ self.startProgressInterval.delay(4e3, self);
+ }
+ },
+ doFullSearch: function() {
+ var self = this;
+ if (!self.search_in_progress) {
+ Api.request("movie.searcher.full_search");
+ self.startProgressInterval();
+ }
+ },
+ startProgressInterval: function() {
+ var self = this;
+ var start_text = self.manual_search.get("text");
+ self.progress_interval = setInterval(function() {
+ if (self.search_progress && self.search_progress.running) return;
+ self.search_progress = Api.request("movie.searcher.progress", {
+ onComplete: function(json) {
+ self.search_in_progress = true;
+ if (!json.movie) {
+ clearInterval(self.progress_interval);
+ self.search_in_progress = false;
+ self.manual_search.set("text", start_text);
+ } else {
+ var progress = json.movie;
+ self.manual_search.set("text", "Searching.. (" + Math.round((progress.total - progress.to_go) / progress.total * 100) + "%)");
+ }
+ }
+ });
+ }, 1e3);
+ },
+ scanFolder: function(e) {
+ e.stop();
+ var self = this;
+ var options = {
+ name: "Scan_folder"
+ };
+ if (!self.folder_browser) {
+ self.folder_browser = new Option.Directory("Scan", "folder", "", options);
+ self.folder_browser.save = function() {
+ var folder = self.folder_browser.getValue();
+ Api.request("renamer.scan", {
+ data: {
+ base_folder: folder
+ }
+ });
+ };
+ self.folder_browser.inject(self.el, "top");
+ self.folder_browser.fireEvent("injected");
+ self.folder_browser.directory_inlay.hide();
+ self.folder_browser.el.removeChild(self.folder_browser.el.firstChild);
+ self.folder_browser.showBrowser();
+ self.folder_browser.browser.getElements(".clear.button").hide();
+ self.folder_browser.save_button.text = "Select";
+ self.folder_browser.browser.style.zIndex = 1e3;
+ } else {
+ self.folder_browser.showBrowser();
+ }
+ }
+});
+
+var Charts = new Class({
+ Implements: [ Options, Events ],
+ shown_once: false,
+ initialize: function(options) {
+ var self = this;
+ self.setOptions(options);
+ self.create();
+ },
+ create: function() {
+ var self = this;
+ self.el = new Element("div.charts").adopt(self.el_no_charts_enabled = new Element("p.no_charts_enabled", {
+ html: "Hey, it looks like you have no charts enabled at the moment. If you'd like some great movie suggestions you can go to settings and turn on some charts of your choice.'
+ }), self.el_refresh_container = new Element("div.refresh").adopt(self.el_refresh_link = new Element("a.refresh.icon2", {
+ href: "#",
+ events: {
+ click: function(e) {
+ e.preventDefault();
+ self.el.getElements(".chart").destroy();
+ self.el_refreshing_text.show();
+ self.el_refresh_link.hide();
+ self.api_request = Api.request("charts.view", {
+ data: {
+ force_update: 1
+ },
+ onComplete: self.fill.bind(self)
+ });
+ }
+ }
+ }), self.el_refreshing_text = new Element("span.refreshing", {
+ text: "Refreshing charts..."
+ })));
+ if (Cookie.read("suggestions_charts_menu_selected") === "charts") {
+ self.show();
+ } else self.el.hide();
+ self.fireEvent.delay(0, self, "created");
+ },
+ fill: function(json) {
+ var self = this;
+ self.el_refreshing_text.hide();
+ self.el_refresh_link.show();
+ if (!json || json.count === 0) {
+ self.el_no_charts_enabled.show();
+ self.el_refresh_link.show();
+ self.el_refreshing_text.hide();
+ } else {
+ self.el_no_charts_enabled.hide();
+ json.charts.sort(function(a, b) {
+ return a.order - b.order;
+ });
+ Object.each(json.charts, function(chart) {
+ var c = new Element("div.chart.tiny_scroll").grab(new Element("h3").grab(new Element("a", {
+ text: chart.name,
+ href: chart.url
+ })));
+ var it = 1;
+ Object.each(chart.list, function(movie) {
+ var m = new BlockSearchMovieItem(movie, {
+ onAdded: function() {
+ self.afterAdded(m, movie);
+ }
+ });
+ var in_database_class = chart.hide_wanted && movie.in_wanted ? "hidden" : movie.in_wanted ? "chart_in_wanted" : chart.hide_library && movie.in_library ? "hidden" : movie.in_library ? "chart_in_library" : "", in_database_title = movie.in_wanted ? "Movie in wanted list" : movie.in_library ? "Movie in library" : "";
+ m.el.addClass(in_database_class).grab(new Element("div.chart_number", {
+ text: it++,
+ title: in_database_title
+ }));
+ m.data_container.grab(new Element("div.actions").adopt(new Element("a.add.icon2", {
+ title: "Add movie with your default quality",
+ "data-add": movie.imdb,
+ events: {
+ click: m.showOptions.bind(m)
+ }
+ }), $(new MA.IMDB(m)), $(new MA.Trailer(m, {
+ height: 150
+ }))));
+ m.data_container.removeEvents("click");
+ var plot = false;
+ if (m.info.plot && m.info.plot.length > 0) plot = m.info.plot;
+ m.info_container.adopt(m.rating = m.info.rating && m.info.rating.imdb && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element("span.rating", {
+ text: parseFloat(m.info.rating.imdb[0]),
+ title: parseInt(m.info.rating.imdb[1]) + " votes"
+ }) : null, m.genre = m.info.genres && m.info.genres.length > 0 ? new Element("span.genres", {
+ text: m.info.genres.slice(0, 3).join(", ")
+ }) : null, m.plot = plot ? new Element("span.plot", {
+ text: plot,
+ events: {
+ click: function() {
+ this.toggleClass("full");
+ }
+ }
+ }) : null);
+ $(m).inject(c);
+ });
+ c.inject(self.el);
+ });
+ }
+ self.fireEvent("loaded");
+ },
+ show: function() {
+ var self = this;
+ self.el.show();
+ if (!self.shown_once) {
+ self.api_request = Api.request("charts.view", {
+ onComplete: self.fill.bind(self)
+ });
+ self.shown_once = true;
+ }
+ },
+ hide: function() {
+ this.el.hide();
+ },
+ afterAdded: function(m) {
+ $(m).getElement("div.chart_number").addClass("chart_in_wanted").set("title", "Movie in wanted list");
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+var TraktAutomation = new Class({
+ initialize: function() {
+ var self = this;
+ App.addEvent("loadSettings", self.addRegisterButton.bind(self));
+ },
+ addRegisterButton: function() {
+ var self = this, setting_page = App.getPage("Settings");
+ setting_page.addEvent("create", function() {
+ var fieldset = setting_page.tabs.automation.groups.trakt_automation, l = window.location;
+ var trakt_set = 0;
+ fieldset.getElements("input[type=text]").each(function(el) {
+ trakt_set += +(el.get("value") !== "");
+ });
+ new Element(".ctrlHolder").adopt(trakt_set > 0 ? [ self.unregister = new Element("a.button.red", {
+ text: "Unregister",
+ events: {
+ click: function() {
+ fieldset.getElements("input[name*=oauth_token]").set("value", "").fireEvent("change");
+ self.unregister.destroy();
+ self.unregister_or.destroy();
+ }
+ }
+ }), self.unregister_or = new Element("span[text=or]") ] : null, new Element("a.button", {
+ text: trakt_set > 0 ? "Register a different account" : "Register your trakt.tv account",
+ events: {
+ click: function() {
+ Api.request("automation.trakt.auth_url", {
+ data: {
+ host: l.protocol + "//" + l.hostname + (l.port ? ":" + l.port : "")
+ },
+ onComplete: function(json) {
+ window.location = json.url;
+ }
+ });
+ }
+ }
+ })).inject(fieldset);
+ });
+ }
+});
+
+new TraktAutomation();
+
+var SuggestList = new Class({
+ Implements: [ Options, Events ],
+ shown_once: false,
+ initialize: function(options) {
+ var self = this;
+ self.setOptions(options);
+ self.create();
+ },
+ create: function() {
+ var self = this;
+ self.el = new Element("div.suggestions", {
+ events: {
+ "click:relay(a.delete)": function(e, el) {
+ e.stop();
+ $(el).getParent(".media_result").destroy();
+ Api.request("suggestion.ignore", {
+ data: {
+ imdb: el.get("data-ignore")
+ },
+ onComplete: self.fill.bind(self)
+ });
+ },
+ "click:relay(a.eye-open)": function(e, el) {
+ e.stop();
+ $(el).getParent(".media_result").destroy();
+ Api.request("suggestion.ignore", {
+ data: {
+ imdb: el.get("data-seen"),
+ mark_seen: 1
+ },
+ onComplete: self.fill.bind(self)
+ });
+ }
+ }
+ });
+ var cookie_menu_select = Cookie.read("suggestions_charts_menu_selected") || "suggestions";
+ if (cookie_menu_select === "suggestions") self.show(); else self.hide();
+ self.fireEvent.delay(0, self, "created");
+ },
+ fill: function(json) {
+ var self = this;
+ if (!json || json.count === 0) {
+ self.el.hide();
+ } else {
+ Object.each(json.suggestions, function(movie) {
+ var m = new BlockSearchMovieItem(movie, {
+ onAdded: function() {
+ self.afterAdded(m, movie);
+ }
+ });
+ m.data_container.grab(new Element("div.actions").adopt(new Element("a.add.icon2", {
+ title: "Add movie with your default quality",
+ "data-add": movie.imdb,
+ events: {
+ click: m.showOptions.bind(m)
+ }
+ }), $(new MA.IMDB(m)), $(new MA.Trailer(m, {
+ height: 150
+ })), new Element("a.delete.icon2", {
+ title: "Don't suggest this movie again",
+ "data-ignore": movie.imdb
+ }), new Element("a.eye-open.icon2", {
+ title: "Seen it, like it, don't add",
+ "data-seen": movie.imdb
+ })));
+ m.data_container.removeEvents("click");
+ var plot = false;
+ if (m.info.plot && m.info.plot.length > 0) plot = m.info.plot;
+ m.info_container.adopt(m.rating = m.info.rating && m.info.rating.imdb && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element("span.rating", {
+ text: parseFloat(m.info.rating.imdb[0]),
+ title: parseInt(m.info.rating.imdb[1]) + " votes"
+ }) : null, m.genre = m.info.genres && m.info.genres.length > 0 ? new Element("span.genres", {
+ text: m.info.genres.slice(0, 3).join(", ")
+ }) : null, m.plot = plot ? new Element("span.plot", {
+ text: plot,
+ events: {
+ click: function() {
+ this.toggleClass("full");
+ }
+ }
+ }) : null);
+ $(m).inject(self.el);
+ });
+ }
+ self.fireEvent("loaded");
+ },
+ afterAdded: function(m, movie) {
+ var self = this;
+ setTimeout(function() {
+ $(m).destroy();
+ Api.request("suggestion.ignore", {
+ data: {
+ imdb: movie.imdb,
+ remove_only: true
+ },
+ onComplete: self.fill.bind(self)
+ });
+ }, 3e3);
+ },
+ show: function() {
+ var self = this;
+ self.el.show();
+ if (!self.shown_once) {
+ self.api_request = Api.request("suggestion.view", {
+ onComplete: self.fill.bind(self)
+ });
+ self.shown_once = true;
+ }
+ },
+ hide: function() {
+ this.el.hide();
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+var NotificationBase = new Class({
+ Extends: BlockBase,
+ Implements: [ Options, Events ],
+ initialize: function(options) {
+ var self = this;
+ self.setOptions(options);
+ App.addEvent("unload", self.stopPoll.bind(self));
+ App.addEvent("reload", self.startInterval.bind(self, [ true ]));
+ App.on("notification", self.notify.bind(self));
+ App.on("message", self.showMessage.bind(self));
+ App.addEvent("loadSettings", self.addTestButtons.bind(self));
+ self.notifications = [];
+ App.addEvent("load", function() {
+ App.block.notification = new BlockMenu(self, {
+ button_class: "icon-notifications",
+ class: "notification_menu",
+ onOpen: self.markAsRead.bind(self)
+ });
+ $(App.block.notification).inject(App.getBlock("search"), "after");
+ self.badge = new Element("div.badge").inject(App.block.notification, "top").hide();
+ });
+ window.addEvent("load", function() {
+ self.startInterval.delay($(window).getSize().x <= 480 ? 2e3 : 100, self);
+ });
+ },
+ notify: function(result) {
+ var self = this;
+ var added = new Date();
+ added.setTime(result.added * 1e3);
+ result.el = App.getBlock("notification").addLink(new Element("span." + (result.read ? "read" : "")).adopt(new Element("span.message", {
+ html: result.message
+ }), new Element("span.added", {
+ text: added.timeDiffInWords(),
+ title: added
+ })), "top");
+ self.notifications.include(result);
+ if ((result.important !== undefined || result.sticky !== undefined) && !result.read) {
+ var sticky = true;
+ App.trigger("message", [ result.message, sticky, result ]);
+ } else if (!result.read) {
+ self.setBadge(self.notifications.filter(function(n) {
+ return !n.read;
+ }).length);
+ }
+ },
+ setBadge: function(value) {
+ var self = this;
+ self.badge.set("text", value);
+ self.badge[value ? "show" : "hide"]();
+ },
+ markAsRead: function(force_ids) {
+ var self = this, ids = force_ids;
+ if (!force_ids) {
+ var rn = self.notifications.filter(function(n) {
+ return !n.read && n.important === undefined;
+ });
+ ids = [];
+ rn.each(function(n) {
+ ids.include(n._id);
+ });
+ }
+ if (ids.length > 0) Api.request("notification.markread", {
+ data: {
+ ids: ids.join(",")
+ },
+ onSuccess: function() {
+ self.setBadge("");
+ }
+ });
+ },
+ startInterval: function(force) {
+ var self = this;
+ if (self.stopped && !force) {
+ self.stopped = false;
+ return;
+ }
+ self.request = Api.request("notification.listener", {
+ data: {
+ init: true
+ },
+ onSuccess: function(json) {
+ self.processData(json, true);
+ }
+ }).send();
+ setInterval(function() {
+ if (self.request && self.request.isRunning()) {
+ self.request.cancel();
+ self.startPoll();
+ }
+ }, 12e4);
+ },
+ startPoll: function() {
+ var self = this;
+ if (self.stopped) return;
+ if (self.request && self.request.isRunning()) self.request.cancel();
+ self.request = Api.request("nonblock/notification.listener", {
+ onSuccess: function(json) {
+ self.processData(json, false);
+ },
+ data: {
+ last_id: self.last_id
+ },
+ onFailure: function() {
+ self.startPoll.delay(2e3, self);
+ }
+ }).send();
+ },
+ stopPoll: function() {
+ if (this.request) this.request.cancel();
+ this.stopped = true;
+ },
+ processData: function(json, init) {
+ var self = this;
+ if (json && json.result) {
+ Array.each(json.result, function(result) {
+ App.trigger(result._t || result.type, [ result ]);
+ if (result.message && result.read === undefined && !init) self.showMessage(result.message);
+ });
+ if (json.result.length > 0) self.last_id = json.result.getLast().message_id;
+ }
+ self.startPoll.delay(1500, self);
+ },
+ showMessage: function(message, sticky, data) {
+ var self = this;
+ if (!self.message_container) self.message_container = new Element("div.messages").inject(document.body);
+ var new_message = new Element("div", {
+ class: "message" + (sticky ? " sticky" : ""),
+ html: '