aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.WebDashboard/Html/scripts
diff options
context:
space:
mode:
authorLukePulverenti <luke.pulverenti@gmail.com>2013-02-20 20:33:05 -0500
committerLukePulverenti <luke.pulverenti@gmail.com>2013-02-20 20:33:05 -0500
commit767cdc1f6f6a63ce997fc9476911e2c361f9d402 (patch)
tree49add55976f895441167c66cfa95e5c7688d18ce /MediaBrowser.WebDashboard/Html/scripts
parent845554722efaed872948a9e0f7202e3ef52f1b6e (diff)
Pushing missing changes
Diffstat (limited to 'MediaBrowser.WebDashboard/Html/scripts')
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/AddPluginPage.js243
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/AdvancedConfigurationPage.js63
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/AdvancedMetadataConfigurationPage.js65
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/DashboardPage.js423
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/DisplaySettingsPage.js46
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/EditUserPage.js175
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/Extensions.js333
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/IndexPage.js106
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/ItemDetailPage.js353
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/LogPage.js86
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/LoginPage.js112
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/MediaLibraryPage.js272
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/MediaPlayer.js170
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/MetadataConfigurationPage.js103
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/MetadataImagesPage.js74
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/PluginCatalogPage.js80
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/PluginUpdatesPage.js109
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/PluginsPage.js92
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/ScheduledTaskPage.js294
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/ScheduledTasksPage.js170
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/SupporterKeyPage.js79
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/SupporterPage.js29
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/UpdatePasswordPage.js88
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/UserImagePage.js181
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/UserProfilesPage.js76
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/WizardStartPage.js17
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/WizardUserPage.js59
-rw-r--r--MediaBrowser.WebDashboard/Html/scripts/site.js1185
28 files changed, 5083 insertions, 0 deletions
diff --git a/MediaBrowser.WebDashboard/Html/scripts/AddPluginPage.js b/MediaBrowser.WebDashboard/Html/scripts/AddPluginPage.js
new file mode 100644
index 0000000000..e8cf82e869
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/AddPluginPage.js
@@ -0,0 +1,243 @@
+var AddPluginPage = {
+
+ onPageShow: function () {
+
+ var page = this;
+
+ Dashboard.showLoadingMsg();
+
+ var name = getParameterByName('name');
+
+ var promise1 = ApiClient.getPackageInfo(name);
+ var promise2 = ApiClient.getInstalledPlugins();
+ var promise3 = ApiClient.getPluginSecurityInfo();
+
+ $.when(promise1, promise2, promise3).done(function (response1, response2, response3) {
+
+ AddPluginPage.renderPackage(response1[0], response2[0], response3[0], page);
+
+ });
+ },
+
+ renderPackage: function (pkg, installedPlugins, pluginSecurityInfo, page) {
+
+ var installedPlugin = installedPlugins.filter(function (ip) {
+ return ip.Name == pkg.name;
+ })[0];
+
+ AddPluginPage.populateVersions(pkg, page, installedPlugin);
+ AddPluginPage.populateHistory(pkg);
+
+ Dashboard.setPageTitle(pkg.name);
+
+ if (pkg.shortDescription) {
+ $('#tagline', page).show().html(pkg.shortDescription);
+ } else {
+ $('#tagline', page).hide();
+ }
+
+ $('#overview', page).html(pkg.overview || "");
+
+
+ $('#developer', page).html(pkg.owner);
+
+ if (pkg.isPremium) {
+ $('.premiumPackage', page).show();
+
+ // Fill in registration info
+ var regStatus = "<strong>";
+ if (pkg.isRegistered) {
+ regStatus += "You are currently registered for this feature";
+ } else {
+ if (new Date(pkg.expDate).getTime() < new Date(1970, 1, 1).getTime()) {
+ regStatus += "You have never installed this feature";
+ } else {
+ if (pkg.expDate <= new Date().getTime()) {
+ regStatus += "The trial period for this feature has expired on this machine";
+ } else {
+ regStatus += "The trial period for this feature will expire in " + Math.round((new Date(pkg.expDate).getTime() - new Date().getTime()) / (86400000)) + " day(s)";
+ }
+ }
+ }
+
+ regStatus += "</strong>";
+ $('#regStatus', page).html(regStatus);
+
+ if (pluginSecurityInfo.IsMBSupporter) {
+ $('#regInfo', page).html(pkg.regInfo || "");
+ // Fill in PayPal info
+ $('#featureId', page).val(pkg.featureId);
+ $('#featureName', page).val(pkg.name);
+ $('#amount', page).val(pkg.price);
+ $('#regPrice', page).html("<h2>Price: $" + pkg.price.toFixed(2) + " (USD)</h2>");
+ var url = "http://mb3admin.com/admin/service/user/getPayPalEmail?id=" + pkg.owner;
+ $.getJSON(url).done(function (dev) {
+ if (dev.payPalEmail) {
+ $('#payPalEmail', page).val(dev.payPalEmail);
+
+ } else {
+ $('#ppButton', page).hide();
+ $('#noEmail', page).show();
+ }
+ });
+ } else {
+ $('#regInfo', page).html("<h3>You must be a <a href='supporter.html'>Media Browser Supporter</a> in order to register this feature.</h3>");
+ $('#ppButton', page).hide();
+ }
+
+ } else {
+ $('.premiumPackage', page).hide();
+ }
+
+ if (pkg.richDescUrl) {
+ $('#pViewWebsite', page).show();
+ $('#pViewWebsite a', page)[0].href = pkg.richDescUrl;
+ } else {
+ $('#pViewWebsite', page).hide();
+ }
+
+ if (pkg.previewImage) {
+
+ var color = pkg.tileColor || "#2572EB";
+ var img = pkg.previewImage ? pkg.previewImage : pkg.thumbImage;
+ $('#pPreviewImage', page).show().html("<img src='" + img + "' style='max-width: 100%;border-radius:10px;-moz-box-shadow: 0 0 20px 3px " + color + ";-webkit-box-shadow: 0 0 20px 3px " + color + ";box-shadow: 0 0 20px 3px " + color + ";' />");
+ } else {
+ $('#pPreviewImage', page).hide().html("");
+ }
+
+ if (installedPlugin) {
+ $('#pCurrentVersion', page).show().html("You currently have version <strong>" + installedPlugin.Version + "</strong> installed.");
+
+ } else {
+ $('#pCurrentVersion', page).hide().html("");
+ }
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ populateVersions: function (packageInfo, page, installedPlugin) {
+
+ var html = '';
+
+ for (var i = 0, length = packageInfo.versions.length; i < length; i++) {
+
+ var version = packageInfo.versions[i];
+
+ html += '<option value="' + version.versionStr + '|' + version.classification + '">' + version.versionStr + ' (' + version.classification + ')</option>';
+
+ }
+
+ var selectmenu = $('#selectVersion', page).html(html);
+
+ var packageVersion;
+
+ if (installedPlugin) {
+
+ // Select the first available package with the same update class as the installed version
+ packageVersion = packageInfo.versions.filter(function (current) {
+
+ return current.classification == installedPlugin.UpdateClass;
+ })[0];
+
+
+ } else {
+ $('#pCurrentVersion', page).hide().html("");
+ }
+
+ // If we don't have a package version to select, pick the first release build
+ if (!packageVersion) {
+
+ // Select the first available package with the same update class as the installed version
+ packageVersion = packageInfo.versions.filter(function (current) {
+
+ return current.classification == "Release";
+ })[0];
+ }
+
+ // If we still don't have a package version to select, pick the first Beta build
+ if (!packageVersion) {
+
+ // Select the first available package with the same update class as the installed version
+ packageVersion = packageInfo.versions.filter(function (current) {
+
+ return current.classification == "Beta";
+ })[0];
+ }
+
+ if (packageVersion) {
+ var val = packageVersion.versionStr + '|' + packageVersion.classification;
+
+ $('#selectVersion', page).val(val);
+ }
+
+ selectmenu.selectmenu('refresh');
+ },
+
+ populateHistory: function (packageInfo) {
+
+ var html = '';
+
+ for (var i = 0, length = Math.min(packageInfo.versions.length, 10) ; i < length; i++) {
+
+ var version = packageInfo.versions[i];
+
+ html += '<h2 style="margin:.5em 0;">' + version.versionStr + ' (' + version.classification + ')</h2>';
+
+ html += '<div style="margin-bottom:1.5em;">' + version.description + '</div>';
+ }
+
+ $('#revisionHistory', $.mobile.activePage).html(html);
+ },
+
+ onSubmit: function () {
+
+ Dashboard.showLoadingMsg();
+
+ $('#btnInstall', $.mobile.activePage).button('disable');
+
+ var name = getParameterByName('name');
+
+ ApiClient.getInstalledPlugins().done(function (plugins) {
+
+ var installedPlugin = plugins.filter(function (ip) {
+ return ip.Name == name;
+ })[0];
+
+ var vals = $('#selectVersion', $.mobile.activePage).val().split('|');
+
+ var version = vals[0];
+
+ if (installedPlugin && installedPlugin.Version == version) {
+
+ Dashboard.hideLoadingMsg();
+
+ Dashboard.confirm("Are you sure you wish to reinstall the same version you already have? In most cases this will not have any effect.", "Plugin Reinstallation", function (confirmResult) {
+
+ if (confirmResult) {
+
+ Dashboard.showLoadingMsg();
+ AddPluginPage.performInstallation(name, vals[1], version);
+ } else {
+ $('#btnInstall', $.mobile.activePage).button('enable');
+ }
+
+ });
+ } else {
+ AddPluginPage.performInstallation(name, vals[1], version);
+ }
+ });
+
+
+ return false;
+ },
+
+ performInstallation: function (packageName, updateClass, version) {
+
+ ApiClient.installPlugin(packageName, updateClass, version).done(function () {
+
+ Dashboard.hideLoadingMsg();
+ });
+ }
+};
+
+$(document).on('pageshow', "#addPluginPage", AddPluginPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/AdvancedConfigurationPage.js b/MediaBrowser.WebDashboard/Html/scripts/AdvancedConfigurationPage.js
new file mode 100644
index 0000000000..5c6f282fb0
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/AdvancedConfigurationPage.js
@@ -0,0 +1,63 @@
+var AdvancedConfigurationPage = {
+
+ onPageShow: function () {
+ Dashboard.showLoadingMsg();
+
+ var promise1 = ApiClient.getServerConfiguration();
+
+ var promise2 = ApiClient.getSystemInfo();
+
+ $.when(promise1, promise2).done(function (response1, response2) {
+
+ AdvancedConfigurationPage.loadPage(response1[0], response2[0]);
+
+ });
+ },
+
+ loadPage: function (config, systemInfo) {
+
+ var page = $.mobile.activePage;
+
+ if (systemInfo.SupportsNativeWebSocket) {
+
+ $('#fldWebSocketPortNumber', page).hide();
+ } else {
+ $('#fldWebSocketPortNumber', page).show();
+ }
+
+ $('#txtWebSocketPortNumber', page).val(config.LegacyWebSocketPortNumber);
+
+ $('#txtPortNumber', page).val(config.HttpServerPortNumber);
+ $('#chkDebugLog', page).checked(config.EnableDebugLevelLogging).checkboxradio("refresh");
+
+ $('#chkEnableDeveloperTools', page).checked(config.EnableDeveloperTools).checkboxradio("refresh");
+ $('#chkRunAtStartup', page).checked(config.RunAtStartup).checkboxradio("refresh");
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ onSubmit: function () {
+
+ Dashboard.showLoadingMsg();
+
+ var form = this;
+
+ ApiClient.getServerConfiguration().done(function (config) {
+
+ config.LegacyWebSocketPortNumber = $('#txtWebSocketPortNumber', form).val();
+
+ config.HttpServerPortNumber = $('#txtPortNumber', form).val();
+ config.EnableDebugLevelLogging = $('#chkDebugLog', form).checked();
+
+ config.EnableDeveloperTools = $('#chkEnableDeveloperTools', form).checked();
+ config.RunAtStartup = $('#chkRunAtStartup', form).checked();
+
+ ApiClient.updateServerConfiguration(config).done(Dashboard.processServerConfigurationUpdateResult);
+ });
+
+ // Disable default form submission
+ return false;
+ }
+};
+
+$(document).on('pageshow', "#advancedConfigurationPage", AdvancedConfigurationPage.onPageShow);
diff --git a/MediaBrowser.WebDashboard/Html/scripts/AdvancedMetadataConfigurationPage.js b/MediaBrowser.WebDashboard/Html/scripts/AdvancedMetadataConfigurationPage.js
new file mode 100644
index 0000000000..73ed3f4311
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/AdvancedMetadataConfigurationPage.js
@@ -0,0 +1,65 @@
+var AdvancedMetadataConfigurationPage = {
+
+ onPageShow: function () {
+
+ Dashboard.showLoadingMsg();
+
+ var page = this;
+
+ var promise1 = ApiClient.getServerConfiguration();
+ var promise2 = ApiClient.getItemTypes({ HasInternetProvider: true });
+
+ $.when(promise1, promise2).done(function (response1, response2) {
+
+ AdvancedMetadataConfigurationPage.load(page, response1[0], response2[0]);
+
+ });
+ },
+
+ load: function (page, config, itemTypes) {
+
+ AdvancedMetadataConfigurationPage.loadItemTypes(page, config, itemTypes);
+ Dashboard.hideLoadingMsg();
+ },
+
+ loadItemTypes: function (page, configuration, types) {
+
+ var html = '<div data-role="controlgroup">';
+
+ for (var i = 0, length = types.length; i < length; i++) {
+
+ var type = types[i];
+ var id = "checkbox-" + i + "a";
+
+ var checkedAttribute = configuration.InternetProviderExcludeTypes.indexOf(type) != -1 ? ' checked="checked"' : '';
+
+ html += '<input' + checkedAttribute + ' class="chkItemType" data-itemtype="' + type + '" type="checkbox" name="' + id + '" id="' + id + '" />';
+ html += '<label for="' + id + '">' + type + '</label>';
+ }
+
+ html += "</div>";
+
+ $('#divItemTypes', page).html(html).trigger("create");
+ },
+
+ onSubmit: function () {
+ Dashboard.showLoadingMsg();
+
+ var form = this;
+
+ ApiClient.getServerConfiguration().done(function (config) {
+
+ config.InternetProviderExcludeTypes = $.map($('.chkItemType:checked', form), function (currentCheckbox) {
+
+ return currentCheckbox.getAttribute('data-itemtype');
+ });
+
+ ApiClient.updateServerConfiguration(config).done(Dashboard.processServerConfigurationUpdateResult);
+ });
+
+ // Disable default form submission
+ return false;
+ }
+};
+
+$(document).on('pageshow', "#advancedMetadataConfigurationPage", AdvancedMetadataConfigurationPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/DashboardPage.js b/MediaBrowser.WebDashboard/Html/scripts/DashboardPage.js
new file mode 100644
index 0000000000..93163de79d
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/DashboardPage.js
@@ -0,0 +1,423 @@
+var DashboardPage = {
+
+ onPageShow: function () {
+
+ Dashboard.showLoadingMsg();
+ DashboardPage.pollForInfo();
+ DashboardPage.startInterval();
+ $(document).on("websocketmessage", DashboardPage.onWebSocketMessage).on("websocketopen", DashboardPage.onWebSocketConnectionChange).on("websocketerror", DashboardPage.onWebSocketConnectionChange).on("websocketclose", DashboardPage.onWebSocketConnectionChange);
+
+ DashboardPage.lastAppUpdateCheck = null;
+ DashboardPage.lastPluginUpdateCheck = null;
+ },
+
+ onPageHide: function () {
+
+ $(document).off("websocketmessage", DashboardPage.onWebSocketMessage).off("websocketopen", DashboardPage.onWebSocketConnectionChange).off("websocketerror", DashboardPage.onWebSocketConnectionChange).off("websocketclose", DashboardPage.onWebSocketConnectionChange);
+ DashboardPage.stopInterval();
+ },
+
+ startInterval: function () {
+
+ if (Dashboard.isWebSocketOpen()) {
+ Dashboard.sendWebSocketMessage("DashboardInfoStart", "0,1500");
+ }
+ },
+
+ stopInterval: function () {
+
+ if (Dashboard.isWebSocketOpen()) {
+ Dashboard.sendWebSocketMessage("DashboardInfoStop");
+ }
+ },
+
+ onWebSocketMessage: function (e, msg) {
+
+ if (msg.MessageType == "DashboardInfo") {
+ DashboardPage.renderInfo(msg.Data);
+ }
+ },
+
+ onWebSocketConnectionChange: function () {
+
+ DashboardPage.stopInterval();
+ DashboardPage.startInterval();
+ },
+
+ pollForInfo: function () {
+ $.getJSON("dashboardInfo").done(DashboardPage.renderInfo);
+ },
+
+ renderInfo: function (dashboardInfo) {
+
+ DashboardPage.lastDashboardInfo = dashboardInfo;
+
+ DashboardPage.renderRunningTasks(dashboardInfo);
+ DashboardPage.renderSystemInfo(dashboardInfo);
+ DashboardPage.renderActiveConnections(dashboardInfo);
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ renderActiveConnections: function (dashboardInfo) {
+
+ var page = $.mobile.activePage;
+
+ var html = '';
+
+ if (!dashboardInfo.ActiveConnections.length) {
+ html += '<p>There are no users currently connected.</p>';
+ $('#divConnections', page).html(html).trigger('create');
+ return;
+ }
+
+ html += '<table class="tblConnections" style="border-collapse:collapse;">';
+
+ for (var i = 0, length = dashboardInfo.ActiveConnections.length; i < length; i++) {
+
+ var connection = dashboardInfo.ActiveConnections[i];
+
+ var user = dashboardInfo.Users.filter(function (u) {
+ return u.Id == connection.UserId;
+ })[0];
+
+ html += '<tr>';
+
+ html += '<td style="text-align:center;">';
+ html += DashboardPage.getClientType(connection);
+ html += '</td>';
+
+ html += '<td>';
+ html += user.Name;
+ html += '</td>';
+
+ html += '<td>';
+ html += connection.DeviceName;
+ html += '</td>';
+
+ html += '<td>';
+ html += DashboardPage.getNowPlayingImage(connection.NowPlayingItem);
+ html += '</td>';
+
+ html += '<td>';
+ html += DashboardPage.getNowPlayingText(connection, connection.NowPlayingItem);
+ html += '</td>';
+
+ html += '</tr>';
+
+ }
+
+ html += '</table>';
+
+ $('#divConnections', page).html(html);
+ },
+
+ getClientType: function (connection) {
+
+ if (connection.ClientType == "Dashboard") {
+
+ return "<img src='css/images/clients/html5.png' alt='Dashboard' title='Dashboard' />";
+ }
+ if (connection.ClientType == "Pc") {
+
+ return "<img src='css/images/clients/mb.png' alt='Media Browser' title='Media Browser' />";
+ }
+ if (connection.ClientType == "Android") {
+
+ return "<img src='css/images/clients/android.png' alt='Android' title='Android' />";
+ }
+ if (connection.ClientType == "Ios") {
+
+ return "<img src='css/images/clients/ios.png' alt='iOS' title='iOS' />";
+ }
+ if (connection.ClientType == "WindowsRT") {
+
+ return "<img src='css/images/clients/windowsrt.png' alt='Windows RT' title='Windows RT' />";
+ }
+ if (connection.ClientType == "WindowsPhone") {
+
+ return "<img src='css/images/clients/windowsphone.png' alt='Windows Phone' title='Windows Phone' />";
+ }
+
+ return connection.ClientType;
+ },
+
+ getNowPlayingImage: function (item) {
+
+ if (item) {
+
+ if (item.BackdropImageTag) {
+ var url = ApiClient.getImageUrl(item.Id, {
+ type: "Backdrop",
+ height: 100,
+ tag: item.BackdropImageTag
+ });
+
+ return "<img class='clientNowPlayingImage' src='" + url + "' alt='" + item.Name + "' title='" + item.Name + "' />";
+ }
+ else if (item.PrimaryImageTag) {
+
+ var url = ApiClient.getImageUrl(item.Id, {
+ type: "Primary",
+ height: 100,
+ tag: item.PrimaryImageTag
+ });
+
+ return "<img class='clientNowPlayingImage' src='" + url + "' alt='" + item.Name + "' title='" + item.Name + "' />";
+ }
+ }
+
+ return "";
+ },
+
+ getNowPlayingText: function (connection, item) {
+
+ var html = "";
+
+ if (item) {
+
+ html += "<div>" + item.Name + "</div>";
+
+ html += "<div>";
+
+ if (item.RunTimeTicks) {
+ html += DashboardPage.getDisplayText(connection.NowPlayingPositionTicks || 0) + " / ";
+
+ html += DashboardPage.getDisplayText(item.RunTimeTicks);
+ }
+
+ html += "</div>";
+ }
+
+ return html;
+ },
+
+ getDisplayText: function (ticks) {
+
+ var ticksPerHour = 36000000000;
+
+ var parts = [];
+
+ var hours = ticks / ticksPerHour;
+ hours = parseInt(hours);
+
+ if (hours) {
+ parts.push(hours);
+ }
+
+ ticks -= (hours * ticksPerHour);
+
+ var ticksPerMinute = 600000000;
+
+ var minutes = ticks / ticksPerMinute;
+ minutes = parseInt(minutes);
+
+ ticks -= (minutes * ticksPerMinute);
+
+ if (minutes < 10) {
+ minutes = '0' + minutes;
+ }
+ parts.push(minutes);
+
+ var ticksPerSecond = 10000000;
+
+ var seconds = ticks / ticksPerSecond;
+ seconds = parseInt(seconds);
+
+ if (seconds < 10) {
+ seconds = '0' + seconds;
+ }
+ parts.push(seconds);
+
+ return parts.join(':');
+ },
+
+ renderRunningTasks: function (dashboardInfo) {
+
+ var page = $.mobile.activePage;
+
+ var html = '';
+
+ if (!dashboardInfo.RunningTasks.length) {
+ html += '<p>No tasks are currently running.</p>';
+ }
+
+ for (var i = 0, length = dashboardInfo.RunningTasks.length; i < length; i++) {
+
+
+ var task = dashboardInfo.RunningTasks[i];
+
+ html += '<p>';
+
+ html += task.Name;
+
+ if (task.State == "Running") {
+ var progress = task.CurrentProgress || { PercentComplete: 0 };
+ html += '<span style="color:#267F00;margin-right:5px;font-weight:bold;"> - ' + Math.round(progress.PercentComplete) + '%</span>';
+
+ html += '<button type="button" data-icon="stop" data-iconpos="notext" data-inline="true" data-theme="b" data-mini="true" onclick="DashboardPage.stopTask(\'' + task.Id + '\');">Stop</button>';
+ }
+ else if (task.State == "Cancelling") {
+ html += '<span style="color:#cc0000;"> - Stopping</span>';
+ }
+
+ html += '</p>';
+ }
+
+
+ $('#divRunningTasks', page).html(html).trigger('create');
+ },
+
+ renderSystemInfo: function (dashboardInfo) {
+
+ Dashboard.updateSystemInfo(dashboardInfo.SystemInfo);
+
+ var page = $.mobile.activePage;
+
+ $('#appVersionNumber', page).html(dashboardInfo.SystemInfo.Version);
+
+ if (dashboardInfo.RunningTasks.filter(function (task) {
+
+ return task.Id == dashboardInfo.ApplicationUpdateTaskId;
+
+ }).length) {
+
+ $('#btnUpdateApplication', page).button('disable');
+ } else {
+ $('#btnUpdateApplication', page).button('enable');
+ }
+
+ DashboardPage.renderApplicationUpdateInfo(dashboardInfo);
+ DashboardPage.renderPluginUpdateInfo(dashboardInfo);
+ },
+
+ renderApplicationUpdateInfo: function (dashboardInfo) {
+
+ var page = $.mobile.activePage;
+
+ if (dashboardInfo.SystemInfo.IsNetworkDeployed && !dashboardInfo.SystemInfo.HasPendingRestart) {
+
+ // Only check once every 10 mins
+ if (DashboardPage.lastAppUpdateCheck && (new Date().getTime() - DashboardPage.lastAppUpdateCheck) < 600000) {
+ return;
+ }
+
+ DashboardPage.lastAppUpdateCheck = new Date().getTime();
+
+ ApiClient.getAvailableApplicationUpdate().done(function (packageInfo) {
+
+ var version = packageInfo.versions[0];
+
+ if (!version) {
+ $('#pUpToDate', page).show();
+ $('#pUpdateNow', page).hide();
+ } else {
+ $('#pUpToDate', page).hide();
+
+ $('#pUpdateNow', page).show();
+
+ $('#newVersionNumber', page).html("Version " + version.versionStr + " is now available for download.");
+ }
+
+ }).fail(function () {
+
+ Dashboard.showFooterNotification({ html: '<img src="css/images/notifications/error.png" class="notificationIcon" />There was an error connecting to the remote Media Browser repository.', id: "MB3ConnectionError" });
+
+ });
+
+ } else {
+
+ if (dashboardInfo.SystemInfo.HasPendingRestart) {
+ $('#pUpToDate', page).hide();
+ } else {
+ $('#pUpToDate', page).show();
+ }
+
+ $('#pUpdateNow', page).hide();
+ }
+ },
+
+ renderPluginUpdateInfo: function (dashboardInfo) {
+
+ // Only check once every 10 mins
+ if (DashboardPage.lastPluginUpdateCheck && (new Date().getTime() - DashboardPage.lastPluginUpdateCheck) < 600000) {
+ return;
+ }
+
+ DashboardPage.lastPluginUpdateCheck = new Date().getTime();
+
+ var page = $.mobile.activePage;
+
+ ApiClient.getAvailablePluginUpdates().done(function (updates) {
+
+ if (updates.length) {
+
+ $('#collapsiblePluginUpdates', page).show();
+
+ } else {
+ $('#collapsiblePluginUpdates', page).hide();
+
+ return;
+ }
+ var html = '';
+
+ for (var i = 0, length = updates.length; i < length; i++) {
+
+ var update = updates[i];
+
+ html += '<p><strong>A new version of ' + update.name + ' is available!</strong></p>';
+
+ html += '<button type="button" data-icon="download" data-theme="b" onclick="DashboardPage.installPluginUpdate(this);" data-name="' + update.name + '" data-version="' + update.versionStr + '" data-classification="' + update.classification + '">Update Now</button>';
+ }
+
+ $('#pPluginUpdates', page).html(html).trigger('create');
+
+ }).fail(function () {
+
+ Dashboard.showFooterNotification({ html: '<img src="css/images/notifications/error.png" class="notificationIcon" />There was an error connecting to the remote Media Browser repository.', id: "MB3ConnectionError" });
+
+ });
+ },
+
+ installPluginUpdate: function (button) {
+
+ $(button).button('disable');
+
+ var name = button.getAttribute('data-name');
+ var version = button.getAttribute('data-version');
+ var classification = button.getAttribute('data-classification');
+
+ Dashboard.showLoadingMsg();
+
+ ApiClient.installPlugin(name, classification, version).done(function () {
+
+ Dashboard.hideLoadingMsg();
+ });
+ },
+
+ updateApplication: function () {
+
+ var page = $.mobile.activePage;
+ $('#btnUpdateApplication', page).button('disable');
+
+ Dashboard.showLoadingMsg();
+
+ ApiClient.startScheduledTask(DashboardPage.lastDashboardInfo.ApplicationUpdateTaskId).done(function () {
+
+ DashboardPage.pollForInfo();
+
+ Dashboard.hideLoadingMsg();
+ });
+ },
+
+ stopTask: function (id) {
+
+ ApiClient.stopScheduledTask(id).done(function () {
+
+ DashboardPage.pollForInfo();
+ });
+
+ }
+};
+
+$(document).on('pageshow', "#dashboardPage", DashboardPage.onPageShow).on('pagehide', "#dashboardPage", DashboardPage.onPageHide); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/DisplaySettingsPage.js b/MediaBrowser.WebDashboard/Html/scripts/DisplaySettingsPage.js
new file mode 100644
index 0000000000..da87a106f7
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/DisplaySettingsPage.js
@@ -0,0 +1,46 @@
+var DisplaySettingsPage = {
+
+ onPageShow: function () {
+ Dashboard.showLoadingMsg();
+
+ var page = this;
+
+ ApiClient.getServerConfiguration().done(function (config) {
+
+ $('#txtWeatherLocation', page).val(config.WeatherLocation);
+ $('#txtMinResumePct', page).val(config.MinResumePct);
+ $('#txtMaxResumePct', page).val(config.MaxResumePct);
+ $('#txtMinResumeDuration', page).val(config.MinResumeDurationSeconds);
+ $('#selectWeatherUnit', page).val(config.WeatherUnit).selectmenu("refresh");
+
+ Dashboard.hideLoadingMsg();
+ });
+
+ },
+
+ submit: function() {
+
+ $('.btnSubmit', $.mobile.activePage)[0].click();
+
+ },
+
+ onSubmit: function () {
+ var form = this;
+
+ ApiClient.getServerConfiguration().done(function (config) {
+
+ config.WeatherLocation = $('#txtWeatherLocation', form).val();
+ config.WeatherUnit = $('#selectWeatherUnit', form).val();
+ config.MinResumePct = $('#txtMinResumePct', form).val();
+ config.MaxResumePct = $('#txtMaxResumePct', form).val();
+ config.MinResumeDurationSeconds = $('#txtMinResumeDuration', form).val();
+
+ ApiClient.updateServerConfiguration(config);
+ });
+
+ // Disable default form submission
+ return false;
+ }
+};
+
+$(document).on('pageshow', "#displaySettingsPage", DisplaySettingsPage.onPageShow);
diff --git a/MediaBrowser.WebDashboard/Html/scripts/EditUserPage.js b/MediaBrowser.WebDashboard/Html/scripts/EditUserPage.js
new file mode 100644
index 0000000000..0d362e5dea
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/EditUserPage.js
@@ -0,0 +1,175 @@
+var EditUserPage = {
+
+ onPageShow: function () {
+ Dashboard.showLoadingMsg();
+
+ var userId = getParameterByName("userId");
+
+ if (userId) {
+ $('#userProfileNavigation', this).show();
+ } else {
+ $('#userProfileNavigation', this).hide();
+ }
+
+ var promise4 = ApiClient.getCultures();
+
+ var promise3 = ApiClient.getParentalRatings();
+
+ var promise1;
+
+ if (!userId) {
+
+ var deferred = $.Deferred();
+
+ deferred.resolveWith(null, [{
+ Configuration: {}
+ }]);
+
+ promise1 = deferred.promise();
+ } else {
+
+ promise1 = ApiClient.getUser(userId);
+ }
+
+ var promise2 = Dashboard.getCurrentUser();
+
+ $.when(promise1, promise2, promise3, promise4).done(function (response1, response2, response3, response4) {
+
+ EditUserPage.loadUser(response1[0] || response1, response2[0], response3[0], response4[0]);
+
+ });
+ },
+
+ loadUser: function (user, loggedInUser, allParentalRatings, allCultures) {
+
+ var page = $($.mobile.activePage);
+
+ EditUserPage.populateLanguages($('#selectAudioLanguage', page), allCultures);
+ EditUserPage.populateLanguages($('#selectSubtitleLanguage', page), allCultures);
+ EditUserPage.populateRatings(allParentalRatings, page);
+
+ if (!loggedInUser.Configuration.IsAdministrator || user.Id == loggedInUser.Id) {
+
+ $('#fldIsAdmin', page).hide();
+ $('#fldMaxParentalRating', page).hide();
+ } else {
+ $('#fldIsAdmin', page).show();
+ $('#fldMaxParentalRating', page).show();
+ }
+
+ Dashboard.setPageTitle(user.Name || "Add User");
+
+ $('#txtUserName', page).val(user.Name);
+
+ var ratingValue = "";
+
+ if (user.Configuration.MaxParentalRating) {
+
+ for (var i = 0, length = allParentalRatings.length; i < length; i++) {
+
+ var rating = allParentalRatings[i];
+
+ if (user.Configuration.MaxParentalRating >= rating.Value) {
+ ratingValue = rating.Value;
+ }
+ }
+ }
+
+ $('#selectMaxParentalRating', page).val(ratingValue).selectmenu("refresh");
+
+ $('#selectAudioLanguage', page).val(user.Configuration.AudioLanguagePreference || "").selectmenu("refresh");
+ $('#selectSubtitleLanguage', page).val(user.Configuration.SubtitleLanguagePreference || "").selectmenu("refresh");
+
+ $('#chkForcedSubtitlesOnly', page).checked(user.Configuration.UseForcedSubtitlesOnly || false).checkboxradio("refresh");
+ $('#chkIsAdmin', page).checked(user.Configuration.IsAdministrator || false).checkboxradio("refresh");
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ populateLanguages: function (select, allCultures) {
+
+ var html = "";
+
+ html += "<option value=''>None</option>";
+
+ for (var i = 0, length = allCultures.length; i < length; i++) {
+
+ var culture = allCultures[i];
+
+ html += "<option value='" + culture.ThreeLetterISOLanguageName + "'>" + culture.DisplayName + "</option>";
+ }
+
+ select.html(html).selectmenu("refresh");
+ },
+
+ populateRatings: function (allParentalRatings, page) {
+
+ var html = "";
+
+ html += "<option value=''>None</option>";
+
+ for (var i = 0, length = allParentalRatings.length; i < length; i++) {
+
+ var rating = allParentalRatings[i];
+
+ html += "<option value='" + rating.Value + "'>" + rating.Name + "</option>";
+ }
+
+ $('#selectMaxParentalRating', page).html(html).selectmenu("refresh");
+ },
+
+ saveUser: function (user) {
+
+ var page = $($.mobile.activePage);
+
+ user.Name = $('#txtUserName', page).val();
+ user.Configuration.MaxParentalRating = $('#selectMaxParentalRating', page).val() || null;
+
+ user.Configuration.IsAdministrator = $('#chkIsAdmin', page).checked();
+
+ user.Configuration.AudioLanguagePreference = $('#selectAudioLanguage', page).val();
+ user.Configuration.SubtitleLanguagePreference = $('#selectSubtitleLanguage', page).val();
+ user.Configuration.UseForcedSubtitlesOnly = $('#chkForcedSubtitlesOnly', page).checked();
+
+ var userId = getParameterByName("userId");
+
+ if (userId) {
+ ApiClient.updateUser(user).done(EditUserPage.saveComplete);
+ } else {
+ ApiClient.createUser(user).done(EditUserPage.saveComplete);
+ }
+ },
+
+ saveComplete: function () {
+ Dashboard.hideLoadingMsg();
+
+ var userId = getParameterByName("userId");
+
+ Dashboard.validateCurrentUser();
+
+ if (userId) {
+ Dashboard.alert("Settings saved.");
+ } else {
+ Dashboard.navigate("userProfiles.html");
+ }
+ },
+
+ onSubmit: function () {
+ Dashboard.showLoadingMsg();
+
+ var userId = getParameterByName("userId");
+
+ if (!userId) {
+ EditUserPage.saveUser({
+ Configuration: {}
+ });
+ } else {
+ ApiClient.getUser(userId).done(EditUserPage.saveUser);
+ }
+
+ // Disable default form submission
+ return false;
+ }
+};
+
+$(document).on('pageshow', "#editUserPage", EditUserPage.onPageShow);
diff --git a/MediaBrowser.WebDashboard/Html/scripts/Extensions.js b/MediaBrowser.WebDashboard/Html/scripts/Extensions.js
new file mode 100644
index 0000000000..dba43a7046
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/Extensions.js
@@ -0,0 +1,333 @@
+// Array Remove - By John Resig (MIT Licensed)
+Array.prototype.remove = function (from, to) {
+ var rest = this.slice((to || from) + 1 || this.length);
+ this.length = from < 0 ? this.length + from : from;
+ return this.push.apply(this, rest);
+};
+
+String.prototype.endsWith = function (suffix) {
+ return this.indexOf(suffix, this.length - suffix.length) !== -1;
+};
+
+$.fn.checked = function (value) {
+ if (value === true || value === false) {
+ // Set the value of the checkbox
+ return $(this).each(function () {
+ this.checked = value;
+ });
+ } else {
+ // Return check state
+ return $(this).is(':checked');
+ }
+};
+
+var WebNotifications = {
+
+ show: function (data) {
+ if (window.webkitNotifications) {
+ if (!webkitNotifications.checkPermission()) {
+ var notif = webkitNotifications.createNotification(data.icon, data.title, data.body);
+ notif.show();
+
+ if (data.timeout) {
+ setTimeout(function () {
+ notif.cancel();
+ }, data.timeout);
+ }
+
+ return notif;
+ } else {
+ webkitNotifications.requestPermission(function () {
+ return WebNotifications.show(data);
+ });
+ }
+ }
+ else if (window.Notification) {
+ if (Notification.permissionLevel() === "granted") {
+ var notif = new Notification(data.title, data);
+ notif.show();
+
+ if (data.timeout) {
+ setTimeout(function () {
+ notif.cancel();
+ }, data.timeout);
+ }
+
+ return notif;
+ } else if (Notification.permissionLevel() === "default") {
+ Notification.requestPermission(function () {
+ return WebNotifications.show(data);
+ });
+ }
+ }
+ },
+
+ requestPermission: function () {
+ if (window.webkitNotifications) {
+ if (!webkitNotifications.checkPermission()) {
+ } else {
+ webkitNotifications.requestPermission(function () {
+ });
+ }
+ }
+ else if (window.Notification) {
+ if (Notification.permissionLevel() === "granted") {
+ } else if (Notification.permissionLevel() === "default") {
+ Notification.requestPermission(function () {
+ });
+ }
+ }
+ }
+};
+
+/*
+ * Javascript Humane Dates
+ * Copyright (c) 2008 Dean Landolt (deanlandolt.com)
+ * Re-write by Zach Leatherman (zachleat.com)
+ *
+ * Adopted from the John Resig's pretty.js
+ * at http://ejohn.org/blog/javascript-pretty-date
+ * and henrah's proposed modification
+ * at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
+ *
+ * Licensed under the MIT license.
+ */
+
+function humane_date(date_str) {
+ var time_formats = [[90, 'a minute'], // 60*1.5
+ [3600, 'minutes', 60], // 60*60, 60
+ [5400, 'an hour'], // 60*60*1.5
+ [86400, 'hours', 3600], // 60*60*24, 60*60
+ [129600, 'a day'], // 60*60*24*1.5
+ [604800, 'days', 86400], // 60*60*24*7, 60*60*24
+ [907200, 'a week'], // 60*60*24*7*1.5
+ [2628000, 'weeks', 604800], // 60*60*24*(365/12), 60*60*24*7
+ [3942000, 'a month'], // 60*60*24*(365/12)*1.5
+ [31536000, 'months', 2628000], // 60*60*24*365, 60*60*24*(365/12)
+ [47304000, 'a year'], // 60*60*24*365*1.5
+ [3153600000, 'years', 31536000] // 60*60*24*365*100, 60*60*24*365
+ ];
+
+ var dt = new Date;
+ var date = parseISO8601Date(date_str, true);
+
+ var seconds = ((dt - date) / 1000);
+ var token = ' ago';
+ var i = 0;
+ var format;
+
+ if (seconds < 0) {
+ seconds = Math.abs(seconds);
+ token = '';
+ }
+
+ while (format = time_formats[i++]) {
+ if (seconds < format[0]) {
+ if (format.length == 2) {
+ return format[1] + token;
+ } else {
+ return Math.round(seconds / format[2]) + ' ' + format[1] + token;
+ }
+ }
+ }
+
+ // overflow for centuries
+ if (seconds > 4730400000)
+ return Math.round(seconds / 4730400000) + ' centuries' + token;
+
+ return date_str;
+};
+
+function humane_elapsed(firstDateStr, secondDateStr) {
+ var dt1 = new Date(firstDateStr);
+ var dt2 = new Date(secondDateStr);
+ var seconds = (dt2.getTime() - dt1.getTime()) / 1000;
+ var numdays = Math.floor((seconds % 31536000) / 86400);
+ var numhours = Math.floor(((seconds % 31536000) % 86400) / 3600);
+ var numminutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60);
+ var numseconds = Math.round((((seconds % 31536000) % 86400) % 3600) % 60);
+
+ var elapsedStr = '';
+ elapsedStr += numdays == 1 ? numdays + ' day ' : '';
+ elapsedStr += numdays > 1 ? numdays + ' days ' : '';
+ elapsedStr += numhours == 1 ? numhours + ' hour ' : '';
+ elapsedStr += numhours > 1 ? numhours + ' hours ' : '';
+ elapsedStr += numminutes == 1 ? numminutes + ' minute ' : '';
+ elapsedStr += numminutes > 1 ? numminutes + ' minutes ' : '';
+ elapsedStr += elapsedStr.length > 0 ? 'and ' : '';
+ elapsedStr += numseconds == 1 ? numseconds + ' second' : '';
+ elapsedStr += numseconds == 0 || numseconds > 1 ? numseconds + ' seconds' : '';
+
+ return elapsedStr;
+
+}
+
+function getParameterByName(name) {
+ name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
+ var regexS = "[\\?&]" + name + "=([^&#]*)";
+ var regex = new RegExp(regexS);
+ var results = regex.exec(window.location.search);
+ if (results == null)
+ return "";
+ else
+ return decodeURIComponent(results[1].replace(/\+/g, " "));
+}
+
+function parseISO8601Date(s, toLocal) {
+
+ // parenthese matches:
+ // year month day hours minutes seconds
+ // dotmilliseconds
+ // tzstring plusminus hours minutes
+ var re = /(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(\.\d+)?(Z|([+-])(\d\d):(\d\d))/;
+
+ var d = [];
+ d = s.match(re);
+
+ // "2010-12-07T11:00:00.000-09:00" parses to:
+ // ["2010-12-07T11:00:00.000-09:00", "2010", "12", "07", "11",
+ // "00", "00", ".000", "-09:00", "-", "09", "00"]
+ // "2010-12-07T11:00:00.000Z" parses to:
+ // ["2010-12-07T11:00:00.000Z", "2010", "12", "07", "11",
+ // "00", "00", ".000", "Z", undefined, undefined, undefined]
+
+ if (!d) {
+ throw "Couldn't parse ISO 8601 date string '" + s + "'";
+ }
+
+ // parse strings, leading zeros into proper ints
+ var a = [1, 2, 3, 4, 5, 6, 10, 11];
+ for (var i in a) {
+ d[a[i]] = parseInt(d[a[i]], 10);
+ }
+ d[7] = parseFloat(d[7]);
+
+ // Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
+ // note that month is 0-11, not 1-12
+ // see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/UTC
+ var ms = Date.UTC(d[1], d[2] - 1, d[3], d[4], d[5], d[6]);
+
+ // if there are milliseconds, add them
+ if (d[7] > 0) {
+ ms += Math.round(d[7] * 1000);
+ }
+
+ // if there's a timezone, calculate it
+ if (d[8] != "Z" && d[10]) {
+ var offset = d[10] * 60 * 60 * 1000;
+ if (d[11]) {
+ offset += d[11] * 60 * 1000;
+ }
+ if (d[9] == "-") {
+ ms -= offset;
+ } else {
+ ms += offset;
+ }
+ } else if (!toLocal) {
+ ms += new Date().getTimezoneOffset() * 60000;
+ }
+
+ return new Date(ms);
+};
+
+
+
+// jqm.page.params.js - version 0.1
+// Copyright (c) 2011, Kin Blas
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of the <organization> nor the
+// names of its contributors may be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+(function ($, window, undefined) {
+
+ // Given a query string, convert all the name/value pairs
+ // into a property/value object. If a name appears more than
+ // once in a query string, the value is automatically turned
+ // into an array.
+ function queryStringToObject(qstr) {
+ var result = {}, nvPairs = ((qstr || "").replace(/^\?/, "").split(/&/)), i, pair, n, v;
+
+ for (i = 0; i < nvPairs.length; i++) {
+ var pstr = nvPairs[i];
+ if (pstr) {
+ pair = pstr.split(/=/);
+ n = pair[0];
+ v = pair[1];
+ if (result[n] === undefined) {
+ result[n] = v;
+ } else {
+ if (typeof result[n] !== "object") {
+ result[n] = [result[n]];
+ }
+ result[n].push(v);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ // The idea here is to listen for any pagebeforechange notifications from
+ // jQuery Mobile, and then muck with the toPage and options so that query
+ // params can be passed to embedded/internal pages. So for example, if a
+ // changePage() request for a URL like:
+ //
+ // http://mycompany.com/myapp/#page-1?foo=1&bar=2
+ //
+ // is made, the page that will actually get shown is:
+ //
+ // http://mycompany.com/myapp/#page-1
+ //
+ // The browser's location will still be updated to show the original URL.
+ // The query params for the embedded page are also added as a property/value
+ // object on the options object. You can access it from your page notifications
+ // via data.options.pageData.
+ $(document).bind("pagebeforechange", function (e, data) {
+
+ // We only want to handle the case where we are being asked
+ // to go to a page by URL, and only if that URL is referring
+ // to an internal page by id.
+
+ if (typeof data.toPage === "string") {
+ var u = $.mobile.path.parseUrl(data.toPage);
+ if ($.mobile.path.isEmbeddedPage(u)) {
+
+ // The request is for an internal page, if the hash
+ // contains query (search) params, strip them off the
+ // toPage URL and then set options.dataUrl appropriately
+ // so the location.hash shows the originally requested URL
+ // that hash the query params in the hash.
+
+ var u2 = $.mobile.path.parseUrl(u.hash.replace(/^#/, ""));
+ if (u2.search) {
+ if (!data.options.dataUrl) {
+ data.options.dataUrl = data.toPage;
+ }
+ data.options.pageData = queryStringToObject(u2.search);
+ data.toPage = u.hrefNoHash + "#" + u2.pathname;
+ }
+ }
+ }
+ });
+
+})(jQuery, window); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/IndexPage.js b/MediaBrowser.WebDashboard/Html/scripts/IndexPage.js
new file mode 100644
index 0000000000..ee84e07de9
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/IndexPage.js
@@ -0,0 +1,106 @@
+var IndexPage = {
+
+ onPageShow: function () {
+ IndexPage.loadLibrary(Dashboard.getCurrentUserId(), this);
+ },
+
+ loadLibrary: function (userId, page) {
+
+ if (!userId) {
+ return;
+ }
+
+ page = $(page);
+
+ var options = {
+
+ limit: 5,
+ sortBy: "DateCreated",
+ sortOrder: "Descending",
+ filters: "IsRecentlyAdded,IsNotFolder",
+ ImageTypes: "Primary,Backdrop,Thumb",
+ recursive: true
+ };
+
+ ApiClient.getItems(userId, options).done(function (result) {
+
+ $('#divWhatsNew', page).html(Dashboard.getPosterViewHtml({
+ items: result.Items,
+ preferBackdrop: true,
+ showTitle: true
+ }));
+
+ });
+
+ options = {
+
+ limit: 5,
+ sortBy: "DatePlayed",
+ sortOrder: "Descending",
+ filters: "IsResumable",
+ recursive: true
+ };
+
+ ApiClient.getItems(userId, options).done(function (result) {
+
+ $('#divResumableItems', page).html(Dashboard.getPosterViewHtml({
+ items: result.Items,
+ preferBackdrop: true,
+ showTitle: true
+ }));
+
+ if (result.Items.length) {
+ $('#divResumable', page).show();
+ } else {
+ $('#divResumable', page).hide();
+ }
+
+ });
+
+ options = {
+
+ sortBy: "SortName"
+ };
+
+ ApiClient.getItems(userId, options).done(function (result) {
+
+ $('#divCollections', page).html(Dashboard.getPosterViewHtml({
+ items: result.Items,
+ showTitle: true
+ }));
+
+ });
+
+ IndexPage.loadMyLibrary(userId, page);
+ },
+
+ loadMyLibrary: function (userId, page) {
+
+ var items = [{
+ Name: "Recently Played",
+ IsFolder: true
+ }, {
+ Name: "Favorites",
+ IsFolder: true
+ }, {
+ Name: "Genres",
+ IsFolder: true
+ }, {
+ Name: "Studios",
+ IsFolder: true
+ }, {
+ Name: "Performers",
+ IsFolder: true
+ }, {
+ Name: "Directors",
+ IsFolder: true
+ }];
+
+ $('#divMyLibrary', page).html(Dashboard.getPosterViewHtml({
+ items: items,
+ showTitle: true
+ }));
+ }
+};
+
+$(document).on('pageshow', "#indexPage", IndexPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/ItemDetailPage.js b/MediaBrowser.WebDashboard/Html/scripts/ItemDetailPage.js
new file mode 100644
index 0000000000..e5629f217f
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/ItemDetailPage.js
@@ -0,0 +1,353 @@
+var ItemDetailPage = {
+
+ onPageShow: function () {
+
+ var id = getParameterByName('id');
+
+ Dashboard.showLoadingMsg();
+
+ ApiClient.getItem(Dashboard.getCurrentUserId(), id).done(ItemDetailPage.renderItem);
+
+ },
+
+ renderItem: function (item) {
+
+ var page = $.mobile.activePage;
+
+ ItemDetailPage.item = item;
+
+ var name = item.Name;
+
+ if (item.IndexNumber != null) {
+ name = item.IndexNumber + " - " + name;
+ }
+
+ Dashboard.setPageTitle(name);
+
+ ItemDetailPage.renderImage(item);
+ ItemDetailPage.renderOverviewBlock(item);
+ ItemDetailPage.renderScenes(item);
+ ItemDetailPage.renderGallery(item);
+ ItemDetailPage.renderMediaInfo(item);
+
+ $('#itemName', page).html(name);
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ renderImage: function (item) {
+
+ var page = $.mobile.activePage;
+
+ var imageTags = item.ImageTags || {};
+
+ var html = '';
+
+ var url;
+ var useBackgroundColor;
+
+ if (imageTags.Primary) {
+
+ url = ApiClient.getImageUrl(item.Id, {
+ type: "Primary",
+ width: 800,
+ tag: item.ImageTags.Primary
+ });
+ }
+ else if (item.BackdropImageTags && item.BackdropImageTags.length) {
+
+ url = ApiClient.getImageUrl(item.Id, {
+ type: "Backdrop",
+ width: 800,
+ tag: item.BackdropImageTags[0]
+ });
+ }
+ else if (imageTags.Thumb) {
+
+ url = ApiClient.getImageUrl(item.Id, {
+ type: "Thumb",
+ width: 800,
+ tag: item.ImageTags.Thumb
+ });
+ }
+ else if (imageTags.Disc) {
+
+ url = ApiClient.getImageUrl(item.Id, {
+ type: "Disc",
+ width: 800,
+ tag: item.ImageTags.Disc
+ });
+ }
+ else if (item.MediaType == "Audio") {
+ url = "css/images/itemDetails/audioDefault.png";
+ useBackgroundColor = true;
+ }
+ else if (item.MediaType == "Game") {
+ url = "css/images/itemDetails/gameDefault.png";
+ useBackgroundColor = true;
+ }
+ else {
+ url = "css/images/itemDetails/videoDefault.png";
+ useBackgroundColor = true;
+ }
+
+ if (url) {
+
+ var style = useBackgroundColor ? "background-color:" + Dashboard.getRandomMetroColor() + ";" : "";
+
+ html += "<img class='itemDetailImage' src='" + url + "' style='" + style + "' />";
+ }
+
+ $('#itemImage', page).html(html);
+ },
+
+ renderOverviewBlock: function (item) {
+
+ var page = $.mobile.activePage;
+
+ if (item.Taglines && item.Taglines.length) {
+ $('#itemTagline', page).html(item.Taglines[0]).show();
+ } else {
+ $('#itemTagline', page).hide();
+ }
+
+ if (item.Overview) {
+ $('#itemOverview', page).html(item.Overview).show();
+ } else {
+ $('#itemOverview', page).hide();
+ }
+
+ if (item.CommunityRating) {
+ $('#itemCommunityRating', page).html(ItemDetailPage.getStarRating(item)).show().attr('title', item.CommunityRating);
+ } else {
+ $('#itemCommunityRating', page).hide();
+ }
+
+ if (MediaPlayer.canPlay(item)) {
+ $('#btnPlay', page).show();
+ $('#playButtonShadow', page).show();
+ } else {
+ $('#btnPlay', page).hide();
+ $('#playButtonShadow', page).hide();
+ }
+
+ var miscInfo = [];
+
+ if (item.ProductionYear) {
+ miscInfo.push(item.ProductionYear);
+ }
+
+ if (item.OfficialRating) {
+ miscInfo.push(item.OfficialRating);
+ }
+
+ if (item.RunTimeTicks) {
+
+ var minutes = item.RunTimeTicks / 600000000;
+
+ minutes = minutes || 1;
+
+ miscInfo.push(parseInt(minutes) + "min");
+ }
+
+ if (item.DisplayMediaType) {
+ miscInfo.push(item.DisplayMediaType);
+ }
+
+ if (item.VideoFormat && item.VideoFormat !== 'Standard') {
+ miscInfo.push(item.VideoFormat);
+ }
+
+ $('#itemMiscInfo', page).html(miscInfo.join('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'));
+
+ ItemDetailPage.renderGenres(item);
+ ItemDetailPage.renderStudios(item);
+ },
+
+ renderGenres: function (item) {
+
+ var page = $.mobile.activePage;
+
+ if (item.Genres && item.Genres.length) {
+ var elem = $('#itemGenres', page).show();
+
+ var html = 'Genres:&nbsp;&nbsp;';
+
+ for (var i = 0, length = item.Genres.length; i < length; i++) {
+
+ if (i > 0) {
+ html += '&nbsp;&nbsp;/&nbsp;&nbsp;';
+ }
+
+ html += '<a class="interiorLink" href="#">' + item.Genres[i] + '</a>';
+ }
+
+ elem.html(html);
+
+
+ } else {
+ $('#itemGenres', page).hide();
+ }
+ },
+
+ renderStudios: function (item) {
+
+ var page = $.mobile.activePage;
+
+ if (item.Studios && item.Studios.length) {
+ var elem = $('#itemStudios', page).show();
+
+ var html = 'Studios:&nbsp;&nbsp;';
+
+ for (var i = 0, length = item.Studios.length; i < length; i++) {
+
+ if (i > 0) {
+ html += '&nbsp;&nbsp;/&nbsp;&nbsp;';
+ }
+
+ html += '<a class="interiorLink" href="#">' + item.Studios[i] + '</a>';
+ }
+
+ elem.html(html);
+
+
+ } else {
+ $('#itemStudios', page).hide();
+ }
+ },
+
+ getStarRating: function (item) {
+ var rating = item.CommunityRating;
+
+ var html = "";
+ for (var i = 1; i <= 10; i++) {
+ if (rating < i - 1) {
+ html += "<div class='starRating emptyStarRating'></div>";
+ }
+ else if (rating < i) {
+ html += "<div class='starRating halfStarRating'></div>";
+ }
+ else {
+ html += "<div class='starRating'></div>";
+ }
+ }
+
+ return html;
+ },
+
+ renderScenes: function (item) {
+
+ var html = '';
+
+ var page = $.mobile.activePage;
+
+ if (!item.Chapters || !item.Chapters.length) {
+ $('#scenesCollapsible', page).hide();
+ $('#scenesContent', page).html(html);
+ return;
+ }
+
+ for (var i = 0, length = item.Chapters.length; i < length; i++) {
+
+ var chapter = item.Chapters[i];
+
+
+ }
+
+ $('#scenesCollapsible', page).show();
+ $('#scenesContent', page).html(html);
+ },
+
+ play: function () {
+ MediaPlayer.play([ItemDetailPage.item]);
+ },
+
+ renderGallery: function (item) {
+
+ var page = $.mobile.activePage;
+
+ var imageTags = item.ImageTags || {};
+
+ var html = '';
+
+ var downloadWidth = 400;
+
+ if (imageTags.Logo) {
+
+ html += '<img class="galleryImage" src="' + ApiClient.getImageUrl(item.Id, {
+ type: "Logo",
+ width: downloadWidth,
+ tag: item.ImageTags.Logo
+ }) + '" />';
+ }
+ if (imageTags.Thumb) {
+
+ html += '<img class="galleryImage" src="' + ApiClient.getImageUrl(item.Id, {
+ type: "Thumb",
+ width: downloadWidth,
+ tag: item.ImageTags.Thumb
+ }) + '" />';
+ }
+ if (imageTags.Art) {
+
+ html += '<img class="galleryImage" src="' + ApiClient.getImageUrl(item.Id, {
+ type: "Art",
+ width: downloadWidth,
+ tag: item.ImageTags.Art
+ }) + '" />';
+ }
+ if (imageTags.Menu) {
+
+ html += '<img class="galleryImage" src="' + ApiClient.getImageUrl(item.Id, {
+ type: "Menu",
+ width: downloadWidth,
+ tag: item.ImageTags.Menu
+ }) + '" />';
+ }
+ if (imageTags.Disc) {
+
+ html += '<img class="galleryImage" src="' + ApiClient.getImageUrl(item.Id, {
+ type: "Disc",
+ width: downloadWidth,
+ tag: item.ImageTags.Disc
+ }) + '" />';
+ }
+ if (imageTags.Box) {
+
+ html += '<img class="galleryImage" src="' + ApiClient.getImageUrl(item.Id, {
+ type: "Box",
+ width: downloadWidth,
+ tag: item.ImageTags.Box
+ }) + '" />';
+ }
+
+ if (item.BackdropImageTags) {
+
+ for (var i = 0, length = item.BackdropImageTags.length; i < length; i++) {
+ html += '<img class="galleryImage" src="' + ApiClient.getImageUrl(item.Id, {
+ type: "Backdrop",
+ width: downloadWidth,
+ tag: item.BackdropImageTags[0],
+ index: i
+ }) + '" />';
+ }
+
+ }
+
+ $('#galleryContent', page).html(html);
+ },
+
+ renderMediaInfo: function(item) {
+
+ var page = $.mobile.activePage;
+
+ if (!item.MediaStreams || !item.MediaStreams.length) {
+ $('#mediaInfoCollapsible', page).hide();
+ return;
+ }
+
+ $('#mediaInfoCollapsible', page).show();
+ }
+};
+
+$(document).on('pageshow', "#itemDetailPage", ItemDetailPage.onPageShow);
diff --git a/MediaBrowser.WebDashboard/Html/scripts/LogPage.js b/MediaBrowser.WebDashboard/Html/scripts/LogPage.js
new file mode 100644
index 0000000000..133eb34fbb
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/LogPage.js
@@ -0,0 +1,86 @@
+var LogPage = {
+
+ onPageShow: function () {
+
+ LogPage.startLine = 0;
+
+ $('#logContents', this).html('');
+
+ $(document).on("websocketmessage", LogPage.onWebSocketMessage).on("websocketopen", LogPage.onWebSocketConnectionChange).on("websocketerror", LogPage.onWebSocketConnectionChange).on("websocketclose", LogPage.onWebSocketConnectionChange);
+
+ LogPage.startInterval();
+
+ var autoScroll = localStorage.getItem("autoScrollLogPage");
+
+ if (autoScroll == "true") {
+ LogPage.updateAutoScroll(true);
+ }
+ else if (autoScroll == "false") {
+ LogPage.updateAutoScroll(false);
+ }
+ },
+
+ onPageHide: function () {
+
+ $(document).off("websocketmessage", LogPage.onWebSocketMessage).off("websocketopen", LogPage.onWebSocketConnectionChange).off("websocketerror", LogPage.onWebSocketConnectionChange).off("websocketclose", LogPage.onWebSocketConnectionChange);
+
+ LogPage.stopInterval();
+ },
+
+ startInterval: function () {
+
+ if (Dashboard.isWebSocketOpen()) {
+ Dashboard.sendWebSocketMessage("LogFileStart", "0,2000");
+ }
+ },
+
+ stopInterval: function () {
+
+ if (Dashboard.isWebSocketOpen()) {
+ Dashboard.sendWebSocketMessage("LogFileStop");
+ }
+ },
+
+ onWebSocketConnectionChange: function () {
+ LogPage.stopInterval();
+ LogPage.startInterval();
+ },
+
+ onWebSocketMessage: function (e, msg) {
+
+ if (msg.MessageType == "LogFile") {
+ LogPage.appendLines(msg.Data);
+ }
+ },
+
+ appendLines: function (lines) {
+
+ if (!lines.length) {
+ return;
+ }
+
+ LogPage.startLine += lines.length;
+
+ lines = lines.join('\n') + '\n';
+
+ var elem = $('#logContents', $.mobile.activePage).append(lines)[0];
+
+ elem.style.height = (elem.scrollHeight) + 'px';
+
+ if ($('#chkAutoScroll', $.mobile.activePage).checked()) {
+ $('html, body').animate({ scrollTop: $(document).height() }, 'slow');
+ }
+ },
+
+ updateAutoScroll: function (value) {
+
+ var page = $.mobile.activePage;
+
+ $('#chkAutoScrollBottom', page).checked(value).checkboxradio('refresh');
+ $('#chkAutoScroll', page).checked(value).checkboxradio('refresh');
+
+ localStorage.setItem("autoScrollLogPage", value.toString());
+ }
+};
+
+$(document).on('pageshow', "#logPage", LogPage.onPageShow).on('pagehide', "#logPage", LogPage.onPageHide); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/LoginPage.js b/MediaBrowser.WebDashboard/Html/scripts/LoginPage.js
new file mode 100644
index 0000000000..46e7d53cca
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/LoginPage.js
@@ -0,0 +1,112 @@
+var LoginPage = {
+
+ onPageShow: function () {
+ Dashboard.showLoadingMsg();
+
+ ApiClient.getAllUsers().done(LoginPage.loadUserList);
+ },
+
+ getLastSeenText: function (lastActivityDate) {
+
+ if (!lastActivityDate) {
+ return "";
+ }
+
+ return "Last seen " + humane_date(lastActivityDate);
+ },
+
+ getImagePath: function (user) {
+
+ if (!user.PrimaryImageTag) {
+ return "css/images/logindefault.png";
+ }
+
+ return ApiClient.getUserImageUrl(user.Id, {
+ width: 240,
+ tag: user.PrimaryImageTag,
+ type: "Primary"
+ });
+ },
+
+ authenticateUserLink: function (link) {
+
+ LoginPage.authenticateUser(link.getAttribute('data-username'), link.getAttribute('data-userid'));
+ },
+
+ authenticateUser: function (username, userId, password) {
+
+ Dashboard.showLoadingMsg();
+
+ ApiClient.authenticateUser(userId, password).done(function () {
+
+ Dashboard.setCurrentUser(userId);
+
+ window.location = "index.html?u=" + userId;
+
+ }).fail(function () {
+ Dashboard.hideLoadingMsg();
+
+ setTimeout(function () {
+ Dashboard.showError("Invalid user or password.");
+ }, 300);
+ });
+ },
+
+ loadUserList: function (users) {
+ var html = "";
+
+ for (var i = 0, length = users.length; i < length; i++) {
+ var user = users[i];
+
+ var linkId = "lnkUser" + i;
+
+ var background = Dashboard.getRandomMetroColor();
+
+ if (user.HasPassword) {
+ html += "<a id='" + linkId + "' data-userid='" + user.Id + "' data-username='" + user.Name + "' href='#popupLogin' data-rel='popup' onclick='LoginPage.authenticatingLinkId=this.id;' class='userItem'>";
+ } else {
+ html += "<a id='" + linkId + "' data-userid='" + user.Id + "' data-username='" + user.Name + "' href='#' onclick='LoginPage.authenticateUserLink(this);' class='userItem'>";
+ }
+
+ if (user.PrimaryImageTag) {
+
+ var imgUrl = ApiClient.getUserImageUrl(user.Id, {
+ width: 500,
+ tag: user.PrimaryImageTag,
+ type: "Primary"
+ });
+
+ html += '<img class="userItemImage" src="' + imgUrl + '" />';
+ } else {
+ html += '<img class="userItemImage" src="css/images/logindefault.png" style="background:' + background + ';" />';
+ }
+
+ html += '<div class="userItemContent" style="background:' + background + ';">';
+
+ html += '<div class="userItemContentInner">';
+ html += '<p class="userItemHeader">' + user.Name + '</p>';
+ html += '<p>' + LoginPage.getLastSeenText(user.LastActivityDate) + '</p>';
+ html += '</div>';
+
+ html += '</div>';
+ html += '</a>';
+ }
+
+ $('#divUsers', '#loginPage').html(html);
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ onSubmit: function () {
+ $('#popupLogin', '#loginPage').popup('close');
+
+ var link = $('#' + LoginPage.authenticatingLinkId)[0];
+
+ LoginPage.authenticateUser(link.getAttribute('data-username'), link.getAttribute('data-userid'), $('#pw', '#loginPage').val());
+
+ // Disable default form submission
+ return false;
+ }
+};
+
+$(document).on('pageshow', "#loginPage", LoginPage.onPageShow);
diff --git a/MediaBrowser.WebDashboard/Html/scripts/MediaLibraryPage.js b/MediaBrowser.WebDashboard/Html/scripts/MediaLibraryPage.js
new file mode 100644
index 0000000000..4f4d119d83
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/MediaLibraryPage.js
@@ -0,0 +1,272 @@
+var MediaLibraryPage = {
+
+ onPageShow: function () {
+
+ MediaLibraryPage.lastVirtualFolderName = "";
+
+ MediaLibraryPage.reloadLibrary();
+ },
+
+ reloadLibrary: function () {
+
+ Dashboard.showLoadingMsg();
+
+ var userId = getParameterByName("userId");
+
+ var page = $.mobile.activePage;
+
+ if (userId) {
+
+ $('#userProfileNavigation', page).show();
+
+ ApiClient.getUser(userId).done(function (user) {
+
+ Dashboard.setPageTitle(user.Name);
+
+ $('#fldUseDefaultLibrary', page).show();
+
+ $('#chkUseDefaultLibrary', page).checked(!user.Configuration.UseCustomLibrary).checkboxradio("refresh");
+
+ if (user.Configuration.UseCustomLibrary) {
+
+ ApiClient.getVirtualFolders(userId).done(MediaLibraryPage.reloadVirtualFolders);
+ $('#divMediaLibrary', page).show();
+ } else {
+ $('#divMediaLibrary', page).hide();
+ Dashboard.hideLoadingMsg();
+ }
+
+ });
+
+ } else {
+
+ $('#userProfileNavigation', page).hide();
+ ApiClient.getVirtualFolders().done(MediaLibraryPage.reloadVirtualFolders);
+
+ $('#fldUseDefaultLibrary', page).hide();
+ $('#divMediaLibrary', page).show();
+ Dashboard.setPageTitle("Media Library");
+ }
+ },
+
+ reloadVirtualFolders: function (virtualFolders) {
+
+ var page = $.mobile.activePage;
+
+ if (virtualFolders) {
+ MediaLibraryPage.virtualFolders = virtualFolders;
+ } else {
+ virtualFolders = MediaLibraryPage.virtualFolders;
+ }
+
+ var html = '';
+
+ for (var i = 0, length = virtualFolders.length; i < length; i++) {
+
+ var virtualFolder = virtualFolders[i];
+
+ var isCollapsed = MediaLibraryPage.lastVirtualFolderName != virtualFolder.Name;
+
+ html += MediaLibraryPage.getVirtualFolderHtml(virtualFolder, isCollapsed, i);
+ }
+
+ $('#divVirtualFolders', page).html(html).trigger('create');
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ getVirtualFolderHtml: function (virtualFolder, isCollapsed, index) {
+
+ isCollapsed = isCollapsed ? "true" : "false";
+ var html = '<div class="collapsibleVirtualFolder" data-role="collapsible" data-collapsed="' + isCollapsed + '" data-content-theme="c">';
+
+ html += '<h3>' + virtualFolder.Name + '</h3>';
+
+ html += '<ul class="mediaFolderLocations" data-inset="true" data-role="listview" data-split-icon="minus">';
+
+ html += '<li data-role="list-divider" class="mediaLocationsHeader">Media Locations';
+ html += '<button type="button" data-icon="plus" data-mini="true" data-theme="c" data-inline="true" data-iconpos="notext" onclick="MediaLibraryPage.addMediaLocation(' + index + ');"></button>';
+ html += '</li>';
+
+ for (var i = 0, length = virtualFolder.Locations.length; i < length; i++) {
+
+ var location = virtualFolder.Locations[i];
+ html += '<li>';
+ html += '<a class="lnkMediaLocation" href="#">' + location + '</a>';
+ html += '<a href="#" data-index="' + i + '" data-folderindex="' + index + '" onclick="MediaLibraryPage.deleteMediaLocation(this);"></a>';
+ html += '</li>';
+ }
+ html += '</ul>';
+
+ html += '<p>';
+ html += '<button type="button" data-inline="true" data-icon="minus" data-folderindex="' + index + '" onclick="MediaLibraryPage.deleteVirtualFolder(this);">Remove collection</button>';
+ html += '<button type="button" data-inline="true" data-icon="pencil" data-folderindex="' + index + '" onclick="MediaLibraryPage.renameVirtualFolder(this);">Rename collection</button>';
+ html += '</p>';
+
+ html += '</div>';
+
+ return html;
+ },
+
+ setUseDefaultMediaLibrary: function (useDefaultLibrary) {
+
+ Dashboard.showLoadingMsg();
+
+ var userId = getParameterByName("userId");
+
+ ApiClient.getUser(userId).done(function (user) {
+
+ user.Configuration.UseCustomLibrary = !useDefaultLibrary;
+
+ ApiClient.updateUser(user).done(MediaLibraryPage.reloadLibrary);
+ });
+ },
+
+ addVirtualFolder: function () {
+
+ MediaLibraryPage.getTextValue("Add Media Collection", "Name:", "", function (name) {
+
+ var userId = getParameterByName("userId");
+
+ MediaLibraryPage.lastVirtualFolderName = name;
+
+ ApiClient.addVirtualFolder(name, userId).done(MediaLibraryPage.processOperationResult);
+
+ });
+ },
+
+ addMediaLocation: function (virtualFolderIndex) {
+
+ MediaLibraryPage.selectDirectory(function (path) {
+
+ if (path) {
+
+ var virtualFolder = MediaLibraryPage.virtualFolders[virtualFolderIndex];
+
+ MediaLibraryPage.lastVirtualFolderName = virtualFolder.Name;
+
+ var userId = getParameterByName("userId");
+
+ ApiClient.addMediaPath(virtualFolder.Name, path, userId).done(MediaLibraryPage.processOperationResult);
+ }
+
+ });
+ },
+
+ selectDirectory: function (callback) {
+
+ Dashboard.selectDirectory({callback: callback});
+ },
+
+ getTextValue: function (header, label, initialValue, callback) {
+
+ var page = $.mobile.activePage;
+
+ var popup = $('#popupEnterText', page);
+
+ $('h3', popup).html(header);
+ $('label', popup).html(label);
+ $('#txtValue', popup).val(initialValue);
+
+ popup.popup("open").on("popupafterclose", function () {
+
+ $(this).off("popupafterclose").off("click");
+
+ $('#textEntryForm', this).off("submit");
+
+ });
+
+ $('#textEntryForm', popup).on('submit', function () {
+
+ if (callback) {
+ callback($('#txtValue', popup).val());
+ }
+
+ return false;
+ });
+ },
+
+ renameVirtualFolder: function (button) {
+
+ var folderIndex = button.getAttribute('data-folderindex');
+ var virtualFolder = MediaLibraryPage.virtualFolders[folderIndex];
+
+ MediaLibraryPage.lastVirtualFolderName = virtualFolder.Name;
+
+ MediaLibraryPage.getTextValue(virtualFolder.Name, "Rename " + virtualFolder.Name, virtualFolder.Name, function (newName) {
+
+ if (virtualFolder.Name != newName) {
+
+ var userId = getParameterByName("userId");
+
+ ApiClient.renameVirtualFolder(virtualFolder.Name, newName, userId).done(MediaLibraryPage.processOperationResult);
+ }
+ });
+ },
+
+ deleteVirtualFolder: function (button) {
+
+ var folderIndex = button.getAttribute('data-folderindex');
+ var virtualFolder = MediaLibraryPage.virtualFolders[folderIndex];
+
+ var parent = $(button).parents('.collapsibleVirtualFolder');
+
+ var locations = $('.lnkMediaLocation', parent).map(function () {
+ return this.innerHTML;
+ }).get();
+
+ var msg = "Are you sure you wish to remove " + virtualFolder.Name + "?";
+
+ if (locations.length) {
+ msg += "<br/><br/>The following media locations will be removed from your library:<br/><br/>";
+ msg += locations.join("<br/>");
+ }
+
+ MediaLibraryPage.lastVirtualFolderName = virtualFolder.Name;
+
+ Dashboard.confirm(msg, "Remove Media Folder", function (confirmResult) {
+
+ if (confirmResult) {
+
+ var userId = getParameterByName("userId");
+
+ ApiClient.removeVirtualFolder(virtualFolder.Name, userId).done(MediaLibraryPage.processOperationResult);
+ }
+
+ });
+ },
+
+ deleteMediaLocation: function (button) {
+
+ var folderIndex = button.getAttribute('data-folderindex');
+ var index = parseInt(button.getAttribute('data-index'));
+
+ var virtualFolder = MediaLibraryPage.virtualFolders[folderIndex];
+
+ MediaLibraryPage.lastVirtualFolderName = virtualFolder.Name;
+
+ var location = virtualFolder.Locations[index];
+
+ Dashboard.confirm("Are you sure you wish to remove " + location + "?", "Remove Media Location", function (confirmResult) {
+
+ if (confirmResult) {
+
+ var userId = getParameterByName("userId");
+
+ ApiClient.removeMediaPath(virtualFolder.Name, location, userId).done(MediaLibraryPage.processOperationResult);
+ }
+ });
+ },
+
+ processOperationResult: function (result) {
+ Dashboard.hideLoadingMsg();
+
+ var page = $.mobile.activePage;
+
+ $('#popupEnterText', page).popup("close");
+ $('#popupDirectoryPicker', page).popup("close");
+ MediaLibraryPage.reloadLibrary();
+ }
+};
+
+$(document).on('pageshow', ".mediaLibraryPage", MediaLibraryPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/MediaPlayer.js b/MediaBrowser.WebDashboard/Html/scripts/MediaPlayer.js
new file mode 100644
index 0000000000..bf3733ffdc
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/MediaPlayer.js
@@ -0,0 +1,170 @@
+var MediaPlayer = {
+
+ canPlay: function (item) {
+
+ if (item.MediaType === "Video") {
+
+ var media = document.createElement('video');
+
+ if (media.canPlayType) {
+
+ return media.canPlayType('video/mp2t').replace(/no/, '') || media.canPlayType('video/webm').replace(/no/, '') || media.canPlayType('video/ogv').replace(/no/, '');
+ }
+
+ return false;
+ }
+
+ if (item.MediaType === "Audio") {
+
+ var media = document.createElement('audio');
+
+ if (media.canPlayType) {
+ return media.canPlayType('audio/mpeg').replace(/no/, '') || media.canPlayType('audio/aac').replace(/no/, '');
+ }
+
+ return false;
+ }
+
+ return false;
+ },
+
+ play: function (items) {
+
+ if (MediaPlayer.isPlaying()) {
+ MediaPlayer.stop();
+ }
+
+ var item = items[0];
+
+ var mediaElement;
+
+ if (item.MediaType === "Video") {
+
+ mediaElement = MediaPlayer.playVideo(items);
+ }
+
+ else if (item.MediaType === "Audio") {
+
+ mediaElement = MediaPlayer.playAudio(items);
+ }
+
+ if (!mediaElement) {
+ return;
+ }
+
+ MediaPlayer.mediaElement = mediaElement;
+
+ var nowPlayingBar = $('#nowPlayingBar').show();
+
+ if (items.length > 1) {
+ $('#previousTrackButton', nowPlayingBar)[0].disabled = false;
+ $('#nextTrackButton', nowPlayingBar)[0].disabled = false;
+ } else {
+ $('#previousTrackButton', nowPlayingBar)[0].disabled = true;
+ $('#nextTrackButton', nowPlayingBar)[0].disabled = true;
+ }
+ },
+
+ playAudio: function (items) {
+ var item = items[0];
+
+ var baseParams = {
+ id: item.Id,
+ audioChannels: 2,
+ audioBitrate: 128000
+ };
+
+ var mp3Url = ApiClient.getUrl('audio.mp3', $.extend({}, baseParams, {
+ audioCodec: 'mp3'
+ }));
+
+ var aacUrl = ApiClient.getUrl('audio.aac', $.extend({}, baseParams, {
+ audioCodec: 'aac'
+ }));
+
+ var webmUrl = ApiClient.getUrl('audio.webma', $.extend({}, baseParams, {
+ audioCodec: 'Vorbis'
+ }));
+
+ var oggUrl = ApiClient.getUrl('audio.oga', $.extend({}, baseParams, {
+ audioCodec: 'Vorbis'
+ }));
+
+ var html = '';
+ html += '<audio class="itemAudio" preload="none" controls autoplay>';
+ html += '<source type="audio/mpeg" src="' + mp3Url + '">';
+ html += '<source type="audio/aac" src="' + aacUrl + '">';
+ html += '<source type="audio/webm" src="' + webmUrl + '">';
+ html += '<source type="audio/ogg" src="' + oggUrl + '">';
+ html += '</audio';
+
+ var nowPlayingBar = $('#nowPlayingBar').show();
+
+ $('#mediaElement', nowPlayingBar).html(html);
+
+ return $('audio', nowPlayingBar)[0];
+ },
+
+ playVideo: function (items) {
+
+ var item = items[0];
+
+ var screenWidth = Math.max(screen.height, screen.width);
+ var screenHeight = Math.min(screen.height, screen.width);
+
+ var baseParams = {
+ id: item.Id,
+ audioChannels: 2,
+ audioBitrate: 128000,
+ videoBitrate: 500000,
+ maxWidth: screenWidth,
+ maxHeight: screenHeight
+ };
+
+ var tsVideoUrl = ApiClient.getUrl('video.ts', $.extend({}, baseParams, {
+ videoCodec: 'h264',
+ audioCodec: 'aac'
+ }));
+
+ var webmVideoUrl = ApiClient.getUrl('video.webm', $.extend({}, baseParams, {
+ videoCodec: 'vpx',
+ audioCodec: 'Vorbis'
+ }));
+
+ var ogvVideoUrl = ApiClient.getUrl('video.ogv', $.extend({}, baseParams, {
+ videoCodec: 'theora',
+ audioCodec: 'Vorbis'
+ }));
+
+ var html = '';
+ html += '<video class="itemVideo" preload="none" controls autoplay>';
+ html += '<source type=\'video/mp2t; codecs="h264, aac"\' src="' + tsVideoUrl + '">';
+ html += '<source type=\'video/webm; codecs="vp8, vorbis"\' src="' + webmVideoUrl + '">';
+ html += '<source type=\'video/ogg; codecs="theora, vorbis"\' src="' + ogvVideoUrl + '">';
+ html += '</video';
+
+ var nowPlayingBar = $('#nowPlayingBar').show();
+
+ $('#mediaElement', nowPlayingBar).html(html);
+
+ return $('video', nowPlayingBar)[0];
+ },
+
+ stop: function () {
+
+ var elem = MediaPlayer.mediaElement;
+
+ elem.pause();
+ elem.src = "";
+
+ $(elem).remove();
+
+ $('#nowPlayingBar').hide();
+
+ MediaPlayer.mediaElement = null;
+ },
+
+ isPlaying: function() {
+ return MediaPlayer.mediaElement;
+ }
+}; \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/MetadataConfigurationPage.js b/MediaBrowser.WebDashboard/Html/scripts/MetadataConfigurationPage.js
new file mode 100644
index 0000000000..e68940b832
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/MetadataConfigurationPage.js
@@ -0,0 +1,103 @@
+var MetadataConfigurationPage = {
+
+ onPageShow: function () {
+ Dashboard.showLoadingMsg();
+
+ var page = this;
+
+ var config;
+ var allCultures;
+ var allCountries;
+
+ ApiClient.getServerConfiguration().done(function (result) {
+
+ config = result;
+ MetadataConfigurationPage.load(page, config, allCultures, allCountries);
+ });
+
+ ApiClient.getCultures().done(function (result) {
+
+ MetadataConfigurationPage.populateLanguages(result);
+
+ allCultures = result;
+ MetadataConfigurationPage.load(page, config, allCultures, allCountries);
+ });
+
+ ApiClient.getCountries().done(function (result) {
+
+ MetadataConfigurationPage.populateCountries(result);
+
+ allCountries = result;
+ MetadataConfigurationPage.load(page, config, allCultures, allCountries);
+ });
+ },
+
+ load: function (page, config, allCultures, allCountries) {
+
+ if (!config || !allCultures || !allCountries) {
+ return;
+ }
+
+ $('#chkEnableInternetProviders', page).checked(config.EnableInternetProviders).checkboxradio("refresh");
+ $('#chkSaveLocal', page).checked(config.SaveLocalMeta).checkboxradio("refresh");
+ $('#txtRefreshDays', page).val(config.MetadataRefreshDays);
+ $('#selectLanguage', page).val(config.PreferredMetadataLanguage).selectmenu("refresh");
+ $('#selectCountry', page).val(config.MetadataCountryCode).selectmenu("refresh");
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ populateCountries: function (allCountries) {
+
+ var html = "";
+
+ html += "<option value=''>None</option>";
+
+ for (var i = 0, length = allCountries.length; i < length; i++) {
+
+ var culture = allCountries[i];
+
+ html += "<option value='" + culture.TwoLetterISORegionName + "'>" + culture.DisplayName + "</option>";
+ }
+
+ $('#selectCountry', '#metadataConfigurationPage').html(html).selectmenu("refresh");
+ },
+
+ populateLanguages: function (allCultures) {
+
+ var html = "";
+
+ html += "<option value=''>None</option>";
+
+ for (var i = 0, length = allCultures.length; i < length; i++) {
+
+ var culture = allCultures[i];
+
+ html += "<option value='" + culture.TwoLetterISOLanguageName + "'>" + culture.DisplayName + "</option>";
+ }
+
+ $('#selectLanguage', '#metadataConfigurationPage').html(html).selectmenu("refresh");
+ },
+
+ onSubmit: function () {
+ Dashboard.showLoadingMsg();
+
+ var form = this;
+
+ ApiClient.getServerConfiguration().done(function (config) {
+
+ config.EnableInternetProviders = $('#chkEnableInternetProviders', form).checked();
+ config.SaveLocalMeta = $('#chkSaveLocal', form).checked();
+ config.MetadataRefreshDays = $('#txtRefreshDays', form).val();
+ config.PreferredMetadataLanguage = $('#selectLanguage', form).val();
+ config.MetadataCountryCode = $('#selectCountry', form).val();
+
+ ApiClient.updateServerConfiguration(config).done(Dashboard.processServerConfigurationUpdateResult);
+ });
+
+ // Disable default form submission
+ return false;
+ }
+};
+
+$(document).on('pageshow', "#metadataConfigurationPage", MetadataConfigurationPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/MetadataImagesPage.js b/MediaBrowser.WebDashboard/Html/scripts/MetadataImagesPage.js
new file mode 100644
index 0000000000..c9f2523f7a
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/MetadataImagesPage.js
@@ -0,0 +1,74 @@
+var MetadataImagesPage = {
+
+ onPageShow: function () {
+
+ Dashboard.showLoadingMsg();
+
+ var page = this;
+
+ ApiClient.getServerConfiguration().done(function(result) {
+ MetadataImagesPage.load(page, result);
+ });
+ },
+
+ load: function (page, config) {
+
+ $('#selectTmdbPersonImageDownloadSize', page).val(config.TmdbFetchedProfileSize).selectmenu("refresh");
+ $('#selectTmdbPosterDownloadSize', page).val(config.TmdbFetchedPosterSize).selectmenu("refresh");
+ $('#selectTmdbBackdropDownloadSize', page).val(config.TmdbFetchedBackdropSize).selectmenu("refresh");
+
+ $('#chkRefreshItemImages', page).checked(config.RefreshItemImages).checkboxradio("refresh");
+ $('#txtNumbackdrops', page).val(config.MaxBackdrops);
+
+ $('#chkDownloadMovieArt', page).checked(config.DownloadMovieArt).checkboxradio("refresh");
+ $('#chkDownloadMovieBanner', page).checked(config.DownloadMovieBanner).checkboxradio("refresh");
+ $('#chkDownloadMovieDisc', page).checked(config.DownloadMovieDisc).checkboxradio("refresh");
+ $('#chkDownloadMovieLogo', page).checked(config.DownloadMovieLogo).checkboxradio("refresh");
+ $('#chkDownloadMovieThumb', page).checked(config.DownloadMovieThumb).checkboxradio("refresh");
+ $('#chKDownloadTVArt', page).checked(config.DownloadTVArt).checkboxradio("refresh");
+ $('#chkDownloadTVBanner', page).checked(config.DownloadTVBanner).checkboxradio("refresh");
+ $('#chkDownloadTVLogo', page).checked(config.DownloadTVLogo).checkboxradio("refresh");
+ $('#chkDownloadTVThumb', page).checked(config.DownloadTVThumb).checkboxradio("refresh");
+ $('#chkDownloadSeasonBanner', page).checked(config.DownloadTVSeasonBanner).checkboxradio("refresh");
+ $('#chkDownloadSeasonThumb', page).checked(config.DownloadTVSeasonThumb).checkboxradio("refresh");
+ $('#chkDownloadSeasonBackdrops', page).checked(config.DownloadTVSeasonBackdrops).checkboxradio("refresh");
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ onSubmit: function () {
+ Dashboard.showLoadingMsg();
+
+ var form = this;
+
+ ApiClient.getServerConfiguration().done(function (config) {
+
+ config.TmdbFetchedProfileSize = $('#selectTmdbPersonImageDownloadSize', form).val();
+ config.TmdbFetchedPosterSize = $('#selectTmdbPosterDownloadSize', form).val();
+ config.TmdbFetchedBackdropSize = $('#selectTmdbBackdropDownloadSize', form).val();
+
+ config.RefreshItemImages = $('#chkRefreshItemImages', form).checked();
+ config.MaxBackdrops = $('#txtNumbackdrops', form).val();
+
+ config.DownloadMovieArt = $('#chkDownloadMovieArt', form).checked();
+ config.DownloadMovieBanner = $('#chkDownloadMovieBanner', form).checked();
+ config.DownloadMovieDisc = $('#chkDownloadMovieDisc', form).checked();
+ config.DownloadMovieLogo = $('#chkDownloadMovieLogo', form).checked();
+ config.DownloadMovieThumb = $('#chkDownloadMovieThumb', form).checked();
+ config.DownloadTVArt = $('#chKDownloadTVArt', form).checked();
+ config.DownloadTVBanner = $('#chkDownloadTVBanner', form).checked();
+ config.DownloadTVLogo = $('#chkDownloadTVLogo', form).checked();
+ config.DownloadTVThumb = $('#chkDownloadTVThumb', form).checked();
+ config.DownloadTVSeasonBanner = $('#chkDownloadSeasonBanner', form).checked();
+ config.DownloadTVSeasonThumb = $('#chkDownloadSeasonThumb', form).checked();
+ config.DownloadTVSeasonBackdrops = $('#chkDownloadSeasonBackdrops', form).checked();
+
+ ApiClient.updateServerConfiguration(config).done(Dashboard.processServerConfigurationUpdateResult);
+ });
+
+ // Disable default form submission
+ return false;
+ }
+};
+
+$(document).on('pageshow', "#metadataImagesConfigurationPage", MetadataImagesPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/PluginCatalogPage.js b/MediaBrowser.WebDashboard/Html/scripts/PluginCatalogPage.js
new file mode 100644
index 0000000000..51034a0337
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/PluginCatalogPage.js
@@ -0,0 +1,80 @@
+var PluginCatalogPage = {
+
+ onPageShow: function () {
+ PluginCatalogPage.reloadList();
+ },
+
+ reloadList: function () {
+
+ Dashboard.showLoadingMsg();
+
+ var promise1 = ApiClient.getAvailablePlugins();
+
+ var promise2 = ApiClient.getInstalledPlugins();
+
+ $.when(promise1, promise2).done(function (response1, response2) {
+
+ PluginCatalogPage.populateList(response1[0], response2[0]);
+ });
+ },
+
+ populateList: function (availablePlugins, installedPlugins) {
+
+ var page = $($.mobile.activePage);
+
+ availablePlugins = availablePlugins.filter(function (p) {
+
+ return p.type == "UserInstalled";
+
+ }).sort(function (a, b) {
+
+ return a.name > b.name ? 1 : -1;
+
+ });
+
+ var html = "";
+
+ for (var i = 0, length = availablePlugins.length; i < length; i++) {
+
+ var plugin = availablePlugins[i];
+
+ html += "<div class='posterViewItem'><a href='addPlugin.html?name=" + encodeURIComponent(plugin.name) + "'>";
+
+ if (plugin.thumbImage) {
+ html += "<img src='" + plugin.thumbImage + "' />";
+ } else {
+ html += "<img style='background:#444444;' src='css/images/defaultCollectionImage.png' />";
+ }
+
+ if (plugin.isPremium) {
+ html += "<div class='premiumBanner'><img src='css/images/premiumflag.png' /></div>";
+ }
+
+ var color = plugin.tileColor || Dashboard.getRandomMetroColor();
+
+ html += "<div class='posterViewItemText' style='background:" + color + "'>";
+
+ var installedPlugin = installedPlugins.filter(function (ip) {
+ return ip.Name == plugin.name;
+ })[0];
+
+ if (installedPlugin) {
+
+ html += plugin.name + " (Installed)";
+ } else {
+ html += plugin.name;
+ }
+
+ html += "</div>";
+
+ html += "</a></div>";
+
+ }
+
+ $('#pluginTiles', page).html(html);
+
+ Dashboard.hideLoadingMsg();
+ }
+};
+
+$(document).on('pageshow', "#pluginCatalogPage", PluginCatalogPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/PluginUpdatesPage.js b/MediaBrowser.WebDashboard/Html/scripts/PluginUpdatesPage.js
new file mode 100644
index 0000000000..4edb08602b
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/PluginUpdatesPage.js
@@ -0,0 +1,109 @@
+var PluginUpdatesPage = {
+
+ onPageShow: function () {
+
+ Dashboard.showLoadingMsg();
+
+ $('.liPluginUpdate', this).remove();
+
+ ApiClient.getInstalledPlugins().done(PluginUpdatesPage.loadPlugins);
+
+ },
+
+ loadPlugins: function (plugins) {
+
+ plugins = plugins.filter(function (p) {
+
+ return !p.IsCorePlugin;
+ });
+
+ var elem = $('#tbodyPluginUpdates', $.mobile.activePage).html('');
+
+ for (var i = 0, length = plugins.length; i < length; i++) {
+
+ PluginUpdatesPage.addPlugin(plugins[i], i, elem);
+
+ }
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ addPlugin: function (plugin, fieldIndex, elem) {
+
+ var html = "";
+
+ html += "<tr>";
+
+ html += "<td><h3>" + plugin.Name + "</h3></td>";
+
+ var fieldId = "liPluginUpdateFielda" + fieldIndex;
+
+ var options = PluginUpdatesPage.getHtmlOptions(["Off", "On"], (plugin.EnableAutoUpdate ? "On" : "Off"));
+
+ html += "<td>";
+ html += "<select data-uniqueid='" + plugin.UniqueId + "' onchange='PluginUpdatesPage.setAutoUpdate(this);' data-role='slider' id='" + fieldId + "' name='" + fieldId + "'>" + options + "</select>";
+ html += "</td>";
+
+ fieldId = "liPluginUpdateFieldb" + fieldIndex;
+
+ options = PluginUpdatesPage.getHtmlOptions(["Release", "Beta", "Dev"], plugin.UpdateClass);
+
+ html += "<td>";
+ html += "<select data-uniqueid='" + plugin.UniqueId + "' onchange='PluginUpdatesPage.setUpdateClass(this);' data-inline='true' id='" + fieldId + "' name='" + fieldId + "'>" + options + "</select>";
+ html += "</td>";
+
+ html += "</tr>";
+
+ elem.append(html).trigger('create');
+ },
+
+ getHtmlOptions: function (names, selectedValue) {
+
+ var html = "";
+
+ for (var i = 0, length = names.length; i < length; i++) {
+
+ var name = names[i];
+
+ if (name == selectedValue) {
+ html += '<option value="' + name + '" selected="selected">' + name + '</option>';
+ } else {
+ html += '<option value="' + name + '">' + name + '</option>';
+ }
+ }
+
+
+ return html;
+
+ },
+
+ setAutoUpdate: function (select) {
+
+ var id = $(select).attr('data-uniqueid');
+
+ Dashboard.showLoadingMsg();
+
+ ApiClient.getPluginConfiguration(id).done(function (config) {
+
+ config.EnableAutoUpdate = select.selectedIndex === 1;
+
+ ApiClient.updatePluginConfiguration(id, config).done(Dashboard.hideLoadingMsg);
+ });
+ },
+
+ setUpdateClass: function (select) {
+
+ var id = $(select).attr('data-uniqueid');
+
+ Dashboard.showLoadingMsg();
+
+ ApiClient.getPluginConfiguration(id).done(function (config) {
+
+ config.UpdateClass = select.value;
+
+ ApiClient.updatePluginConfiguration(id, config).done(Dashboard.hideLoadingMsg);
+ });
+ }
+};
+
+$(document).on('pageshow', "#pluginUpdatesPage", PluginUpdatesPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/PluginsPage.js b/MediaBrowser.WebDashboard/Html/scripts/PluginsPage.js
new file mode 100644
index 0000000000..f7e05e4bac
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/PluginsPage.js
@@ -0,0 +1,92 @@
+var PluginsPage = {
+
+ onPageShow: function () {
+ PluginsPage.reloadList();
+ },
+
+ reloadList: function () {
+
+ Dashboard.showLoadingMsg();
+
+ var promise1 = ApiClient.getInstalledPlugins();
+
+ var promise2 = $.getJSON("configurationpages?pageType=PluginConfiguration");
+
+ $.when(promise1, promise2).done(function(response1, response2) {
+
+ PluginsPage.populateList(response1[0], response2[0]);
+
+ });
+ },
+
+ populateList: function (plugins, pluginConfigurationPages) {
+
+ var page = $($.mobile.activePage);
+
+ plugins = plugins.sort(function (plugin1, plugin2) {
+
+ return (plugin1.IsCorePlugin.toString() + plugin1.Name) > (plugin2.IsCorePlugin.toString() + plugin2.Name) ? 1 : -1;
+
+ });
+
+ var html = "";
+
+ for (var i = 0, length = plugins.length; i < length; i++) {
+
+ var plugin = plugins[i];
+
+ if (plugin.IsCorePlugin) {
+ continue;
+ }
+
+ var configPage = $.grep(pluginConfigurationPages, function (pluginConfigurationPage) {
+ return pluginConfigurationPage.OwnerPluginName == plugin.Name;
+ })[0];
+
+ html += "<li>";
+
+ var href = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : "#";
+
+ html += "<a href='" + href + "'>";
+
+ html += "<h3>" + plugin.Name + "</h3>";
+
+ html += "<p><strong>" + plugin.Version + "</strong></p>";
+
+ html += "</a>";
+
+ if (!plugin.IsCorePlugin) {
+ html += "<a data-uniqueid='" + plugin.UniqueId + "' data-pluginname='" + plugin.Name + "' onclick='PluginsPage.deletePlugin(this);' href='#'>Delete</a>";
+ }
+
+ html += "</li>";
+ }
+
+ $('#ulInstalledPlugins', page).html(html).listview('refresh');
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ deletePlugin: function (link) {
+
+ var name = link.getAttribute('data-pluginname');
+ var uniqueid = link.getAttribute('data-uniqueid');
+
+ var msg = "Are you sure you wish to uninstall " + name + "?";
+
+ Dashboard.confirm(msg, "Uninstall Plugin", function (result) {
+
+ if (result) {
+ Dashboard.showLoadingMsg();
+
+ ApiClient.uninstallPlugin(uniqueid).done(function () {
+
+ PluginsPage.reloadList();
+ });
+ }
+ });
+
+ }
+};
+
+$(document).on('pageshow', "#pluginsPage", PluginsPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/ScheduledTaskPage.js b/MediaBrowser.WebDashboard/Html/scripts/ScheduledTaskPage.js
new file mode 100644
index 0000000000..e7ea33f809
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/ScheduledTaskPage.js
@@ -0,0 +1,294 @@
+var ScheduledTaskPage = {
+
+ onPageShow: function () {
+
+ ScheduledTaskPage.refreshScheduledTask();
+ },
+
+ refreshScheduledTask: function () {
+ Dashboard.showLoadingMsg();
+
+ var id = getParameterByName('id');
+
+
+ ApiClient.getScheduledTask(id).done(ScheduledTaskPage.loadScheduledTask);
+ },
+
+ loadScheduledTask: function (task) {
+
+ Dashboard.setPageTitle(task.Name);
+
+ $('#pTaskDescription', $.mobile.activePage).html(task.Description);
+
+ ScheduledTaskPage.loadTaskTriggers(task);
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ loadTaskTriggers: function (task) {
+
+ var html = '';
+
+ html += '<li data-role="list-divider"><h3>Task Triggers</h3></li>';
+
+ for (var i = 0, length = task.Triggers.length; i < length; i++) {
+
+ var trigger = task.Triggers[i];
+
+ html += '<li>';
+
+ html += '<a href="#">';
+ html += ScheduledTaskPage.getTriggerFriendlyName(trigger);
+ html += '</a>';
+
+ html += '<a href="#" onclick="ScheduledTaskPage.confirmDeleteTrigger(' + i + ');">Delete</a>';
+
+ html += '</li>';
+ }
+
+ $('#ulTaskTriggers', $.mobile.activePage).html(html).listview('refresh');
+ },
+
+ getTriggerFriendlyName: function (trigger) {
+
+ if (trigger.Type == 'DailyTrigger') {
+ return 'Daily at ' + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks);
+ }
+
+ if (trigger.Type == 'WeeklyTrigger') {
+
+ return trigger.DayOfWeek + 's at ' + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks);
+ }
+
+ if (trigger.Type == 'SystemEventTrigger') {
+
+ if (trigger.SystemEvent == 'WakeFromSleep') {
+ return 'On wake from sleep';
+ }
+ }
+
+ if (trigger.Type == 'IntervalTrigger') {
+
+ var hours = trigger.IntervalTicks / 36000000000;
+
+ if (hours == .25) {
+ return "Every 15 minutes";
+ }
+ if (hours == .5) {
+ return "Every 30 minutes";
+ }
+ if (hours == .75) {
+ return "Every 45 minutes";
+ }
+ if (hours == 1) {
+ return "Every hour";
+ }
+
+ return 'Every ' + hours + ' hours';
+ }
+
+ if (trigger.Type == 'StartupTrigger') {
+ return 'On application startup';
+ }
+
+ return trigger.Type;
+ },
+
+ getDisplayTime: function (ticks) {
+
+ var hours = ticks / 36000000000;
+
+ if (hours < 1) {
+ hours = 0;
+ }
+
+ hours = parseInt(hours);
+
+ ticks -= (hours * 36000000000);
+
+ var minutes = parseInt(ticks / 600000000);
+
+ var suffix = "am";
+
+ if (hours > 11) {
+ suffix = "pm";
+ }
+
+ hours = hours % 12;
+
+ if (hours == 0) {
+ hours = 12;
+ }
+
+ if (minutes < 10) {
+ minutes = '0' + minutes;
+ }
+
+ return hours + ':' + minutes + ' ' + suffix;
+ },
+
+ showAddTriggerPopup: function () {
+
+ var page = $.mobile.activePage;
+
+ $('#selectTriggerType', page).val('DailyTrigger').trigger('change').selectmenu('refresh');
+
+ $('#popupAddTrigger', page).popup("open").on("popupafterclose", function () {
+
+ $('#addTriggerForm', page).off("submit");
+ $(this).off("popupafterclose");
+ });
+
+ $('#addTriggerForm', page).on('submit', function () {
+
+ ScheduledTaskPage.addTrigger();
+
+ return false;
+ });
+ },
+
+ addTrigger: function () {
+
+ Dashboard.showLoadingMsg();
+
+ var id = getParameterByName('id');
+
+ ApiClient.getScheduledTask(id).done(function (task) {
+
+ task.Triggers.push(ScheduledTaskPage.getTriggerToAdd());
+
+ ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).done(function () {
+
+ $('#popupAddTrigger').popup('close');
+
+ ScheduledTaskPage.refreshScheduledTask();
+
+ });
+
+ });
+
+ },
+
+ confirmDeleteTrigger: function (index) {
+
+ Dashboard.confirm("Are you sure you wish to delete this task trigger?", "Delete Task Trigger", function (result) {
+
+ if (result) {
+ ScheduledTaskPage.deleteTrigger(index);
+ }
+
+ });
+
+ },
+
+ deleteTrigger: function (index) {
+
+ Dashboard.showLoadingMsg();
+
+ var id = getParameterByName('id');
+
+
+ ApiClient.getScheduledTask(id).done(function (task) {
+
+ task.Triggers.remove(index);
+
+ ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).done(function () {
+
+ ScheduledTaskPage.refreshScheduledTask();
+
+ });
+
+ });
+ },
+
+ refreshTriggerFields: function (triggerType) {
+
+ var page = $.mobile.activePage;
+
+ if (triggerType == 'DailyTrigger') {
+
+ $('#fldTimeOfDay', page).show();
+ $('#fldDayOfWeek', page).hide();
+ $('#fldSelectSystemEvent', page).hide();
+ $('#fldSelectInterval', page).hide();
+ $('#txtTimeOfDay', page).attr('required', 'required');
+ }
+
+ else if (triggerType == 'WeeklyTrigger') {
+ $('#fldTimeOfDay', page).show();
+ $('#fldDayOfWeek', page).show();
+ $('#fldSelectSystemEvent', page).hide();
+ $('#fldSelectInterval', page).hide();
+ $('#txtTimeOfDay', page).attr('required', 'required');
+ }
+
+ else if (triggerType == 'SystemEventTrigger') {
+ $('#fldTimeOfDay', page).hide();
+ $('#fldDayOfWeek', page).hide();
+ $('#fldSelectSystemEvent', page).show();
+ $('#fldSelectInterval', page).hide();
+ $('#txtTimeOfDay', page).removeAttr('required');
+ }
+
+ else if (triggerType == 'IntervalTrigger') {
+ $('#fldTimeOfDay', page).hide();
+ $('#fldDayOfWeek', page).hide();
+ $('#fldSelectSystemEvent', page).hide();
+ $('#fldSelectInterval', page).show();
+ $('#txtTimeOfDay', page).removeAttr('required');
+ }
+
+ else if (triggerType == 'StartupTrigger') {
+ $('#fldTimeOfDay', page).hide();
+ $('#fldDayOfWeek', page).hide();
+ $('#fldSelectSystemEvent', page).hide();
+ $('#fldSelectInterval', page).hide();
+ $('#txtTimeOfDay', page).removeAttr('required');
+ }
+ },
+
+ getTriggerToAdd: function () {
+
+ var page = $.mobile.activePage;
+
+ var trigger = {
+ Type: $('#selectTriggerType', page).val()
+ };
+
+ if (trigger.Type == 'DailyTrigger') {
+ trigger.TimeOfDayTicks = ScheduledTaskPage.getTimeOfDayTicks($('#txtTimeOfDay', page).val());
+ }
+
+ else if (trigger.Type == 'WeeklyTrigger') {
+ trigger.DayOfWeek = $('#selectDayOfWeek', page).val();
+ trigger.TimeOfDayTicks = ScheduledTaskPage.getTimeOfDayTicks($('#txtTimeOfDay', page).val());
+ }
+
+ else if (trigger.Type == 'SystemEventTrigger') {
+ trigger.SystemEvent = $('#selectSystemEvent', page).val();
+ }
+
+ else if (trigger.Type == 'IntervalTrigger') {
+ trigger.IntervalTicks = $('#selectInterval', page).val();
+ }
+
+ return trigger;
+ },
+
+ getTimeOfDayTicks: function (val) {
+
+ var vals = val.split(':');
+
+ var hours = vals[0];
+ var minutes = vals[1];
+
+ // Add hours
+ var ticks = hours * 60 * 60 * 1000 * 10000;
+
+ ticks += minutes * 60 * 1000 * 10000;
+
+ return ticks;
+ }
+};
+
+$(document).on('pageshow', "#scheduledTaskPage", ScheduledTaskPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/ScheduledTasksPage.js b/MediaBrowser.WebDashboard/Html/scripts/ScheduledTasksPage.js
new file mode 100644
index 0000000000..d822db722d
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/ScheduledTasksPage.js
@@ -0,0 +1,170 @@
+var ScheduledTasksPage = {
+
+ onPageShow: function () {
+
+ Dashboard.showLoadingMsg();
+
+ ScheduledTasksPage.reloadList(true);
+
+ $(document).on("websocketmessage", ScheduledTasksPage.onWebSocketMessage).on("websocketopen", ScheduledTasksPage.onWebSocketConnectionChange).on("websocketerror", ScheduledTasksPage.onWebSocketConnectionChange).on("websocketclose", ScheduledTasksPage.onWebSocketConnectionChange);
+ },
+
+ onPageHide: function () {
+ $(document).off("websocketmessage", ScheduledTasksPage.onWebSocketMessage).off("websocketopen", ScheduledTasksPage.onWebSocketConnectionChange).off("websocketerror", ScheduledTasksPage.onWebSocketConnectionChange).off("websocketclose", ScheduledTasksPage.onWebSocketConnectionChange);
+ ScheduledTasksPage.stopInterval();
+ },
+
+ startInterval: function () {
+
+ if (Dashboard.isWebSocketOpen()) {
+ Dashboard.sendWebSocketMessage("ScheduledTasksInfoStart", "1500,1500");
+ }
+ },
+
+ stopInterval: function () {
+
+ if (Dashboard.isWebSocketOpen()) {
+ Dashboard.sendWebSocketMessage("ScheduledTasksInfoStop");
+ }
+ },
+
+ onWebSocketMessage: function (e, msg) {
+
+ if (msg.MessageType == "ScheduledTasksInfo") {
+ ScheduledTasksPage.populateList(msg.Data);
+ }
+ },
+
+ onWebSocketConnectionChange: function() {
+ ScheduledTasksPage.reloadList(true);
+ },
+
+ reloadList: function (updateInterval) {
+
+ if (updateInterval) {
+ ScheduledTasksPage.stopInterval();
+ }
+
+ ApiClient.getScheduledTasks().done(function (tasks) {
+ ScheduledTasksPage.populateList(tasks);
+ Dashboard.hideLoadingMsg();
+
+ if (updateInterval) {
+ ScheduledTasksPage.startInterval();
+ }
+
+ });
+ },
+
+ populateList: function (tasks) {
+
+ tasks = tasks.sort(function (a, b) {
+
+ a = a.Category + " " + a.Name;
+ b = b.Category + " " + b.Name;
+
+ if (a == b) {
+ return 0;
+ }
+
+ if (a < b) {
+ return -1;
+ }
+
+ return 1;
+ });
+
+ var page = $($.mobile.activePage);
+
+ var html = "";
+
+ var currentCategory;
+
+ for (var i = 0, length = tasks.length; i < length; i++) {
+
+ var task = tasks[i];
+
+ if (task.Category != currentCategory) {
+ currentCategory = task.Category;
+
+ html += "<li data-role='list-divider'>" + currentCategory + "</li>";
+ }
+
+ html += "<li>";
+
+ html += "<a href='scheduledTask.html?id=" + task.Id + "'>";
+
+ html += "<h3>" + task.Name + "</h3>";
+
+ if (task.State == "Idle") {
+
+ if (task.LastExecutionResult) {
+
+ var text = "Last run " + humane_date(task.LastExecutionResult.EndTimeUtc) + ', taking ' + humane_elapsed(task.LastExecutionResult.StartTimeUtc, task.LastExecutionResult.EndTimeUtc);
+
+ if (task.LastExecutionResult.Status == "Failed") {
+ text += " <span style='color:#FF0000;'>(failed)</span>";
+ }
+ else if (task.LastExecutionResult.Status == "Cancelled") {
+ text += " <span style='color:#0026FF;'>(cancelled)</span>";
+ }
+ else if (task.LastExecutionResult.Status == "Aborted") {
+ text += " <span style='color:#FF0000;'>(Aborted by server shutdown)</span>";
+ }
+
+ html += "<p>" + text + "</p>";
+ }
+
+ html += "<a href='#' data-icon='play' onclick='ScheduledTasksPage.startTask(\"" + task.Id + "\");'>Start</a>";
+ }
+ else if (task.State == "Running") {
+
+ var progress = task.CurrentProgress || { PercentComplete: 0 };
+ progress = Math.round(progress.PercentComplete);
+
+ html += '<p><progress max="100" value="' + progress + '" title="' + progress + '%">';
+ html += '' + progress + '%';
+ html += '</progress>';
+
+ html += "<span style='color:#009F00;margin-left:5px;'>" + progress + "%</span>";
+ html += '</p>';
+
+ html += "<a href='#' data-icon='stop' onclick='ScheduledTasksPage.stopTask(\"" + task.Id + "\");'>Stop</a>";
+
+ } else {
+
+ html += "<p style='color:#FF0000;'>Stopping</p>";
+ html += "<a href='#' data-icon='play' style='visibility:hidden;'>Start</a>";
+ }
+
+ html += "</a>";
+
+ html += "</li>";
+ }
+
+ $('#ulScheduledTasks', page).html(html).listview('refresh');
+ },
+
+ startTask: function (id) {
+
+ Dashboard.showLoadingMsg();
+
+ ApiClient.startScheduledTask(id).done(function (result) {
+
+ ScheduledTasksPage.reloadList();
+ });
+
+ },
+
+ stopTask: function (id) {
+
+ Dashboard.showLoadingMsg();
+
+ ApiClient.stopScheduledTask(id).done(function (result) {
+
+ ScheduledTasksPage.reloadList();
+ });
+ }
+};
+
+$(document).on('pageshow', "#scheduledTasksPage", ScheduledTasksPage.onPageShow).on('pagehide', "#scheduledTasksPage", ScheduledTasksPage.onPageHide); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/SupporterKeyPage.js b/MediaBrowser.WebDashboard/Html/scripts/SupporterKeyPage.js
new file mode 100644
index 0000000000..50da44331d
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/SupporterKeyPage.js
@@ -0,0 +1,79 @@
+var SupporterKeyPage = {
+
+ onPageShow: function () {
+ SupporterKeyPage.load();
+ },
+
+ onPageHide: function () {
+
+ },
+
+ load: function() {
+ Dashboard.showLoadingMsg();
+ var page = $.mobile.activePage;
+
+ ApiClient.getPluginSecurityInfo().done(function (info) {
+ $('#txtSupporterKey', page).val(info.SupporterKey);
+ $('#txtLegacyKey', page).val(info.LegacyKey);
+ if (info.IsMBSupporter) {
+ $('.supporterOnly', page).show();
+ } else {
+ $('.supporterOnly', page).hide();
+ }
+ Dashboard.hideLoadingMsg();
+ });
+ },
+
+ updateSupporterKey: function () {
+
+ Dashboard.showLoadingMsg();
+ var page = $.mobile.activePage;
+
+ var key = $('#txtSupporterKey', page).val();
+ var legacyKey = $('#txtLegacyKey', page).val();
+
+ var info = {
+ SupporterKey: key,
+ LegacyKey: legacyKey
+ };
+
+ var url = ApiClient.getUrl("Plugins/SecurityInfo");
+ console.log(url);
+ $.post(url, JSON.stringify(info)).done(function () {
+ Dashboard.resetPluginSecurityInfo();
+ Dashboard.hideLoadingMsg();
+ SupporterPage.load();
+
+ });
+
+ return false;
+ },
+
+ retrieveSupporterKey: function () {
+
+ Dashboard.showLoadingMsg();
+ var page = $.mobile.activePage;
+
+ var email = $('#txtEmail', page).val();
+
+ var url = "http://mb3admin.com/admin/service/supporter/retrievekey?email="+email;
+ console.log(url);
+ $.post(url).done(function (res) {
+ var result = JSON.parse(res);
+ Dashboard.hideLoadingMsg();
+ if (result.Success) {
+ Dashboard.alert("Key emailed to "+email);
+ } else {
+ Dashboard.showError(result.ErrorMessage);
+ }
+ console.log(result);
+
+ });
+
+ return false;
+ }
+
+};
+
+$(document).on('pageshow', "#supporterKeyPage", SupporterKeyPage.onPageShow)
+ .on('pagehide', "#supporterKeyPage", SupporterKeyPage.onPageHide);
diff --git a/MediaBrowser.WebDashboard/Html/scripts/SupporterPage.js b/MediaBrowser.WebDashboard/Html/scripts/SupporterPage.js
new file mode 100644
index 0000000000..2a599f2592
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/SupporterPage.js
@@ -0,0 +1,29 @@
+var SupporterPage = {
+
+ onPageShow: function () {
+ SupporterPage.load();
+ },
+
+ onPageHide: function () {
+
+ },
+
+ load: function() {
+ Dashboard.showLoadingMsg();
+ var page = $.mobile.activePage;
+
+ ApiClient.getPluginSecurityInfo().done(function (info) {
+ $('#txtSupporterKey', page).val(info.SupporterKey);
+ if (info.IsMBSupporter) {
+ $('.supporterOnly', page).show();
+ } else {
+ $('.supporterOnly', page).hide();
+ }
+ $('#paypalReturnUrl', page).val(ApiClient.getCustomUrl("dashboard/supporterKey.html"));
+ Dashboard.hideLoadingMsg();
+ });
+ }
+};
+
+$(document).on('pageshow', "#supporterPage", SupporterPage.onPageShow)
+ .on('pagehide', "#supporterPage", SupporterPage.onPageHide);
diff --git a/MediaBrowser.WebDashboard/Html/scripts/UpdatePasswordPage.js b/MediaBrowser.WebDashboard/Html/scripts/UpdatePasswordPage.js
new file mode 100644
index 0000000000..9c3a009b23
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/UpdatePasswordPage.js
@@ -0,0 +1,88 @@
+var UpdatePasswordPage = {
+
+ onPageShow: function () {
+ UpdatePasswordPage.loadUser();
+ },
+
+ loadUser: function() {
+ var page = $.mobile.activePage;
+
+ var userid = getParameterByName("userId");
+
+ ApiClient.getUser(userid).done(function (user) {
+
+ Dashboard.setPageTitle(user.Name);
+
+ if (user.HasPassword) {
+ $('#btnResetPassword', page).show();
+ } else {
+ $('#btnResetPassword', page).hide();
+ }
+
+ });
+
+ $('#txtCurrentPassword', page).val('');
+ $('#txtNewPassword', page).val('');
+ $('#txtNewPasswordConfirm', page).val('');
+ },
+
+ save: function () {
+
+ var userId = getParameterByName("userId");
+
+ var page = $($.mobile.activePage);
+ var currentPassword = $('#txtCurrentPassword', page).val();
+ var newPassword = $('#txtNewPassword', page).val();
+
+ ApiClient.updateUserPassword(userId, currentPassword, newPassword).done(UpdatePasswordPage.saveComplete);
+ },
+
+ saveComplete: function () {
+
+ Dashboard.hideLoadingMsg();
+
+ Dashboard.alert("Password saved.");
+ UpdatePasswordPage.loadUser();
+ },
+
+ resetPassword: function () {
+
+ var msg = "Are you sure you wish to reset the password?";
+
+ Dashboard.confirm(msg, "Password Reset", function (result) {
+
+ if (result) {
+ var userId = getParameterByName("userId");
+
+ Dashboard.showLoadingMsg();
+
+ ApiClient.resetUserPassword(userId).done(function () {
+
+ Dashboard.hideLoadingMsg();
+ Dashboard.alert("The password has been reset.");
+ UpdatePasswordPage.loadUser();
+
+ });
+ }
+ });
+ },
+
+ onSubmit: function () {
+ var page = $($.mobile.activePage);
+
+ if ($('#txtNewPassword', page).val() != $('#txtNewPasswordConfirm', page).val()) {
+
+ Dashboard.showError("Password and password confirmation must match.");
+ return false;
+ }
+
+ Dashboard.showLoadingMsg();
+
+ UpdatePasswordPage.save();
+
+ // Disable default form submission
+ return false;
+ }
+};
+
+$(document).on('pageshow', "#updatePasswordPage", UpdatePasswordPage.onPageShow); \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/UserImagePage.js b/MediaBrowser.WebDashboard/Html/scripts/UserImagePage.js
new file mode 100644
index 0000000000..58dadc6f8a
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/UserImagePage.js
@@ -0,0 +1,181 @@
+var UserImagePage = {
+
+ onPageShow: function () {
+
+ UserImagePage.reloadUser();
+
+ $("#userImageDropZone", this).on('dragover', UserImagePage.onImageDragOver).on('drop', UserImagePage.onImageDrop);
+ },
+
+ onPageHide: function () {
+ $("#userImageDropZone", this).off('dragover', UserImagePage.onImageDragOver).off('drop', UserImagePage.onImageDrop);
+ },
+
+ reloadUser: function () {
+
+ var userId = getParameterByName("userId");
+
+ Dashboard.showLoadingMsg();
+
+ ApiClient.getUser(userId).done(function (user) {
+
+ var page = $($.mobile.activePage);
+
+ $('#uploadUserImage', page).val('').trigger('change');
+
+ Dashboard.setPageTitle(user.Name);
+
+ if (user.PrimaryImageTag) {
+
+ var imageUrl = ApiClient.getUserImageUrl(user.Id, {
+ height: 450,
+ tag: user.PrimaryImageTag,
+ type: "Primary"
+ });
+
+ $('#fldImage', page).show().html('').html("<img height='200px' src='" + imageUrl + "' />");
+
+ $('#fldDeleteImage', page).show();
+ $('#headerUploadNewImage', page).show();
+ } else {
+ $('#fldImage', page).hide().html('');
+ $('#fldDeleteImage', page).hide();
+ $('#headerUploadNewImage', page).hide();
+ }
+
+ Dashboard.hideLoadingMsg();
+ });
+ },
+
+ deleteImage: function () {
+
+ Dashboard.confirm("Are you sure you wish to delete the image?", "Delete Image", function (result) {
+
+ if (result) {
+
+ Dashboard.showLoadingMsg();
+
+ var userId = getParameterByName("userId");
+
+ ApiClient.deleteUserImage(userId, "primary").done(UserImagePage.processImageChangeResult);
+ }
+
+ });
+ },
+
+ processImageChangeResult: function () {
+
+ Dashboard.hideLoadingMsg();
+
+ Dashboard.validateCurrentUser();
+ UserImagePage.reloadUser();
+ },
+
+ onFileUploadChange: function (fileUpload) {
+
+ UserImagePage.setFiles(fileUpload.files);
+ },
+
+ setFiles: function (files) {
+
+ var page = $.mobile.activePage;
+
+ var file = files[0];
+
+ if (!file || !file.type.match('image.*')) {
+ $('#userImageOutput', page).html('');
+ $('#fldUpload', page).hide();
+ UserImagePage.currentFile = null;
+ return;
+ }
+
+ UserImagePage.currentFile = file;
+
+ var reader = new FileReader();
+
+ reader.onerror = UserImagePage.onFileReaderError;
+ reader.onloadstart = UserImagePage.onFileReaderOnloadStart;
+ reader.onabort = UserImagePage.onFileReaderAbort;
+
+ // Closure to capture the file information.
+ reader.onload = (function (theFile) {
+ return function (e) {
+
+ // Render thumbnail.
+ var html = ['<img style="max-width:500px;max-height:200px;" src="', e.target.result, '" title="', escape(theFile.name), '"/>'].join('');
+
+ $('#userImageOutput', page).html(html);
+ $('#fldUpload', page).show();
+ };
+ })(file);
+
+ // Read in the image file as a data URL.
+ reader.readAsDataURL(file);
+ },
+
+ onFileReaderError: function (evt) {
+
+ Dashboard.hideLoadingMsg();
+
+ switch (evt.target.error.code) {
+ case evt.target.error.NOT_FOUND_ERR:
+ Dashboard.showError('File Not Found!');
+ break;
+ case evt.target.error.NOT_READABLE_ERR:
+ Dashboard.showError('File is not readable');
+ break;
+ case evt.target.error.ABORT_ERR:
+ break; // noop
+ default:
+ Dashboard.showError('An error occurred reading this file.');
+ };
+ },
+
+ onFileReaderOnloadStart: function (evt) {
+
+ $('#fldUpload', $.mobile.activePage).hide();
+ },
+
+ onFileReaderAbort: function (evt) {
+
+ Dashboard.hideLoadingMsg();
+ Dashboard.showError('File read cancelled');
+ },
+
+ onSubmit: function () {
+
+ var file = UserImagePage.currentFile;
+
+ if (!file || !file.type.match('image.*')) {
+ return false;
+ }
+
+ Dashboard.showLoadingMsg();
+
+ var userId = getParameterByName("userId");
+
+ ApiClient.uploadUserImage(userId, 'Primary', file).done(UserImagePage.processImageChangeResult);
+
+ return false;
+ },
+
+ onImageDrop: function (e) {
+
+ e.preventDefault();
+
+ UserImagePage.setFiles(e.originalEvent.dataTransfer.files);
+
+ return false;
+ },
+
+ onImageDragOver: function (e) {
+
+ e.preventDefault();
+
+ e.originalEvent.dataTransfer.dropEffect = 'Copy';
+
+ return false;
+ }
+};
+
+$(document).on('pageshow', "#userImagePage", UserImagePage.onPageShow).on('pagehide', "#userImagePage", UserImagePage.onPageHide);
diff --git a/MediaBrowser.WebDashboard/Html/scripts/UserProfilesPage.js b/MediaBrowser.WebDashboard/Html/scripts/UserProfilesPage.js
new file mode 100644
index 0000000000..b36962a5c4
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/UserProfilesPage.js
@@ -0,0 +1,76 @@
+var UserProfilesPage = {
+ onPageShow: function () {
+
+ UserProfilesPage.loadPageData();
+ },
+
+ loadPageData: function () {
+
+ Dashboard.showLoadingMsg();
+ ApiClient.getAllUsers().done(UserProfilesPage.renderUsers);
+ },
+
+ renderUsers: function (users) {
+
+ var html = "";
+
+ html += '<li data-role="list-divider"><h3>Users</h3></li>';
+
+ for (var i = 0, length = users.length; i < length; i++) {
+
+ var user = users[i];
+
+ html += "<li>";
+
+ html += "<a onclick='Dashboard.navigate(\"editUser.html?userId=" + user.Id + "\");' href='#'>";
+
+ if (user.PrimaryImageTag) {
+
+ var url = ApiClient.getUserImageUrl(user.Id, {
+ width: 225,
+ tag: user.PrimaryImageTag,
+ type: "Primary"
+ });
+ html += "<img src='" + url + "' />";
+ } else {
+ html += "<img src='css/images/userFlyoutDefault.png' />";
+ }
+
+ html += "<h3>" + user.Name + "</h3>";
+
+ html += "</a>";
+
+ html += "<a onclick='UserProfilesPage.deleteUser(this);' data-userid='" + user.Id + "' data-username='" + user.Name + "' href='#'>Delete</a>";
+
+ html += "</li>";
+ }
+
+ $('#ulUserProfiles', $('#userProfilesPage')).html(html).listview('refresh');
+
+ Dashboard.hideLoadingMsg();
+ },
+
+ deleteUser: function (link) {
+
+ var name = link.getAttribute('data-username');
+
+ var msg = "Are you sure you wish to delete " + name + "?";
+
+ Dashboard.confirm(msg, "Delete User", function (result) {
+
+ if (result) {
+ Dashboard.showLoadingMsg();
+
+ var id = link.getAttribute('data-userid');
+
+ ApiClient.deleteUser(id).done(function () {
+
+ Dashboard.validateCurrentUser();
+ UserProfilesPage.loadPageData();
+ });
+ }
+ });
+ }
+};
+
+$(document).on('pageshow', "#userProfilesPage", UserProfilesPage.onPageShow);
diff --git a/MediaBrowser.WebDashboard/Html/scripts/WizardStartPage.js b/MediaBrowser.WebDashboard/Html/scripts/WizardStartPage.js
new file mode 100644
index 0000000000..97c09d2ab6
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/WizardStartPage.js
@@ -0,0 +1,17 @@
+var WizardStartPage = {
+
+ gotoNextPage: function () {
+
+ ApiClient.getAllUsers().done(function (users) {
+
+ if (users.length > 1) {
+
+ Dashboard.navigate('wizardLibrary.html');
+
+ } else {
+ Dashboard.navigate('wizardUser.html');
+ }
+ });
+
+ }
+}; \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Html/scripts/WizardUserPage.js b/MediaBrowser.WebDashboard/Html/scripts/WizardUserPage.js
new file mode 100644
index 0000000000..decc6c3a39
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/WizardUserPage.js
@@ -0,0 +1,59 @@
+var WizardUserPage = {
+
+ onPageShow: function () {
+
+ Dashboard.showLoadingMsg();
+
+ var page = this;
+
+ ApiClient.getAllUsers().done(function (users) {
+
+ var user = users[0] || { Name: "User" };
+
+ $('#txtUsername', page).val(user.Name);
+
+ Dashboard.hideLoadingMsg();
+ });
+
+ },
+
+ onSubmit: function() {
+
+ Dashboard.showLoadingMsg();
+
+ var page = $.mobile.activePage;
+
+ ApiClient.getAllUsers().done(function (users) {
+
+ var user;
+
+ if (users.length) {
+
+ user = users[0];
+
+ user.Name = $('#txtUsername', page).val();
+
+ ApiClient.updateUser(user).done(WizardUserPage.saveComplete);
+
+ } else {
+
+ user = { Name: $('#txtUsername', page).val() };
+
+ ApiClient.createUser(user).done(WizardUserPage.saveComplete);
+ }
+
+ });
+
+ return false;
+ },
+
+ saveComplete: function () {
+
+ Dashboard.hideLoadingMsg();
+
+ Dashboard.navigate('wizardLibrary.html');
+ }
+
+};
+
+$(document).on('pageshow', "#wizardUserPage", WizardUserPage.onPageShow);
diff --git a/MediaBrowser.WebDashboard/Html/scripts/site.js b/MediaBrowser.WebDashboard/Html/scripts/site.js
new file mode 100644
index 0000000000..d358b974ef
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Html/scripts/site.js
@@ -0,0 +1,1185 @@
+$.ajaxSetup({
+ crossDomain: true,
+
+ error: function (event, jqxhr, settings, exception) {
+ Dashboard.hideLoadingMsg();
+
+ if (!Dashboard.suppressAjaxErrors) {
+ setTimeout(function () {
+
+
+ var msg = event.getResponseHeader("X-Application-Error-Code") || Dashboard.defaultErrorMessage;
+
+ Dashboard.showError(msg);
+ }, 500);
+ }
+ }
+});
+
+$.support.cors = true;
+
+$(document).one('click', WebNotifications.requestPermission);
+
+var Dashboard = {
+ jQueryMobileInit: function () {
+
+ //$.mobile.defaultPageTransition = 'slide';
+
+ // Page
+ //$.mobile.page.prototype.options.theme = "a";
+ //$.mobile.page.prototype.options.headerTheme = "a";
+ //$.mobile.page.prototype.options.contentTheme = "a";
+ //$.mobile.page.prototype.options.footerTheme = "a";
+
+ //$.mobile.button.prototype.options.theme = "c";
+ $.mobile.listview.prototype.options.dividerTheme = "b";
+
+ $.mobile.popup.prototype.options.theme = "c";
+ //$.mobile.collapsible.prototype.options.contentTheme = "a";
+ },
+
+ getCurrentUser: function () {
+
+ if (!Dashboard.getUserPromise) {
+ Dashboard.getUserPromise = ApiClient.getUser(Dashboard.getCurrentUserId()).fail(Dashboard.logout);
+ }
+
+ return Dashboard.getUserPromise;
+ },
+
+ validateCurrentUser: function () {
+ Dashboard.getUserPromise = null;
+
+ if (Dashboard.getCurrentUserId()) {
+ Dashboard.getCurrentUser();
+ }
+
+ // Re-render the header
+ $('.header').remove();
+ Dashboard.ensureHeader($.mobile.activePage);
+ },
+
+ getCurrentUserId: function () {
+
+ var userId = localStorage.getItem("userId");
+
+ if (!userId) {
+ var autoLoginUserId = getParameterByName('u');
+
+ if (autoLoginUserId) {
+ userId = autoLoginUserId;
+ localStorage.setItem("userId", userId);
+ }
+ }
+
+ return userId;
+ },
+
+ setCurrentUser: function (userId) {
+ localStorage.setItem("userId", userId);
+ Dashboard.getUserPromise = null;
+ },
+
+ logout: function () {
+ localStorage.removeItem("userId");
+ Dashboard.getUserPromise = null;
+ window.location = "login.html";
+ },
+
+ showError: function (message) {
+
+ $.mobile.loading('show', {
+ theme: "e",
+ text: message,
+ textonly: true,
+ textVisible: true
+ });
+
+ setTimeout(function () {
+ $.mobile.loading('hide');
+ }, 2000);
+ },
+
+ alert: function (message) {
+
+ $.mobile.loading('show', {
+ theme: "e",
+ text: message,
+ textonly: true,
+ textVisible: true
+ });
+
+ setTimeout(function () {
+ $.mobile.loading('hide');
+ }, 2000);
+ },
+
+ updateSystemInfo: function (info) {
+
+ var isFirstLoad = !Dashboard.lastSystemInfo;
+
+ Dashboard.lastSystemInfo = info;
+ Dashboard.ensureWebSocket(info);
+
+ if (!Dashboard.initialServerVersion) {
+ Dashboard.initialServerVersion = info.Version;
+ }
+
+ if (info.HasPendingRestart) {
+
+ Dashboard.hideDashboardVersionWarning();
+ Dashboard.showServerRestartWarning();
+
+ } else {
+
+ Dashboard.hideServerRestartWarning();
+
+ if (Dashboard.initialServerVersion != info.Version) {
+
+ Dashboard.showDashboardVersionWarning();
+ }
+ }
+
+ if (isFirstLoad) {
+ Dashboard.showFailedAssemblies(info.FailedPluginAssemblies);
+ }
+
+ Dashboard.showInProgressInstallations(info.InProgressInstallations);
+ },
+
+ showFailedAssemblies: function (failedAssemblies) {
+
+ for (var i = 0, length = failedAssemblies.length; i < length; i++) {
+
+ var assembly = failedAssemblies[i];
+
+ var html = '<img src="css/images/notifications/error.png" class="notificationIcon" />';
+
+ var index = assembly.lastIndexOf('\\');
+
+ if (index != -1) {
+ assembly = assembly.substring(index + 1);
+ }
+
+ html += '<span>';
+ html += assembly + " failed to load.";
+ html += '</span>';
+
+ Dashboard.showFooterNotification({ html: html });
+
+ }
+ },
+
+ showInProgressInstallations: function (installations) {
+
+ installations = installations || [];
+
+ for (var i = 0, length = installations.length; i < length; i++) {
+
+ var installation = installations[i];
+
+ var percent = installation.PercentComplete || 0;
+
+ if (percent < 100) {
+ Dashboard.showPackageInstallNotification(installation, "progress");
+ }
+ }
+
+ if (installations.length) {
+
+ Dashboard.ensureInstallRefreshInterval();
+ } else {
+ Dashboard.stopInstallRefreshInterval();
+ }
+ },
+
+ ensureInstallRefreshInterval: function () {
+
+ if (!Dashboard.installRefreshInterval) {
+
+ if (Dashboard.isWebSocketOpen()) {
+ Dashboard.sendWebSocketMessage("SystemInfoStart", "0,350");
+ }
+ Dashboard.installRefreshInterval = 1;
+ }
+ },
+
+ stopInstallRefreshInterval: function () {
+
+ if (Dashboard.installRefreshInterval) {
+ if (Dashboard.isWebSocketOpen()) {
+ Dashboard.sendWebSocketMessage("SystemInfoStop");
+ }
+ Dashboard.installRefreshInterval = null;
+ }
+ },
+
+ cancelInstallation: function (id) {
+
+ ApiClient.cancelPackageInstallation(id).always(Dashboard.refreshSystemInfoFromServer);
+
+ },
+
+ showServerRestartWarning: function () {
+
+ var html = '<span style="margin-right: 1em;">Please restart Media Browser Server to finish updating.</span>';
+ html += '<button type="button" data-icon="refresh" onclick="Dashboard.restartServer();" data-theme="b" data-inline="true" data-mini="true">Restart Server</button>';
+
+ Dashboard.showFooterNotification({ id: "serverRestartWarning", html: html, forceShow: true, allowHide: false });
+ },
+
+ hideServerRestartWarning: function () {
+
+ $('#serverRestartWarning').remove();
+ },
+
+ showDashboardVersionWarning: function () {
+
+ var html = '<span style="margin-right: 1em;">Please refresh this page to receive new updates from the server.</span>';
+ html += '<button type="button" data-icon="refresh" onclick="Dashboard.reloadPage();" data-theme="b" data-inline="true" data-mini="true">Refresh Page</button>';
+
+ Dashboard.showFooterNotification({ id: "dashboardVersionWarning", html: html, forceShow: true, allowHide: false });
+ },
+
+ reloadPage: function () {
+
+ window.location.href = window.location.href;
+ },
+
+ hideDashboardVersionWarning: function () {
+
+ $('#dashboardVersionWarning').remove();
+ },
+
+ showFooterNotification: function (options) {
+
+ var removeOnHide = !options.id;
+
+ options.id = options.id || "notification" + new Date().getTime() + parseInt(Math.random());
+
+ var parentElem = $('#footerNotifications');
+
+ var elem = $('#' + options.id, parentElem);
+
+ if (!elem.length) {
+ elem = $('<p id="' + options.id + '" class="footerNotification"></p>').appendTo(parentElem);
+ }
+
+ var onclick = removeOnHide ? "$(\"#" + options.id + "\").remove();" : "$(\"#" + options.id + "\").hide();";
+
+ if (options.allowHide !== false) {
+ options.html += "<span style='margin-left: 1em;'><button type='button' onclick='" + onclick + "' data-icon='delete' data-iconpos='notext' data-mini='true' data-inline='true' data-theme='a'>Hide</button></span>";
+ }
+
+ if (options.forceShow) {
+ elem.show();
+ }
+
+ elem.html(options.html).trigger('create');
+
+ if (options.timeout) {
+
+ setTimeout(function () {
+
+ if (removeOnHide) {
+ elem.remove();
+ } else {
+ elem.hide();
+ }
+
+ }, options.timeout);
+ }
+ },
+
+ getConfigurationPageUrl: function (name) {
+ return "ConfigurationPage?name=" + encodeURIComponent(name);
+ },
+
+ navigate: function (url, preserveQueryString) {
+
+ var queryString = window.location.search;
+ if (preserveQueryString && queryString) {
+ url += queryString;
+ }
+ $.mobile.changePage(url);
+ },
+
+ showLoadingMsg: function () {
+ $.mobile.showPageLoadingMsg();
+ },
+
+ hideLoadingMsg: function () {
+ $.mobile.hidePageLoadingMsg();
+ },
+
+ processPluginConfigurationUpdateResult: function () {
+
+ Dashboard.hideLoadingMsg();
+
+ Dashboard.alert("Settings saved.");
+ },
+
+ defaultErrorMessage: "There was an error processing the request.",
+
+ processServerConfigurationUpdateResult: function (result) {
+
+ Dashboard.hideLoadingMsg();
+
+ Dashboard.alert("Settings saved.");
+ },
+
+ confirm: function (message, title, callback) {
+
+ $('#confirmFlyout').popup("close").remove();
+
+ var html = '<div data-role="popup" id="confirmFlyout" style="max-width:500px;" class="ui-corner-all">';
+
+ html += '<div class="ui-corner-top ui-bar-a" style="text-align:center;">';
+ html += '<h3>' + title + '</h3>';
+ html += '</div>';
+
+ html += '<div data-role="content" class="ui-corner-bottom ui-content">';
+
+ html += '<div style="padding: 1em .25em;margin: 0;">';
+ html += message;
+ html += '</div>';
+
+ html += '<p><button type="button" data-icon="ok" onclick="$(\'#confirmFlyout\')[0].confirm=true;$(\'#confirmFlyout\').popup(\'close\');" data-theme="b">Ok</button></p>';
+ html += '<p><button type="button" data-icon="delete" onclick="$(\'#confirmFlyout\').popup(\'close\');" data-theme="a">Cancel</button></p>';
+ html += '</div>';
+
+ html += '</div>';
+
+ $(document.body).append(html);
+
+ $('#confirmFlyout').popup().trigger('create').popup("open").on("popupafterclose", function () {
+
+ if (callback) {
+ callback(this.confirm == true);
+ }
+
+ $(this).off("popupafterclose").remove();
+ });
+ },
+
+ refreshSystemInfoFromServer: function () {
+ ApiClient.getSystemInfo().done(function (info) {
+
+ Dashboard.updateSystemInfo(info);
+ });
+ },
+
+ restartServer: function () {
+
+ Dashboard.suppressAjaxErrors = true;
+ Dashboard.showLoadingMsg();
+
+ ApiClient.performPendingRestart().done(function () {
+
+ setTimeout(function () {
+ Dashboard.reloadPageWhenServerAvailable();
+ }, 500);
+
+ }).fail(function () {
+ Dashboard.suppressAjaxErrors = false;
+ });
+ },
+
+ reloadPageWhenServerAvailable: function (retryCount) {
+
+ ApiClient.getSystemInfo().done(function () {
+ Dashboard.reloadPage();
+
+ }).fail(function () {
+ setTimeout(function () {
+
+ retryCount = retryCount || 0;
+ retryCount++;
+
+ if (retryCount < 10) {
+ Dashboard.reloadPageWhenServerAvailable(retryCount);
+ } else {
+ Dashboard.suppressAjaxErrors = false;
+ }
+ }, 500);
+ });
+ },
+
+ getPosterViewHtml: function (options) {
+
+ var html = "";
+
+ for (var i = 0, length = options.items.length; i < length; i++) {
+ var item = options.items[i];
+
+ var hasPrimaryImage = item.ImageTags && item.ImageTags.Primary;
+
+ var href = item.IsFolder ? "#" : "itemDetails.html?id=" + item.Id;
+
+ html += "<div class='posterViewItem'><a href='" + href + "'>";
+
+ if (options.preferBackdrop && item.BackdropImageTags && item.BackdropImageTags.length) {
+ html += "<img src='" + ApiClient.getImageUrl(item.Id, {
+ type: "Backdrop",
+ height: 198,
+ width: 352,
+ tag: item.BackdropImageTags[0]
+ }) + "' />";
+ } else if (hasPrimaryImage) {
+ html += "<img src='" + ApiClient.getImageUrl(item.Id, {
+ type: "Primary",
+ height: 300,
+ tag: item.ImageTags.Primary
+ }) + "' />";
+ }
+ else if (item.BackdropImageTags && item.BackdropImageTags.length) {
+ html += "<img src='" + ApiClient.getImageUrl(item.Id, {
+ type: "Backdrop",
+ height: 198,
+ width: 352,
+ tag: item.BackdropImageTags[0]
+ }) + "' />";
+ } else {
+ html += "<img style='background:" + Dashboard.getRandomMetroColor() + ";' src='css/images/defaultCollectionImage.png' />";
+ }
+
+ if (options.showTitle || !hasPrimaryImage || (item.Type !== 'Movie' && item.Type !== 'Series' && item.Type !== 'Season')) {
+ html += "<div class='posterViewItemText'>" + item.Name + "</div>";
+ }
+
+ html += "</a></div>";
+ }
+
+ return html;
+ },
+
+ showUserFlyout: function () {
+
+ Dashboard.getCurrentUser().done(function (user) {
+
+ var html = '<div data-role="popup" id="userFlyout" style="max-width:400px;margin-top:50px;margin-right:20px;" class="ui-corner-all" data-position-to=".btnCurrentUser:visible">';
+
+ html += '<a href="#" data-rel="back" data-role="button" data-theme="a" data-icon="delete" data-iconpos="notext" class="ui-btn-right">Close</a>';
+
+ html += '<div class="ui-corner-top ui-bar-a" style="text-align:center;">';
+ html += '<h3>' + user.Name + '</h3>';
+ html += '</div>';
+
+ html += '<div data-role="content" class="ui-corner-bottom ui-content">';
+
+ html += '<p style="text-align:center;">';
+
+ var imageUrl = user.PrimaryImageTag ? ApiClient.getUserImageUrl(user.Id, {
+
+ height: 400,
+ tag: user.PrimaryImageTag,
+ type: "Primary"
+
+ }) : "css/images/userFlyoutDefault.png";
+
+ html += '<img style="max-height:125px;max-width:200px;" src="' + imageUrl + '" />';
+ html += '</p>';
+
+ html += '<p><button type="button" onclick="Dashboard.navigate(\'editUser.html?userId=' + user.Id + '\');" data-icon="user">View Profile</button></p>';
+ html += '<p><button type="button" onclick="Dashboard.logout();" data-icon="lock">Sign Out</button></p>';
+ html += '</div>';
+
+ html += '</div>';
+
+ $(document.body).append(html);
+
+ $('#userFlyout').popup().trigger('create').popup("open").on("popupafterclose", function () {
+
+ $(this).off("popupafterclose").remove();
+ });
+ });
+ },
+
+ selectDirectory: function (options) {
+
+ options = options || {};
+
+ options.header = options.header || "Select Media Path";
+
+ var html = '<div data-role="popup" id="popupDirectoryPicker" class="ui-corner-all popup">';
+
+ html += '<div class="ui-corner-top ui-bar-a" style="text-align: center; padding: 0 20px;">';
+ html += '<h3>' + options.header + '</h3>';
+ html += '</div>';
+
+ html += '<div data-role="content" class="ui-corner-bottom ui-content">';
+ html += '<form>';
+ html += '<p>Browse to or enter the folder containing the media. Network paths (UNC) are recommended for optimal playback performance.</p>';
+
+ html += '<div data-role="fieldcontain" style="margin:0;">';
+ html += '<label for="txtDirectoryPickerPath">Current Folder:</label>';
+ html += '<input id="txtDirectoryPickerPath" name="txtDirectoryPickerPath" type="text" onchange="Dashboard.refreshDirectoryBrowser(this.value);" required="required" style="font-weight:bold;" />';
+ html += '</div>';
+
+ html += '<div style="height: 300px; overflow-y: auto;">';
+ html += '<ul id="ulDirectoryPickerList" data-role="listview" data-inset="true" data-auto-enhanced="false"></ul>';
+ html += '</div>';
+
+ html += '<p>';
+ html += '<button type="submit" data-theme="b" data-icon="ok">OK</button>';
+ html += '<button type="button" data-icon="delete" onclick="$(this).parents(\'.popup\').popup(\'close\');">Cancel</button>';
+ html += '</p>';
+ html += '</form>';
+ html += '</div>';
+ html += '</div>';
+
+ $($.mobile.activePage).append(html);
+
+ var popup = $('#popupDirectoryPicker').popup().trigger('create').popup("open").on("popupafterclose", function () {
+
+ $('form', this).off("submit");
+ $(this).off("click").off("popupafterclose").remove();
+
+ }).on("click", ".lnkDirectory", function () {
+
+ var path = this.getAttribute('data-path');
+
+ Dashboard.refreshDirectoryBrowser(path);
+ });
+
+ var txtCurrentPath = $('#txtDirectoryPickerPath', popup);
+
+ if (options.path) {
+ txtCurrentPath.val(options.path);
+ }
+
+ $('form', popup).on('submit', function () {
+
+ if (options.callback) {
+ options.callback($('#txtDirectoryPickerPath', this).val());
+ }
+
+ return false;
+ });
+
+ Dashboard.refreshDirectoryBrowser(txtCurrentPath.val());
+ },
+
+ refreshDirectoryBrowser: function (path) {
+ var page = $.mobile.activePage;
+
+ Dashboard.showLoadingMsg();
+
+ var promise;
+
+ if (path === "Network") {
+ promise = ApiClient.getNetworkComputers();
+ }
+ else if (path) {
+ promise = ApiClient.getDirectoryContents(path, { includeDirectories: true });
+ } else {
+ promise = ApiClient.getDrives();
+ }
+
+ promise.done(function (folders) {
+
+ $('#txtDirectoryPickerPath', page).val(path || "");
+
+ var html = '';
+
+ if (path) {
+
+ var parentPath = path;
+
+ if (parentPath.endsWith('\\')) {
+ parentPath = parentPath.substring(0, parentPath.length - 1);
+ }
+
+ var lastIndex = parentPath.lastIndexOf('\\');
+ parentPath = lastIndex == -1 ? "" : parentPath.substring(0, lastIndex);
+
+ if (parentPath.endsWith(':')) {
+ parentPath += "\\";
+ }
+
+ if (parentPath == '\\') {
+ parentPath = "Network";
+ }
+
+ html += '<li><a class="lnkDirectory" data-path="' + parentPath + '" href="#">..</a></li>';
+ }
+
+ for (var i = 0, length = folders.length; i < length; i++) {
+
+ var folder = folders[i];
+
+ html += '<li><a class="lnkDirectory" data-path="' + folder.Path + '" href="#">' + folder.Name + '</a></li>';
+ }
+
+ if (!path) {
+ html += '<li><a class="lnkDirectory" data-path="Network" href="#">Network</a></li>';
+ }
+
+ $('#ulDirectoryPickerList', page).html(html).listview('refresh');
+
+ Dashboard.hideLoadingMsg();
+
+ }).fail(function () {
+
+ $('#txtDirectoryPickerPath', page).val("");
+ $('#ulDirectoryPickerList', page).html('').listview('refresh');
+
+ Dashboard.hideLoadingMsg();
+ });
+ },
+
+ getPluginSecurityInfo: function () {
+
+ if (!Dashboard.getPluginSecurityInfoPromise) {
+ Dashboard.getPluginSecurityInfoPromise = ApiClient.getPluginSecurityInfo();
+ }
+
+ return Dashboard.getPluginSecurityInfoPromise;
+ },
+
+ resetPluginSecurityInfo: function () {
+ Dashboard.getPluginSecurityInfoPromise = null;
+ },
+
+ ensureHeader: function (page) {
+
+ if (!$('.header', page).length) {
+
+ var isLoggedIn = Dashboard.getCurrentUserId();
+
+ if (isLoggedIn) {
+
+ var promise1 = Dashboard.getCurrentUser();
+ var promise2 = Dashboard.getPluginSecurityInfo();
+
+ $.when(promise1, promise2).done(function (response1, response2) {
+
+ Dashboard.renderHeader(page, response1[0], response2[0]);
+ });
+
+ } else {
+
+ Dashboard.renderHeader(page);
+ }
+ }
+ },
+
+ renderHeader: function (page, user, pluginSecurityInfo) {
+
+ var headerHtml = '';
+ headerHtml += '<div class="header">';
+
+ var isLibraryPage = page.hasClass('libraryPage');
+
+ headerHtml += '<a class="logo" href="index.html">';
+
+ if (page.hasClass('standalonePage')) {
+
+ headerHtml += '<img class="imgLogo" src="css/images/mblogoblackfull.png" />';
+ }
+ else if (isLibraryPage) {
+
+ headerHtml += '<img class="imgLogo" src="css/images/mblogowhitefull.png" />';
+ }
+ headerHtml += '</a>';
+
+ var imageColor = isLibraryPage ? "White" : "Black";
+
+ if (user && !page.hasClass('wizardPage')) {
+ headerHtml += '<div class="headerButtons">';
+ headerHtml += '<a class="imageLink btnCurrentUser" href="#" onclick="Dashboard.showUserFlyout();"><span class="currentUsername">' + user.Name + '</span>';
+
+ if (user.PrimaryImageTag) {
+
+ var url = ApiClient.getUserImageUrl(user.Id, {
+ width: 225,
+ tag: user.PrimaryImageTag,
+ type: "Primary"
+ });
+
+ headerHtml += '<img src="' + url + '" />';
+ } else {
+ headerHtml += '<img src="css/images/currentUserDefault' + imageColor + '.png" />';
+ }
+ headerHtml += '</a>';
+
+ if (pluginSecurityInfo.IsMBSupporter) {
+ headerHtml += '<a class="imageLink" href="supporter.html"><img src="css/images/suppbadge.png" /></a>';
+ }
+ if (user.Configuration.IsAdministrator) {
+ headerHtml += '<a class="imageLink" href="dashboard.html"><img src="css/images/tools' + imageColor + '.png" /></a>';
+ }
+
+ headerHtml += '</div>';
+ }
+
+ headerHtml += '</div>';
+ page.prepend(headerHtml);
+ },
+
+ ensureToolsMenu: function (page) {
+
+ if (!page.hasClass('type-interior')) {
+ return;
+ }
+
+ var sidebar = $('.toolsSidebar', page);
+
+ if (!sidebar.length) {
+
+ var html = '<div class="content-secondary ui-bar-a toolsSidebar">';
+
+ html += '<h1><a href="index.html" class="imageLink" style="margin-left: 0;margin-right: 20px;"> <img src="css/images/home.png" /></a>Tools</h1>';
+
+ html += '<div class="sidebarLinks">';
+
+ var links = Dashboard.getToolsMenuLinks(page);
+
+ for (var i = 0, length = links.length; i < length; i++) {
+
+ var link = links[i];
+
+ if (link.href) {
+
+ if (link.selected) {
+ html += '<a class="selectedSidebarLink" href="' + link.href + '">' + link.name + '</a>';
+ } else {
+ html += '<a href="' + link.href + '">' + link.name + '</a>';
+ }
+
+ }
+ }
+
+ // collapsible
+ html += '</div>';
+
+ // content-secondary
+ html += '</div>';
+
+ $(page).append(html);
+ }
+ },
+
+ getToolsMenuLinks: function (page) {
+
+ var pageElem = page[0];
+
+ return [{
+ name: "Dashboard",
+ href: "dashboard.html",
+ selected: pageElem.id == "dashboardPage"
+ }, {
+ name: "Media Library",
+ href: "library.html",
+ selected: pageElem.id == "mediaLibraryPage"
+ }, {
+ name: "Metadata",
+ href: "metadata.html",
+ selected: pageElem.id == "metadataConfigurationPage" || pageElem.id == "advancedMetadataConfigurationPage" || pageElem.id == "metadataImagesConfigurationPage"
+ }, {
+ name: "Plugins",
+ href: "plugins.html",
+ selected: page.hasClass("pluginConfigurationPage")
+ }, {
+ name: "User Profiles",
+ href: "userProfiles.html",
+ selected: page.hasClass("userProfilesConfigurationPage")
+ }, {
+ name: "Display Settings",
+ href: "uiSettings.html",
+ selected: pageElem.id == "displaySettingsPage"
+ }, {
+ name: "Advanced",
+ href: "advanced.html",
+ selected: pageElem.id == "advancedConfigurationPage"
+ }, {
+ name: "Scheduled Tasks",
+ href: "scheduledTasks.html",
+ selected: pageElem.id == "scheduledTasksPage" || pageElem.id == "scheduledTaskPage"
+ }, {
+ name: "Help",
+ href: "support.html",
+ selected: pageElem.id == "supportPage" || pageElem.id == "logPage" || pageElem.id == "supporterPage" || pageElem.id == "supporterKeyPage"
+ }];
+
+ },
+
+ ensureWebSocket: function (systemInfo) {
+
+ if (!("WebSocket" in window)) {
+ // Not supported by the browser
+ return;
+ }
+
+ if (Dashboard.webSocket) {
+ if (Dashboard.webSocket.readyState === WebSocket.OPEN || Dashboard.webSocket.readyState === WebSocket.CONNECTING) {
+ return;
+ }
+ }
+
+ systemInfo = systemInfo || Dashboard.lastSystemInfo;
+
+ var url = "ws://" + ApiClient.serverHostName + ":" + systemInfo.WebSocketPortNumber + "/mediabrowser";
+
+ var ws = new WebSocket(url);
+
+ ws.onmessage = Dashboard.onWebSocketMessage;
+
+ ws.onopen = function () {
+ setTimeout(function () {
+ $(document).trigger("websocketopen");
+ }, 500);
+ };
+ ws.onerror = function () {
+ setTimeout(function () {
+ $(document).trigger("websocketerror");
+ }, 0);
+ };
+ ws.onclose = function () {
+ setTimeout(function () {
+ $(document).trigger("websocketclose");
+ }, 0);
+ };
+
+ Dashboard.webSocket = ws;
+ },
+
+ resetWebSocketPingInterval: function () {
+
+ if (Dashboard.pingWebSocketInterval) {
+ clearInterval(Dashboard.pingWebSocketInterval);
+ Dashboard.pingWebSocketInterval = null;
+ }
+ Dashboard.pingWebSocketInterval = setInterval(Dashboard.pingWebSocket, 30000);
+ },
+
+ pingWebSocket: function () {
+
+ // Send a ping to the server every so often to try and keep the connection alive
+ if (Dashboard.isWebSocketOpen()) {
+ Dashboard.sendWebSocketMessage("ping");
+ }
+
+ },
+
+ onWebSocketMessage: function (msg) {
+
+ msg = JSON.parse(msg.data);
+
+ if (msg.MessageType === "LibraryChanged") {
+ Dashboard.processLibraryUpdateNotification(msg.Data);
+ }
+ else if (msg.MessageType === "UserDeleted") {
+ Dashboard.validateCurrentUser();
+ }
+ else if (msg.MessageType === "SystemInfo") {
+ Dashboard.updateSystemInfo(msg.Data);
+ }
+ else if (msg.MessageType === "HasPendingRestartChanged") {
+ Dashboard.updateSystemInfo(msg.Data);
+ }
+ else if (msg.MessageType === "UserUpdated") {
+ Dashboard.validateCurrentUser();
+
+ var user = msg.Data;
+
+ if (user.Id == Dashboard.getCurrentUserId()) {
+
+ $('.currentUsername').html(user.Name);
+ }
+ }
+ else if (msg.MessageType === "PackageInstallationCompleted") {
+ Dashboard.showPackageInstallNotification(msg.Data, "completed");
+ Dashboard.refreshSystemInfoFromServer();
+ }
+ else if (msg.MessageType === "PackageInstallationFailed") {
+ Dashboard.showPackageInstallNotification(msg.Data, "failed");
+ Dashboard.refreshSystemInfoFromServer();
+ }
+ else if (msg.MessageType === "PackageInstallationCancelled") {
+ Dashboard.showPackageInstallNotification(msg.Data, "cancelled");
+ Dashboard.refreshSystemInfoFromServer();
+ }
+ else if (msg.MessageType === "PackageInstalling") {
+ Dashboard.showPackageInstallNotification(msg.Data, "progress");
+ Dashboard.refreshSystemInfoFromServer();
+ }
+ else if (msg.MessageType === "ScheduledTaskEndExecute") {
+
+ Dashboard.showTaskCompletionNotification(msg.Data);
+ }
+
+ $(document).trigger("websocketmessage", [msg]);
+ },
+
+ sendWebSocketMessage: function (name, data) {
+
+ var msg = { MessageType: name };
+
+ if (data) {
+ msg.Data = data;
+ }
+
+ msg = JSON.stringify(msg);
+
+ Dashboard.webSocket.send(msg);
+ },
+
+ isWebSocketOpen: function () {
+ return Dashboard.webSocket && Dashboard.webSocket.readyState === WebSocket.OPEN;
+ },
+
+ showTaskCompletionNotification: function (result) {
+
+ var html = '';
+
+ if (result.Status == "Completed") {
+ html += '<img src="css/images/notifications/done.png" class="notificationIcon" />';
+ return;
+ }
+ else if (result.Status == "Cancelled") {
+ html += '<img src="css/images/notifications/info.png" class="notificationIcon" />';
+ return;
+ }
+ else {
+ html += '<img src="css/images/notifications/error.png" class="notificationIcon" />';
+ }
+
+ html += '<span>';
+ html += result.Name + " " + result.Status;
+ html += '</span>';
+
+ var timeout = 0;
+
+ if (result.Status == 'Cancelled') {
+ timeout = 2000;
+ }
+
+ Dashboard.showFooterNotification({ html: html, id: result.Id, forceShow: true, timeout: timeout });
+ },
+
+ showPackageInstallNotification: function (installation, status) {
+
+ var html = '';
+
+ if (status == 'completed') {
+ html += '<img src="css/images/notifications/done.png" class="notificationIcon" />';
+ }
+ else if (status == 'cancelled') {
+ html += '<img src="css/images/notifications/info.png" class="notificationIcon" />';
+ }
+ else if (status == 'failed') {
+ html += '<img src="css/images/notifications/error.png" class="notificationIcon" />';
+ }
+ else if (status == 'progress') {
+ html += '<img src="css/images/notifications/download.png" class="notificationIcon" />';
+ }
+
+ html += '<span style="margin-right: 1em;">';
+
+ if (status == 'completed') {
+ html += installation.Name + ' ' + installation.Version + ' installation completed';
+ }
+ else if (status == 'cancelled') {
+ html += installation.Name + ' ' + installation.Version + ' installation was cancelled';
+ }
+ else if (status == 'failed') {
+ html += installation.Name + ' ' + installation.Version + ' installation failed';
+ }
+ else if (status == 'progress') {
+ html += 'Installing ' + installation.Name + ' ' + installation.Version;
+ }
+
+ html += '</span>';
+
+ if (status == 'progress') {
+
+ var percentComplete = Math.round(installation.PercentComplete || 0);
+
+ html += '<progress style="margin-right: 1em;" max="100" value="' + percentComplete + '" title="' + percentComplete + '%">';
+ html += '' + percentComplete + '%';
+ html += '</progress>';
+
+ if (percentComplete < 100) {
+ var btnId = "btnCancel" + installation.Id;
+ html += '<button id="' + btnId + '" type="button" data-icon="delete" onclick="$(\'' + btnId + '\').button(\'disable\');Dashboard.cancelInstallation(\'' + installation.Id + '\');" data-theme="b" data-inline="true" data-mini="true">Cancel</button>';
+ }
+ }
+
+ var timeout = 0;
+
+ if (status == 'cancelled') {
+ timeout = 2000;
+ }
+
+ var forceShow = status != "progress";
+ var allowHide = status != "progress" && status != 'cancelled';
+
+ Dashboard.showFooterNotification({ html: html, id: installation.Id, timeout: timeout, forceShow: forceShow, allowHide: allowHide });
+ },
+
+ processLibraryUpdateNotification: function (data) {
+
+ var newItems = data.ItemsAdded.filter(function (a) {
+ return !a.IsFolder;
+ });
+
+ if (!Dashboard.newItems) {
+ Dashboard.newItems = [];
+ }
+
+ for (var i = 0, length = newItems.length ; i < length; i++) {
+
+ Dashboard.newItems.push(newItems[i]);
+ }
+
+ if (Dashboard.newItemTimeout) {
+ clearTimeout(Dashboard.newItemTimeout);
+ }
+
+ Dashboard.newItemTimeout = setTimeout(Dashboard.onNewItemTimerStopped, 60000);
+ },
+
+ onNewItemTimerStopped: function () {
+
+ var newItems = Dashboard.newItems;
+
+ newItems = newItems.sort(function (a, b) {
+
+ if (a.PrimaryImageTag && b.PrimaryImageTag) {
+ return 0;
+ }
+
+ if (a.PrimaryImageTag) {
+ return -1;
+ }
+
+ return 1;
+ });
+
+ Dashboard.newItems = [];
+ Dashboard.newItemTimeout = null;
+
+ // Show at most 3 notifications
+ for (var i = 0, length = Math.min(newItems.length, 3) ; i < length; i++) {
+
+ var item = newItems[i];
+
+ var data = {
+ title: "New " + item.Type,
+ body: item.Name,
+ timeout: 6000
+ };
+
+ if (item.PrimaryImageTag) {
+ data.icon = ApiClient.getImageUrl(item.Id, {
+ width: 100,
+ tag: item.PrimaryImageTag
+ });
+ }
+
+ WebNotifications.show(data);
+ }
+ },
+
+ ensurePageTitle: function (page) {
+
+ if (!page.hasClass('type-interior')) {
+ return;
+ }
+
+ var pageElem = page[0];
+
+ if (pageElem.hasPageTitle) {
+ return;
+ }
+
+ var parent = $('.content-primary', page);
+
+ if (!parent.length) {
+ parent = $('.ui-content', page)[0];
+ }
+
+ $(parent).prepend("<h2 class='pageTitle'>" + (document.title || "&nbsp;") + "</h2>");
+
+ pageElem.hasPageTitle = true;
+ },
+
+ setPageTitle: function (title) {
+
+ $('.pageTitle', $.mobile.activePage).html(title);
+
+ if (title) {
+ document.title = title;
+ }
+ },
+
+ metroColors: ["#6FBD45", "#4BB3DD", "#4164A5", "#E12026", "#800080", "#E1B222", "#008040", "#0094FF", "#FF00C7", "#FF870F", "#7F0037"],
+
+ getRandomMetroColor: function () {
+
+ var index = Math.floor(Math.random() * (Dashboard.metroColors.length - 1));
+
+ return Dashboard.metroColors[index];
+ }
+
+};
+
+$(function () {
+
+ var footerHtml = '<div id="footer" class="ui-bar-a">';
+
+ footerHtml += '<div id="nowPlayingBar" style="display:none;">';
+
+ footerHtml += '<button id="previousTrackButton" class="imageButton mediaButton" title="Previous Track" type="button"><img src="css/images/media/previousTrack.png" /></button>';
+
+ footerHtml += '<button id="stopButton" class="imageButton mediaButton" title="Stop" type="button" onclick="MediaPlayer.stop();"><img src="css/images/media/stop.png" /></button>';
+
+ footerHtml += '<button id="nextTrackButton" class="imageButton mediaButton" title="Next Track" type="button"><img src="css/images/media/nextTrack.png" /></button>';
+
+ footerHtml += '<div id="mediaElement"></div>';
+
+ footerHtml += '</div>';
+
+ footerHtml += '<div id="footerNotifications"></div>';
+
+ footerHtml += '</div>';
+
+
+ $(document.body).append(footerHtml);
+});
+
+Dashboard.jQueryMobileInit();
+
+$(document).on('pagebeforeshow', ".page", function () {
+
+ Dashboard.refreshSystemInfoFromServer();
+
+ var page = $(this);
+
+ Dashboard.ensureHeader(page);
+ Dashboard.ensurePageTitle(page);
+
+}).on('pageinit', ".page", function () {
+
+ var page = $(this);
+ var hasLogin = Dashboard.getCurrentUserId();
+
+ if (!hasLogin) {
+
+ if (this.id !== "loginPage" && !page.hasClass('wizardPage')) {
+
+ Dashboard.logout();
+ }
+ }
+
+ else {
+
+ Dashboard.getCurrentUser().done(function (user) {
+
+ if (user.Configuration.IsAdministrator) {
+ Dashboard.ensureToolsMenu(page);
+ }
+ });
+ }
+}); \ No newline at end of file