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").grab(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 QualityBase = new Class({ tab: "", content: "", setup: function(data) { var self = this; self.qualities = data.qualities; self.profiles_list = null; self.profiles = []; Array.each(data.profiles, self.createProfilesClass.bind(self)); App.addEvent("loadSettings", self.addSettings.bind(self)); }, getProfile: function(id) { return this.profiles.filter(function(profile) { return profile.data._id == id; }).pick(); }, getActiveProfiles: function() { return Array.filter(this.profiles, function(profile) { return !profile.data.hide; }); }, getQuality: function(identifier) { try { return this.qualities.filter(function(q) { return q.identifier == identifier; }).pick(); } catch (e) {} return {}; }, addSettings: function() { var self = this; self.settings = App.getPage("Settings"); self.settings.addEvent("create", function() { var tab = self.settings.createSubTab("profile", { label: "Quality", name: "profile", subtab_label: "Qualities" }, self.settings.tabs.searcher, "searcher"); self.tab = tab.tab; self.content = tab.content; self.createProfiles(); self.createProfileOrdering(); self.createSizes(); }); }, createProfiles: function() { var self = this; var non_core_profiles = Array.filter(self.profiles, function(profile) { return !profile.isCore(); }); var count = non_core_profiles.length; self.settings.createGroup({ label: "Quality Profiles", description: "Create your own profiles with multiple qualities." }).inject(self.content).adopt(self.profile_container = new Element("div.container"), new Element("a.add_new_profile", { text: count > 0 ? "Create another quality profile" : "Click here to create a quality profile.", events: { click: function() { var profile = self.createProfilesClass(); $(profile).inject(self.profile_container); } } })); Array.each(non_core_profiles, function(profile) { $(profile).inject(self.profile_container); }); }, createProfilesClass: function(data) { var self = this; data = data || { id: randomString() }; var profile = new Profile(data); self.profiles.include(profile); return profile; }, createProfileOrdering: function() { var self = this; self.settings.createGroup({ label: "Profile Defaults", description: "(Needs refresh '" + (App.isMac() ? "CMD+R" : "F5") + "' after editing)" }).grab(new Element(".ctrlHolder#profile_ordering").adopt(new Element("label[text=Order]"), self.profiles_list = new Element("ul"), new Element("p.formHint", { html: "Change the order the profiles are in the dropdown list. Uncheck to hide it completely.
First one will be default." }))).inject(self.content); Array.each(self.profiles, function(profile) { var check; new Element("li", { "data-id": profile.data._id }).adopt(check = new Element("input[type=checkbox]", { checked: !profile.data.hide, events: { change: self.saveProfileOrdering.bind(self) } }), new Element("span.profile_label", { text: profile.data.label }), new Element("span.handle.icon-handle")).inject(self.profiles_list); }); var sorted_changed = false; self.profile_sortable = new Sortables(self.profiles_list, { revert: true, handle: ".handle", opacity: .5, onSort: function() { sorted_changed = true; }, onComplete: function() { if (sorted_changed) { self.saveProfileOrdering(); sorted_changed = false; } } }); }, saveProfileOrdering: function() { var self = this, ids = [], hidden = []; self.profiles_list.getElements("li").each(function(el, nr) { ids.include(el.get("data-id")); hidden[nr] = +!el.getElement("input[type=checkbox]").get("checked"); }); Api.request("profile.save_order", { data: { ids: ids, hidden: hidden } }); }, createSizes: function() { var self = this; var group = self.settings.createGroup({ label: "Sizes", description: "Edit the minimal and maximum sizes (in MB) for each quality.", advanced: true, name: "sizes" }).inject(self.content); new Element("div.item.head.ctrlHolder").adopt(new Element("span.label", { text: "Quality" }), new Element("span.min", { text: "Min" }), new Element("span.max", { text: "Max" })).inject(group); Array.each(self.qualities, function(quality) { new Element("div.ctrlHolder.item").adopt(new Element("span.label", { text: quality.label }), new Element("input.min[type=text]", { value: quality.size_min, events: { keyup: function(e) { self.changeSize(quality.identifier, "size_min", e.target.get("value")); } } }), new Element("input.max[type=text]", { value: quality.size_max, events: { keyup: function(e) { self.changeSize(quality.identifier, "size_max", e.target.get("value")); } } })).inject(group); }); }, size_timer: {}, changeSize: function(identifier, type, value) { var self = this; if (self.size_timer[identifier + type]) clearTimeout(self.size_timer[identifier + type]); self.size_timer[identifier + type] = function() { Api.request("quality.size.save", { data: { identifier: identifier, value_type: type, value: value } }); }.delay(300); } }); window.Quality = new QualityBase(); 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) } }), 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.scroll_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: { api_call: "media.list", 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", { events: { "click:relay(.movie)": function(e, el) { el.retrieve("klass").onClick(e); }, "mouseenter:relay(.movie)": function(e, el) { e.stopPropagation(); el.retrieve("klass").onMouseenter(e); }, "mouseleave:relay(.movie)": function(e, el) { e.stopPropagation(); el.retrieve("klass").onMouseleave(e); }, "change:relay(.movie input)": function(e, el) { e.stopPropagation(); el = el.getParent(); var klass = el.retrieve("klass"); klass.fireEvent("select"); klass.select(klass.select_checkbox.get("checked")); } } }), 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(); if (self.options.api_call) 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(); } self.createMovie(movies, "bottom"); 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, movies = Array.isArray(movie) ? movie : [ movie ], movie_els = []; inject_at = inject_at || "bottom"; movies.each(function(movie, nr) { var m = new Movie(self, { actions: self.options.actions, view: self.current_view, onSelect: self.calculateSelected.bind(self) }, movie); var el = $(m); if (inject_at === "bottom") { movie_els.push(el); } else { el.inject(self.movie_list, inject_at); } self.movies.include(m); self.movies_added[movie._id] = true; }); if (movie_els.length > 0) { $(self.movie_list).adopt(movie_els); } }, 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 self.options.force_view ? self.options.view : 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..."); } var loader_timeout; if (self.movies.length === 0 && self.options.loader) { self.loader_first = new Element("div.mask.loading.with_message").grab(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; loader_timeout = setTimeout(function() { lfc.addClass("show"); }, 10); self.el.setStyle("min-height", 220); } Api.request(self.options.api_call, { 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 (loader_timeout) clearTimeout(loader_timeout); 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. Add your existing movie folders in " }).grab(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 → " }).grab(new Element("a.button.green", { text: "Hit me, but not too hard", events: { click: self.refresh.bind(self, true) } }))) }); $(self.list).inject(self.content); 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) {} } var size = info.size ? Math.floor(self.get(release, "size")) : 0; size = size ? size < 1e3 ? size + "MB" : Math.round(size * 10 / 1024) / 10 + "GB" : "n/a"; 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: size }), 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.stopPropagation(); if (!this.hasClass("completed")) self.download(release); } } }), new Element("a", { class: release.status == "ignored" ? "icon-redo" : "icon-cancel", events: { click: function(e) { e.stopPropagation(); 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; }, 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 } }); } }); 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", '