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", '