/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/constants.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define('app/constants',["cjt/util/locale"], function(LOCALE) {
"use strict";
var DEFAULT_PRIMARY_DARK = "#08193E"; // $cp-midnight-express from /usr/local/cpanel/base/frontend/jupiter/base_styles/00_configuration/_cp_colors.scss
var DEFAULT_PRIMARY_LIGHT = "#ffffff"; // $cp-white from /usr/local/cpanel/base/frontend/jupiter/base_styles/00_configuration/_cp_colors.scss
return {
// Image
EMBEDDED_SVG: "data:image/svg+xml;base64,",
EMBEDDED_ICO: "data:image/x-icon;base64,",
DATA_URL_PREFIX_REGEX: /^data:[^,]*,/,
// Colors - It would be nice to figure out a way to load these from the scss file, but I cant see a good way without
DEFAULT_PRIMARY_DARK: DEFAULT_PRIMARY_DARK,
DEFAULT_PRIMARY_LIGHT: DEFAULT_PRIMARY_LIGHT,
DEFAULT_COLORS: {
primary: DEFAULT_PRIMARY_DARK,
},
// File upload sizes
MAX_FILE_SIZE: 100 * 1000, // 100 kilobytes
// Tabs
GENERAL_TABS_INFO: {
logos: LOCALE.maketext("Logos"),
colors: LOCALE.maketext("Colors"),
favicon: LOCALE.maketext("Favicon"),
links: LOCALE.maketext("Links"),
"public-contact": LOCALE.maketext("Public Contact"),
},
JUPITER_TAB_ORDER: [
"logos",
"colors",
"favicon",
"links",
"public-contact",
],
JUPITER_TAB_INDEX: {
"logos": 3,
"colors": 4,
"favicon": 5,
"links": 6,
"public-contact": 10,
},
// Routing
DEFAULT_THEME: "jupiter",
DEFAULT_ROUTE: "logos",
};
});
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/services/savedService.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* jshint -W100 */
/* jshint -W089 */
/* jshint -W018 */
define(
'app/services/savedService',[
// Libraries
"angular",
],
function(angular, API, APIREQUEST) {
"use strict";
// Fetch the current application
var app = angular.module("customize.services.savedService", []);
app.factory("savedService", [function() {
var tabs = {};
var DEFAULT = {
dirty: false,
};
// return the factory interface
return {
/**
* Register all the participating tabs
*
* @param {string[]} tabNames
*/
registerTabs: function(tabNames) {
tabNames.forEach(function(tabName) {
tabs[tabName] = angular.copy(DEFAULT);
});
},
/**
* Check if the tab or tabs need to be saved. If you provide a `tabName` it
* only looks at the one tab, if not it checks all the registered tabs.
*
* NOTE: Not all tabs are registered currently. Unregisted tabs return false
* since they are not yet participating in this system.
*
* @param {string} tabName
* @returns {boolean} true if you need to save something, false otherwise.
*/
needToSave: function(tabName) {
if (!tabName) {
// Check all the tabs
return Object.keys(tabs).some(function(tabName) {
return tabs[tabName] ? tabs[tabName].dirty : false;
});
} else {
// Check just the requested tab
return tabs[tabName] ? tabs[tabName].dirty : false;
}
},
/**
* Mark a tab state:
*
* * dirty = true, need to save it.
* * dirty = false, its saved already.
*
* @param {string} tabName
* @param {boolean} dirty
*/
update: function(tabName, dirty) {
if (tabs[tabName]) {
tabs[tabName].dirty = dirty;
}
},
};
}]);
});
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/services/beforeUnloadService.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define('app/services/beforeUnloadService',[
"angular",
],
function(angular) {
"use strict";
/*
* This service is used to send events when the window
* onbeforeunload and onunload event happen.
*
* The service will broadcast the following events:
*
* onBeforeUnload - application code can register for this event on the scope. This
* event can be used to prevent the unload using the `event.preventDefault()` technique.
* Note, not all browsers will show the unload popup. Some also wont show it unless the
* user has interacted with the form. Some browser will not use the custom message.
*
* onUnload - application code can register for this event on the scope. It can be used
* to clean up any resources or cancel outstanding remote calls. You can not cancel the
* unload from this event.
*/
angular.module("customize.services.beforeUnloadService", [])
.factory("beforeUnload", [ "$rootScope", "$window", function($rootScope, $window) {
/**
* Handler for the browser onbeforeunload event.
*
* @param {Event} e
* @returns {string|undefined} - A message to show the user when deciding if they want to cancel the unload.
*/
$rootScope.doBeforeUnload = function(e) {
var config = {};
/**
* @typedef {Config}
* @property {string} prompt - the propt to tell the user.
*/
var event = $rootScope.$broadcast("onBeforeUnload", config);
if (event.defaultPrevented) {
e.preventDefault();
// Note: Some browsers will not show this message, but instead have their own.
e.returnValue = config.prompt || ""; // For some Chrome browsers
return config.prompt;
} else {
delete e["returnValue"]; // For some Chrome browsers
return false;
}
};
/**
* Handler for the browser unload event
*/
$rootScope.doUnload = function() {
$rootScope.$broadcast("onUnload");
};
$window.addEventListener("beforeunload", $rootScope.doBeforeUnload);
$window.addEventListener("onunload", $rootScope.doUnload);
return {};
} ] )
.run(["beforeUnload", function(beforeUnload) {
// Must invoke the service at least once
} ] );
});
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/directives/fileReader.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define(
'app/directives/fileReader',[
"angular",
"cjt/util/locale",
"app/constants",
],
function(angular, LOCALE, CONSTANTS) {
"use strict";
var module = angular.module("customize.directives.fileReader", []);
module.directive("fileReader", [
"$q",
function($q) {
return {
restrict: "A",
require: [ "?ngModel" ],
priority: 20,
link: function(scope, element, attrs, controllers) {
var ngModel = controllers[0];
// To use this attribute directive a parent element must:
// * have an ngModel attribute
// * be an <input> element
// * have an type attribute of 'file'
if (!ngModel || element[0].tagName !== "INPUT" || !attrs["type"] || attrs["type"] !== "file") {
return;
}
ngModel.$render = function() {};
/**
* Helper used to mock behavior in tests
* @param {HtmlFileInput} el
* @returns {File}
*/
scope._getFiles = function(el) {
return el.files;
};
/**
* Helper used to mock behavior in tests
* @returns {number}
*/
scope._getMaxSize = function() {
return CONSTANTS.MAX_FILE_SIZE;
};
/**
* Merge file data to the model object.
*
* @param {ngModelController} ngModel
* @param {File} file
* @param {string} data
* @returns {Object} The updated model.
*/
scope._mergeToModel = function(ngModel, file, data) {
var model = angular.copy(ngModel.$modelValue);
model.filename = file.name;
model.size = file.size;
model.type = file.type;
model.data = data;
delete model.error;
delete model.errorKey;
return model;
};
/**
* Update the model with the error information.
*
* @param {Object} model - Model to store the data into.
* @param {String} message - Human readable message
* @param {String} key - Machine identifier for the error.
*/
scope._setModelError = function(model, message, key) {
model.error = message;
model.errorKey = key;
};
/**
* Converts a file into a base64 encoded string.
*
* @async
* @param {File} file - As defined here: https://developer.mozilla.org/en-US/docs/Web/API/File
* @returns {string} The base64 encoded file contents.
*/
scope._toBase64 = function(file) {
var deferred = $q.defer();
var maxSize = scope._getMaxSize();
var model;
if (file.size > maxSize) {
// We resolve instead of reject to allow the validation to get all
// the info and make decisions based on it. We only exclude the costly
// .data property
model = scope._mergeToModel(ngModel, file, "");
scope._setModelError(model, LOCALE.maketext("The file is larger than the maximum of [numf,_1] kilobytes.", maxSize), "maxsize");
deferred.resolve(model);
return deferred.promise;
}
var reader = new FileReader();
reader.addEventListener("loadend", function(e) {
scope.$apply(function() {
model = scope._mergeToModel(ngModel, file, e.target.result);
deferred.resolve(model);
});
});
reader.addEventListener("error", function(e) {
scope.$apply(function() {
// We resolve instead of reject to allow the validation to get all
// the info and make decisions based on it. We only exclude the costly
// .data property
model = scope._mergeToModel(ngModel, file, "");
scope._setModelError(model, e && e.target ? e.target.error : LOCALE.maketext("An unknown error occurred while reading the file contents."), "readFailed");
deferred.resolve(model);
});
});
try {
reader.readAsDataURL(file);
} catch (error) {
// Catch any synchronous errors.
// We resolve instead of reject to allow the validation to get all
// the info and make decisions based on it. We only exclude the costly
// .data property
model = scope._mergeToModel(ngModel, file, "");
scope._setModelError(model, error ? error : LOCALE.maketext("An unknown error occurred while reading the file contents."), "readFailed");
deferred.resolve(model);
}
return deferred.promise;
};
element.bind("change", function(e) {
scope.$apply(function() {
var el = e.target;
var files = scope._getFiles(el);
if (!files || !files.length) {
return;
}
var file = files[0];
if (file && file.size !== 0) {
scope._toBase64(file)
.then(function(value) {
ngModel.$setViewValue(value); // NOTE: This will trigger the $parsers above.
});
}
return;
});
});
},
};
},
]);
}
);
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/directives/fileType.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define('app/directives/fileType',[
"angular",
], function(angular) {
"use strict";
var module = angular.module("customize.directives.fileType", []);
// This directive validates an <input type="file"> based on the "type" property of a selected file.
// The file-type attribute should contain an expression defining an array of valid types.
module.directive("fileType", [function() {
function checkType(file, types) {
return types.some(function(type) {
return file.type === type;
});
}
return {
restrict: "A",
require: "ngModel",
link: function link($scope, $element, $attrs, $ngModelCtrl) {
$element.bind("change", function() {
var file = this.files[0];
if (file && !checkType(file, $scope.$eval($attrs.fileType))) {
$ngModelCtrl.$setValidity("filetype", false);
} else {
$ngModelCtrl.$setValidity("filetype", true);
}
});
},
};
}]);
});
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/directives/triggerFor.js
# Copyright 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define('app/directives/triggerFor',[
"angular",
], function(angular) {
"use strict";
var module = angular.module("customize.directives.triggerFor", []);
// This directive will trigger a "click" event on another element when the linked element is clicked.
module.directive("triggerFor", [function() {
return {
restrict: "A",
link: function link($scope, $element, $attrs) {
$element.bind("click", function() {
document.querySelector("#" + $attrs.triggerFor).click();
});
},
};
}]);
});
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/directives/fileUpload.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define(
'app/directives/fileUpload',[
"angular",
"cjt/util/locale",
"cjt/core",
"uiBootstrap",
"app/directives/fileReader",
"app/directives/fileType",
"app/directives/triggerFor",
],
function(angular, LOCALE, CJT) {
"use strict";
var module = angular.module("customize.directives.fileUpload", [
"customize.directives.fileReader",
"customize.directives.fileType",
"customize.directives.triggerFor",
"ui.bootstrap",
]);
module.directive("cpFileUpload",
[
"$uibModal",
function($uibModal) {
var TEMPLATE_PATH = "directives/fileUpload.phtml";
var RELATIVE_PATH = "templates/cpanel_customization/" + TEMPLATE_PATH;
return {
replace: true,
require: [ "^form", "ngModel" ],
restrict: "E",
scope: {
id: "@id",
title: "@title",
browseButtonLabel: "@browseButtonLabel",
deleteButtonTitle: "@deleteButtonTitle",
confirmDeleteMessage: "@confirmDeleteMessage",
previewTitle: "@previewTitle",
help: "@help",
fileTypeError: "@fileTypeError",
fileEmptyError: "@fileEmptyError",
fileMaxSizeError: "@fileMaxSizeError",
model: "=ngModel",
types: "=mimeTypes",
previewBgColor: "=previewBgColor",
onDelete: "&onDelete",
onChange: "&onChange",
onReset: "&onReset",
inputClasses: "@inputClass",
previewClasses: "@previewClass",
},
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH,
link: function(scope, element, attrs, controllers) {
scope.LOCALE = LOCALE;
var formController = controllers[0];
var modelController = controllers[1];
if (!scope.inputClasses) {
scope.inputClasses = "col-xs-12 col-sm-8 col-md-8 col-lg-6";
}
if (!scope.previewClasses) {
scope.previewClasses = "col-xs-12 col-sm-4 col-md-4 col-lg-4";
}
/**
* Generate a complete field name for a field in the upload form partial.
*
* @param {string} field - the partial name of the field in the form
* @returns {string}
*/
scope.field = function(field) {
return "file_upload_" + field;
};
/**
* Check if the given form field is pritine.
*
* @param {string} field - the partial name of the field in the form
* @returns {boolean} - true if its pristine, false otherwise.
*/
scope.isPristine = function(field) {
var fieldName = scope.field(field);
var f = formController[fieldName];
if (f) {
return f.$pristine;
}
return true;
};
/**
* Check if the given form field is invalid.
*
* @param {string} field - the partial name of the field in the form
* @returns {boolean} - true if its invalid, false otherwise.
*/
scope.isInvalid = function(field) {
var fieldName = scope.field(field);
var f = formController[fieldName];
if (f) {
return f.$invalid;
}
return true;
};
/**
* Checks if the given field has the given error.
*
* @param {string} field - the partial name of the field in the form
* @param {string} kind - name of the error you are looking for.
* @returns {boolean} - the error object if present, undefined otherwise.
*/
scope.errors = function(field, kind) {
var fieldName = scope.field(field);
var f = formController[fieldName];
if (f) {
return f.$error[kind];
}
return;
};
/**
* Set the field to pristine.
*
* @param {string} field
*/
scope.setPristine = function(field) {
var fieldName = scope.field(field);
var f = formController[fieldName];
if (f) {
f.$setPristine();
}
return;
};
/**
* Creates a clean copy of the model.
*
* @param {FileModel} model
* @returns {FileModel}
*/
function cleanCopy(original) {
var model = angular.copy(original);
model.data = "";
model.filename = "";
model.size = 0;
model.type = "";
delete model.error;
delete model.errorType;
return model;
}
/**
* @typedef FileModel
* @property {string} filename - name of the file to display
* @property {string} data - base 64 encoded file contents
* @property {boolean} saved - true if the file has been saved to the backend, false otherwise.
* @property {string} name - name of the property
*/
/**
* Initiate a delete file operation
* @param {FileModel} model - the data model
*/
scope.delete = function(model) {
scope.deleteLoading = true;
var promise = scope.onDelete(model);
if (promise && promise.then) {
promise.then(function() {
scope.deleteLoading = false;
scope.model = cleanCopy(modelController.$modelValue);
scope.setPristine(scope.id + "_file");
});
} else {
scope.deleteLoading = false;
scope.model = cleanCopy(modelController.$modelValue);
scope.setPristine(scope.id + "_file");
}
};
/**
* Confirms deleting
*
* @method confirmDelete
* @param {FileModel} model - the data model
*/
scope.confirmDelete = function(model) {
if (model.saved) {
var TEMPLATE_PATH = "directives/fileConfirmUploadDelete.phtml";
var RELATIVE_PATH = "templates/cpanel_customization/" + TEMPLATE_PATH;
$uibModal.open({
templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH,
size: "sm",
controller: [
"$scope",
"$uibModalInstance",
function($scope, $uibModalInstance) {
$scope.message = scope.confirmDeleteMessage;
$scope.LOCALE = LOCALE;
$scope.close = function close(confirmed) {
$uibModalInstance.close(confirmed);
};
},
],
}).result.then(function(confirmed) {
if (confirmed) {
scope.delete(model);
}
});
} else {
scope.model = cleanCopy(scope.model);
var elId = scope.field(scope.id + "_file");
var fileEl = angular.element("#" + elId);
fileEl.val(null);
// Clear the error state.
formController[elId].$setValidity("fileSize", true);
formController[elId].$setValidity("fileMaxSize", true);
formController[elId].$setValidity("filetype", true);
formController[elId].$setPristine();
scope.onReset(scope.model);
}
};
scope.change = function() {
scope.onChange(scope.model);
};
},
};
},
]
);
}
);
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/services/customizeService.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* jshint -W100 */
/* jshint -W089 */
/* jshint -W018 */
define(
'app/services/customizeService',[
// Libraries
"angular",
"cjt/util/locale",
// CJT
"cjt/io/api",
"cjt/io/whm-v1-request",
"app/constants",
"cjt/io/whm-v1", // IMPORTANT: Load the driver so its ready
"cjt/services/APICatcher",
],
function(angular, LOCALE, API, APIREQUEST, CONSTANTS) {
"use strict";
var module = angular.module("customize.services.customizeService", [
"cjt2.services.apicatcher",
"cjt2.services.api",
]);
module.factory("customizeService", ["APICatcher", "$q", function(APICatcher, $q) {
// return the factory interface
return {
/**
* @typedef CustomizationModel
* @property {Object} brand - properties related to branding a cPanel instanance.
* @property {Object} brand.logo - properties related to the logos used in the UI.
* @property {string} brand.logo.forLightBackground - base64 encoded logo used when the background color is light.
* @property {string} brand.logo.forDarkBackground - base64 encoded logo used when the background color is dark.
* @property {string} brand.logo.description - title used with the logo for assistive technology
* @property {Object} brand.colors - dictionary of customizable colors for the UI.
* @property {string} brand.colors.primary - hex color used in primary UI features.
* @property {string} brand.colors.link - hex color used in links.
* @property {string} brand.colors.accent - hex color used in accents.
* @property {string} brand.favicon - base64 encoded favicon.
* @property {Object} help - online help related properties.
* @property {string} help.url - URL to the online help for a company.
* @property {Object} documentation - documenation related properties.
* @property {string} documentation.url - URL to the custom documentation site for a company.
*/
/**
* Update the customization options for jupiter based themes
*
* @async
* @param {CustomizationModel} customizations - the updated customizations to store on the server.
* @param {string} theme - the theme name to which the customization is updated. Defaults to CONSTANTS.DEFAULT_THEME.
*/
update: function(customizations, theme) {
if (angular.isUndefined(customizations)) {
return $q.reject(LOCALE.maketext("The customization parameter is missing or not an object."));
}
var apicall = new APIREQUEST.Class().initialize(
"", "update_customizations", {
application: "cpanel",
theme: theme || CONSTANTS.DEFAULT_THEME,
data: JSON.stringify(customizations),
});
return APICatcher.promise(apicall);
},
/**
* Delete a path in the the customization data.
*
* @async
* @param {string} path - optional, The JSONPath to delete
* @param {string} theme - the theme name to which the customization is updated. Defaults to CONSTANTS.DEFAULT_THEME.
*/
delete: function(path, theme) {
var apicall = new APIREQUEST.Class().initialize(
"", "delete_customizations", {
application: "cpanel",
theme: theme || CONSTANTS.DEFAULT_THEME,
path: path,
});
return APICatcher.promise(apicall);
},
/**
* For the provided tabInfo, this method retrieves the tabs list and their associcated information.
*
* @param {Object} tabInfo - The theme specific tab information.
*/
getThemeTabList: function(tabInfo) {
var themeTabs = tabInfo.order.map(tab => {
return {
key: tab, name: CONSTANTS.GENERAL_TABS_INFO[tab], index: tabInfo.index[tab],
};
});
return themeTabs;
},
};
}]);
}
);
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/views/jupiter/logoController.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* jshint -W100 */
define(
'app/views/jupiter/logoController',[
"lodash",
"angular",
"cjt/util/locale",
"app/constants",
"cjt/directives/autoFocus",
"cjt/decorators/growlDecorator",
"app/directives/fileUpload",
"app/services/customizeService",
"app/services/savedService",
],
function(_, angular, LOCALE, CONSTANTS) {
"use strict";
var module = angular.module("customize.views.logoController", [
"customize.services.customizeService",
"customize.directives.fileUpload",
"customize.services.savedService",
]);
var controller = module.controller(
"logoController", [
"$scope",
"customizeService",
"savedService",
"growl",
"growlMessages",
"PAGE",
function($scope, customizeService, savedService, growl, growlMessages, PAGE) {
$scope.saving = false;
// Load the prefetched data from the PAGE object.
var lightLogo = PAGE.data.jupiter.brand.logo.forLightBackground;
var darkLogo = PAGE.data.jupiter.brand.logo.forDarkBackground;
var description = PAGE.data.jupiter.brand.logo.description;
/**
* @typedef FileModel
* @property {string} filename - name of the file to display
* @property {string} data - base 64 encoded file contents
* @property {boolean} saved - true if the file has been saved to the backend, false otherwise.
* @property {string} name - name of the property
*/
/**
* @typedef LogosModel
* @property {FileModel} forLightBackground - storage for the logo use on light backgrounds.
* @property {FileModel} forDarkBackground - storage for the logo used on dark backgrounds.
* @property {string} description - description for use with the logos as the title property.
*/
$scope.model = {
forLightBackground: {
data: lightLogo ? CONSTANTS.EMBEDDED_SVG + lightLogo : "",
filename: lightLogo ? "logo-light.svg" : "",
saved: !!lightLogo,
name: "forLightBackground",
},
forDarkBackground: {
data: darkLogo ? CONSTANTS.EMBEDDED_SVG + darkLogo : "",
filename: darkLogo ? "logo-dark.svg" : "",
saved: !!darkLogo,
name: "forDarkBackground",
},
description: description || "",
};
$scope.MAX_FILE_SIZE = CONSTANTS.MAX_FILE_SIZE;
$scope.LOCALE = LOCALE;
// Watch for changes
$scope.$watch("model.forLightBackground", function() {
savedService.update("logos", $scope.customization.$dirty);
}, true);
$scope.$watch("model.forDarkBackground", function() {
savedService.update("logos", $scope.customization.$dirty);
}, true);
$scope.$watch("model.description", function() {
savedService.update("logos", $scope.customization.$dirty);
}, false);
/**
* @typedef backgroundColors
* @property {string} primaryDark - the background color the logo in full screen.
* @property {string} primaryLight - the background color for the logo in mobile.
*/
$scope.backgroundColors = {
primaryDark: PAGE.data.jupiter.brand.colors.primary || CONSTANTS.DEFAULT_PRIMARY_DARK,
// NOTE: There does not seem to be an override for mobile background for the logo
primaryLight: CONSTANTS.DEFAULT_PRIMARY_LIGHT,
};
/**
* Save the logo data from the tab.
*
* @param {FormController} $formCtrl
*/
$scope.save = function($formCtrl) {
growlMessages.destroyAllMessages();
if (!$formCtrl.$valid) {
growl.error(LOCALE.maketext("The current customization is invalid."));
return;
}
if ($scope.saving) {
growl.warning(LOCALE.maketext("The system is busy. Try again once the current operation is complete."));
return;
}
$scope.saving = true;
var forDarkBackground = $scope.model.forDarkBackground.data;
if (forDarkBackground) {
forDarkBackground = forDarkBackground.replace(CONSTANTS.DATA_URL_PREFIX_REGEX, "");
}
var forLightBackground = $scope.model.forLightBackground.data;
if (forLightBackground) {
forLightBackground = forLightBackground.replace(CONSTANTS.DATA_URL_PREFIX_REGEX, "");
}
customizeService.update({
brand: {
logo: {
forLightBackground: forLightBackground,
forDarkBackground: forDarkBackground,
description: $scope.model.description,
},
},
}).then(function(update) {
if (forDarkBackground) {
$scope.model.forDarkBackground.filename = "logo-dark.svg";
$scope.model.forDarkBackground.saved = true;
// Update the initial data
PAGE.data.jupiter.brand.logo.forDarkBackground = forDarkBackground;
}
if (forLightBackground) {
$scope.model.forLightBackground.filename = "logo-light.svg";
$scope.model.forLightBackground.saved = true;
// Update the initial data
PAGE.data.jupiter.brand.logo.forLightBackground = forLightBackground;
}
// Update the initial data
PAGE.data.jupiter.brand.logo.description = $scope.model.description;
$formCtrl.$setPristine();
savedService.update("logos", false);
growl.success(LOCALE.maketext("The system successfully updated the logos."));
}).catch(function(error) {
growl.error(LOCALE.maketext("The system failed to update your logos."));
}).finally(function() {
$scope.saving = false;
});
};
/**
* Evaluate the state of the inputs and update the $pristine state of the form.
*
* NOTE:
* angular.js does not reevalute the form.$isPristine flag when the child inputs
* are set to pristine individually. We must loop over the list of controls ourselves
* and the set this property.
* @param {FormController} $formCtrl
*/
var updateFormState = function($formCtrl) {
var controls = ["file_upload_logo_dark_file", "file_upload_logo_light_file", "icon_description"];
var isPristine = true; // Assume pristine, unless there is evidence otherwise.
controls.forEach(function(inputName) {
if ($formCtrl[inputName].$dirty) {
isPristine = false;
}
});
if (isPristine) {
$formCtrl.$setPristine();
}
};
/**
* Reset the logo to a pristine state after a delete before saving.
*
* @param {FormController} $formCtrl
* @param {string} which
*/
$scope.reset = function($formCtrl, which) {
growlMessages.destroyAllMessages();
switch (which) {
case "forDarkBackground":
$formCtrl.file_upload_logo_dark_file.$setPristine();
break;
case "forLightBackground":
$formCtrl.file_upload_logo_light_file.$setPristine();
break;
}
updateFormState($formCtrl);
savedService.update("logos", $formCtrl.$dirty);
};
/**
* Remove the specific logo from the
* @param {FormController} $formCtrl
* @param {string} which - the name of the image field to delete
*/
$scope.delete = function($formCtrl, which) {
growlMessages.destroyAllMessages();
if ($scope.saving) {
growl.warning(LOCALE.maketext("The system is busy. Try again once the current operation is complete."));
return;
}
$scope.saving = true;
return customizeService.delete("brand.logo." + which)
.then(function(update) {
$scope.model[which].saved = false;
$scope.model[which].data = "";
$scope.model[which].filename = "";
// Update the initial data
PAGE.data.jupiter.brand.logo[which] = "";
// Reset the part of the form that was persisted.
switch (which) {
case "forDarkBackground":
$formCtrl.file_upload_logo_dark_file.$setPristine();
break;
case "forLightBackground":
$formCtrl.file_upload_logo_light_file.$setPristine();
break;
}
savedService.update("logos", $formCtrl.$dirty);
growl.success(LOCALE.maketext("The system successfully removed the logo."));
})
.catch(function(error) {
growl.error(LOCALE.maketext("The system failed to remove the logo."));
})
.finally(function() {
$scope.saving = false;
});
};
},
]
);
return controller;
}
);
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/views/jupiter/faviconController.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* jshint -W100 */
define(
'app/views/jupiter/faviconController',[
"lodash",
"angular",
"cjt/util/locale",
"app/constants",
"cjt/decorators/growlDecorator",
"app/directives/fileUpload",
"app/services/customizeService",
"app/services/savedService",
],
function(_, angular, LOCALE, CONSTANTS) {
"use strict";
var module = angular.module("customize.views.faviconController", [
"customize.directives.fileUpload",
"customize.services.customizeService",
"customize.services.savedService",
"customize.directives.fileUpload",
]);
// set up the controller
var controller = module.controller(
"faviconController", [
"$scope",
"customizeService",
"savedService",
"growl",
"growlMessages",
"PAGE",
function($scope, customizeService, savedService, growl, growlMessages, PAGE) {
$scope.saving = false;
$scope.MAX_FILE_SIZE = CONSTANTS.MAX_FILE_SIZE;
$scope.LOCALE = LOCALE;
// Load the prefetched data from the PAGE object.
var favicon = PAGE.data.jupiter.brand.favicon;
/**
* @typedef FileModel
* @private
* @property {string} filename - name of the file to display
* @property {string} data - base 64 encoded file contents
* @property {boolean} saved - true if the file has been saved to the backend, false otherwise.
*/
/**
* @typedef FaviconModel
* @property {FileModel} favicon - storage for the favicon for the site.
* @property {FileModel} forDarkBackground - storage for the logo used on dark backgrounds.
* @property {string} description - description for use with the logos as the title property.
*/
$scope.model = {
favicon: {
data: favicon ? CONSTANTS.EMBEDDED_ICO + favicon : "",
filename: favicon ? "favicon.ico" : "",
saved: !!favicon,
},
};
// Watch for changes
$scope.$watch("model.favicon", function() {
savedService.update("favicon", $scope.customization.$dirty);
}, true);
/**
* Save the favicon data from the tab.
*
* @async
* @param {FormController} $formCtrl
*/
$scope.save = function($formCtrl) {
growlMessages.destroyAllMessages();
if (!$formCtrl.$valid) {
growl.error(LOCALE.maketext("The current customization is invalid."));
return;
}
if ($scope.saving) {
growl.warning(LOCALE.maketext("The system is busy. Try again once the current operation is complete."));
return;
}
$scope.saving = true;
var favicon = $scope.model.favicon.data.replace(CONSTANTS.DATA_URL_PREFIX_REGEX, "");
return customizeService.update({
brand: {
favicon: favicon,
},
}).then(function(update) {
$scope.model.favicon.filename = "favicon.ico";
$scope.model.favicon.saved = true;
// Update the initial data
PAGE.data.jupiter.brand.favicon = favicon;
$formCtrl.$setPristine();
savedService.update("favicon", false);
growl.success(LOCALE.maketext("The system successfully updated the favicon."));
}).catch(function(error) {
growl.error(LOCALE.maketext("The system failed to update the favicon."));
}).finally(function() {
$scope.saving = false;
});
};
/**
* Reset the favicon to a pristine state after a delete before saving.
*
* @param {FormController} $formCtrl
*/
$scope.reset = function($formCtrl) {
growlMessages.destroyAllMessages();
$formCtrl.file_upload_favicon_file.$setPristine();
savedService.update("favicons", false);
};
/**
* Remove the favorite icon from the customizations.
*
* @param {FormController} $formCtrl - the file to delete from the persistance layer.
*/
$scope.delete = function($formCtrl) {
growlMessages.destroyAllMessages();
if ($scope.saving) {
growl.warning(LOCALE.maketext("The system is busy. Try again once the current operation is complete."));
return;
}
$scope.saving = true;
return customizeService.delete("brand.favicon")
.then(function(update) {
$scope.model.favicon.saved = false;
$scope.model.favicon.data = "";
$scope.model.favicon.filename = "";
// Update the initial data
PAGE.data.jupiter.brand.favicon = "";
$formCtrl.$setPristine();
savedService.update("favicon", false);
growl.success(LOCALE.maketext("The system successfully removed the custom favicon and restored the default [asis,cPanel] favicon."));
})
.catch(function(error) {
growl.error(LOCALE.maketext("The system failed to remove the custom favicon."));
})
.finally(function() {
$scope.saving = false;
});
};
},
]
);
return controller;
}
);
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/views/jupiter/linksController.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* jshint -W100 */
define('app/views/jupiter/linksController',[
"lodash",
"angular",
"cjt/util/locale",
"cjt/directives/autoFocus",
"cjt/decorators/growlDecorator",
"app/services/customizeService",
"app/services/savedService",
], function(_, angular, LOCALE) {
"use strict";
var module = angular.module("customize.views.linksController", [
"customize.services.customizeService",
"customize.services.savedService",
]);
var controller = module.controller("linksController", [
"$scope",
"customizeService",
"savedService",
"growl",
"growlMessages",
"PAGE",
function(
$scope,
customizeService,
savedService,
growl,
growlMessages,
PAGE
) {
$scope.saving = false;
$scope.urlRegex =
/^(https?):\/\/(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\\d:]+])(?::\\d+)?(?:\/[^?#]*)?(?:\\?[^#]*)?(?:#.*)?$/i;
// Preload links
$scope.model = {
help: PAGE.data.jupiter.help
? angular.copy(PAGE.data.jupiter.help)
: { url: "" },
documentation: PAGE.data.jupiter.documentation
? angular.copy(PAGE.data.jupiter.documentation)
: { url: "" },
};
// Save initial values
$scope.initialHelpLink = $scope.model.help["url"];
$scope.initialDocumentationLink = $scope.model.documentation["url"];
// Watch for changes
$scope.$watchGroup(["model.help.url", "model.documentation.url"], function(newValues) {
var helpLinkChanged = newValues[0] !== $scope.initialHelpLink;
var documentationLinkChanged = newValues[1] !== $scope.initialDocumentationLink;
// If the links match their original state, make the form pristine again
if (!helpLinkChanged && !documentationLinkChanged) {
growlMessages.destroyAllMessages();
savedService.update("links", false);
$scope.customization.$setPristine();
} else {
savedService.update("links", $scope.customization.$dirty);
}
}, false);
/**
* Saves changes to branding customization
* Persist the customization form if it is valid
* @method save
* @param {Object} $formCtrl Form control
*/
$scope.save = function($formCtrl) {
growlMessages.destroyAllMessages();
if (!$formCtrl.$valid) {
growl.error(
LOCALE.maketext("The current customization is invalid.")
);
return;
}
if ($scope.saving) {
growl.warning(
LOCALE.maketext(
"The system is busy. Try again once the current operation is complete."
)
);
return;
}
$scope.saving = true;
customizeService
.update({
documentation: {
url: $scope.model.documentation.url,
},
help: {
url: $scope.model.help.url,
},
})
.then(function(response) {
// For subsequent loads of links tab, we need to update PAGE to reflect changes
PAGE.data.jupiter.documentation.url =
$scope.model.documentation.url;
PAGE.data.jupiter.help.url = $scope.model.help.url;
$formCtrl.$setPristine();
savedService.update("links", false);
growl.success(
LOCALE.maketext(
"The system successfully updated your links."
)
);
})
.catch(function(error) {
growl.error(
LOCALE.maketext(
"The system failed to update your links."
)
);
})
.finally(function() {
$scope.saving = false;
});
};
},
]);
return controller;
});
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/views/jupiter/colorsController.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
require.config({
paths: {
"jquery-minicolors": "../../../libraries/jquery-minicolors/2.1.7/jquery.minicolors",
"angular-minicolors": "../../../libraries/angular-minicolors/0.0.11/angular-minicolors",
},
shims: {
"jquery-minicolors": {
depends: [ "jquery" ],
},
"angular-minicolors": {
depends: [ "jquery-minicolors" ],
},
},
});
define(
'app/views/jupiter/colorsController',[
"lodash",
"angular",
"jquery",
"app/constants",
"cjt/util/locale",
"cjt/decorators/growlDecorator",
"angular-minicolors",
],
function(_, angular, jquery, CONSTANTS, LOCALE) {
"use strict";
var module = angular.module("customize.views.colorsController", [
"customize.services.customizeService",
"minicolors",
]);
module.config([
"minicolorsProvider",
function(minicolorsProvider) {
angular.extend(minicolorsProvider.defaults, {
control: "wheel",
position: "bottom left",
letterCase: "uppercase",
theme: "bootstrap",
});
}]
);
// set up the controller
var controller = module.controller(
"colorsController", [
"$scope",
"customizeService",
"growl",
"growlMessages",
"savedService",
"PAGE",
function($scope, customizeService, growl, growlMessages, savedService, PAGE) {
$scope.saving = false;
$scope.restoring = false;
$scope.hexColorRegex = "^#[0-9A-Fa-f]{6}";
/**
* @typedef ColorBrandPartial
* @type {object}
* @property {object} brand
* @property {ColorsModel} brand.colors
*/
/**
* @typedef ColorsModel
* @type {object}
* @property {string} primary - CSS color for the left menu
* @property {string} link - CSS color for links - NOT IMPLEMENT YET
* @property {string} accent - CSS color for various accents in the product. - NOT IMPLEMENT YET
*/
// Load the prefetched data from the PAGE object.
$scope.model = {
colors: {},
defaults: angular.copy(CONSTANTS.DEFAULT_COLORS),
};
$scope.$watch("model.colors", function() {
savedService.update("colors", $scope.customization.$dirty);
}, true);
/**
* Blend the defaults and initial settings to get the current configuration.
*
* @param {Dictionary<string, string>} initial - initial colors from persistance layer.
* @param {Dictionary<string, string>} defaults - default colors for cPanel.
* @returns
*/
function blendColors(initial, defaults) {
var copy = Object.assign({}, initial);
Object.keys(initial).forEach(function(key) {
if (copy[key] === "" || copy[key] === undefined || copy[key] === null) {
// Ignore empty keys so we keep the defaults.
delete copy[key];
}
});
var colors = Object.assign({}, defaults);
return Object.assign(colors, copy);
}
$scope.model.colors = blendColors( PAGE.data.jupiter.brand.colors, CONSTANTS.DEFAULT_COLORS );
/**
* Save the updates to the persistance layer.
*
* @param {FormController} $formCtrl
*/
$scope.save = function($formCtrl) {
if (!$formCtrl.$valid) {
growl.error(LOCALE.maketext("The current customization is invalid."));
return;
}
growlMessages.destroyAllMessages();
if ($scope.saving || $scope.restoring) {
growl.warning(LOCALE.maketext("The system is busy. Try again once the current operation is complete."));
return;
}
$scope.saving = true;
/** @type {ColorBrandPartial} */
var partial = {
brand: { colors: $scope.model.colors },
};
customizeService.update(partial).then(function(update) {
// Update the local init values since we updated the server
PAGE.data.jupiter.brand.colors = angular.copy($scope.model.colors);
savedService.update("colors", false);
$formCtrl.$setPristine();
growl.success(LOCALE.maketext("The system successfully updated the brand colors."));
}).catch(function(error) {
growl.error(LOCALE.maketext("The system failed to update the brand colors."));
}).finally(function() {
$scope.saving = false;
});
};
/**
* Remove the brand colors from the customization.
*
* @param {FormController} $formCtrl
*/
$scope.reset = function($formCtrl) {
growlMessages.destroyAllMessages();
if ($scope.saving || $scope.restoring) {
growl.warning(LOCALE.maketext("The system is busy. Try again once the current operation is complete."));
return;
}
$scope.restoring = true;
return customizeService.delete("brand.colors")
.then(function(update) {
$scope.model.colors = blendColors( {}, CONSTANTS.DEFAULT_COLORS ); // Reset to defaults
// Update the local init values since we updated the server
PAGE.data.jupiter.brand.colors = angular.copy($scope.model.colors);
savedService.update("links", false);
$formCtrl.$setPristine();
growl.success(LOCALE.maketext("The system successfully restored the brand colors to the default."));
})
.catch(function(error) {
growl.error(LOCALE.maketext("The system failed to restore the brand colors to the default."));
})
.finally(function() {
$scope.restoring = false;
});
};
},
]
);
return controller;
}
);
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/services/contactService.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* jshint -W100 */
/* jshint -W089 */
/* jshint -W018 */
define(
'app/services/contactService',[
// Libraries
"angular",
// CJT
"cjt/io/api",
"cjt/io/whm-v1-request",
"cjt/io/whm-v1", // IMPORTANT: Load the driver so its ready
"cjt/services/APICatcher",
],
function(angular, API, APIREQUEST) {
"use strict";
// Fetch the current application
var app = angular.module("customize.services.contactService", [
"cjt2.services.apicatcher",
"cjt2.services.api",
]);
app.factory("contactService", ["APICatcher", function(APICatcher) {
// return the factory interface
return {
/**
* Update the contact data for the company.
*
* @param {ContactInfo} contactInfo
* @returns
*/
setPublicContact: function(contactInfo) {
var apicall = new APIREQUEST.Class().initialize(
"", "set_public_contact", contactInfo
);
return APICatcher.promise(apicall);
},
};
},
]);
});
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/views/publicContactController.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* jshint -W100 */
( function() {
"use strict";
define(
'app/views/publicContactController',[
"lodash",
"angular",
"cjt/util/locale",
"uiBootstrap",
"app/services/contactService",
"cjt/decorators/growlAPIReporter",
"app/services/savedService",
],
function(_, angular, LOCALE) {
/**
* @typedef ContactModel
* @property {string} name - the name of the company
* @property {string} url - the url to reach the company at.
*/
// Create the module
var app = angular.module(
"customize.views.publicContactController", [
"customize.services.contactService",
"customize.services.savedService",
]
);
app.value("PAGE", PAGE);
var PAGEDATA = PAGE.data;
// It might be nice for the form model to be saved in this
// scope; that way we could restore the form state between
// loads of this view. AngularJS, though, doesn’t seem to like
// to create FormController objects that are $dirty from the
// get-go. We’d have to hook into some sort of post-render event,
// and AngularJS *really* seems to want to stay away from that
// kind of logic.
//
var SAVED_PCDATA = angular.copy( PAGEDATA.public_contact );
// Setup the controller
var controller = app.controller(
"publicContactController",
[
"$scope",
"contactService",
"savedService",
"growl",
"growlMessages",
function($scope, contactService, savedService, growl, growlMessages) {
angular.extend(
$scope,
{
has_root: !!PAGEDATA.has_root,
pcdata: angular.copy(SAVED_PCDATA),
/**
* Save the public contacts.
*
* @param {*} form
* @returns
*/
doSubmit: function doSubmit(form) {
var scope = this;
growlMessages.destroyAllMessages();
return contactService.setPublicContact(this.pcdata).then( function() {
angular.extend(SAVED_PCDATA, scope.pcdata);
form.$setPristine();
savedService.update("public-contact", false);
growl.success(LOCALE.maketext("The public can now view the information that you provided in this form."));
} );
},
/**
* Reset the form to it intial state.
*/
resetForm: function resetForm(form) {
growlMessages.destroyAllMessages();
angular.extend(this.pcdata, SAVED_PCDATA);
form.$setPristine();
},
}
);
// Watch for changes
$scope.$watchGroup([ "pcdata.name", "pcdata.url" ], function() {
if (!$scope.public_contact_form) {
return;
}
savedService.update("public-contact", $scope.public_contact_form.$dirty);
}, true);
},
]
);
return controller;
}
);
}());
/*
# cpanel - whostmgr/docroot/templates/cpanel_customization/index.js
# Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* jshint -W100 */
(function() {
"use strict";
define(
'app/index',[
"lodash",
"angular",
"cjt/core",
"cjt/util/locale",
"app/constants",
"cjt/modules",
"uiBootstrap",
"cjt/directives/callout",
"app/services/savedService",
"app/services/beforeUnloadService",
// Jupiter Views
"app/views/jupiter/logoController",
"app/views/jupiter/faviconController",
"app/views/jupiter/linksController",
"app/views/jupiter/colorsController",
// Shared Views
"app/views/publicContactController",
],
function(_, angular, CJT, LOCALE, CONSTANTS) {
return function() {
angular.module("App", [
"cjt2.config.whm.configProvider", // This needs to load first
"ngRoute",
"ui.bootstrap",
"angular-growl",
"cjt2.whm",
"customize.services.savedService",
"customize.services.beforeUnloadService",
// Jupiter
"customize.views.logoController",
"customize.views.faviconController",
"customize.views.linksController",
"customize.views.colorsController",
// Shared
"customize.views.publicContactController",
]);
var app = require(
[
"cjt/bootstrap",
// Application Modules
"uiBootstrap",
// Jupiter Views
"app/views/jupiter/logoController",
"app/views/jupiter/faviconController",
"app/views/jupiter/linksController",
"app/views/jupiter/colorsController",
// Shared Views
"app/views/publicContactController",
// Services
"app/services/contactService",
"app/services/customizeService",
], function(BOOTSTRAP) {
var app = angular.module("App");
app.value("PAGE", PAGE);
app.value("firstLoad", {
branding: true,
});
app.controller("BaseController", [
"$rootScope",
"$scope",
"$route",
"$location",
"growl",
"growlMessages",
"$timeout",
"savedService",
"customizeService",
function($rootScope, $scope, $route, $location, growl, growlMessages, $timeout, savedService, customizeService) {
CONSTANTS.DEFAULT_THEME = PAGE.data.default_theme;
$scope.loading = false;
$scope.selectedThemeTabList = [];
savedService.registerTabs(CONSTANTS.JUPITER_TAB_ORDER);
// Convenience functions so we can track changing views for loading purposes
$rootScope.$on("$routeChangeStart", function() {
if (savedService.needToSave()) {
$scope.reportNotSaved();
event.preventDefault();
}
$scope.loading = true;
});
$rootScope.$on("$routeChangeSuccess", function() {
$scope.loading = false;
});
$rootScope.$on("$routeChangeError", function() {
$scope.loading = false;
});
$rootScope.$on("onBeforeUnload", function(e, config) {
if (savedService.needToSave()) {
config.prompt = LOCALE.maketext("The current tab has unsaved changes. You should save the changes before you navigate to another tab.");
e.preventDefault();
return;
}
delete e["returnValue"];
});
/**
* Select a tab by its key. See the indexes for each tab in the ./index.html.tt file.
*
* @param {number} index
*/
$scope.selectTab = function(index) {
var tabInfo = getTabInfo();
tabInfo.lastTab = index;
var activeIndex = tabInfo.index[index];
$scope.activeTab = activeIndex;
};
/**
* Navigate to the selected path and change the tab being viewed.
*
* @param {string} path
*/
$scope.goTo = function(path) {
var tabInfo = getTabInfo();
tabInfo.lastTab = path;
$scope.selectTab(path);
$location.path(path);
$scope.selectTab(path);
$scope.currentTabName = path;
};
/**
* Growl a message about not changing tabs.
*/
$scope.reportNotSaved = function() {
growlMessages.destroyAllMessages();
growl.error(LOCALE.maketext("The current tab has unsaved changes. You should save the changes before you navigate to another tab."));
};
/**
* Do not let the user navigate away from a tab if there
* is unsaved work.
*/
$scope.preventDeselect = function($event) {
if (!$event || !$event.target) {
return;
}
var tabName = findTabName(angular.element($event.target));
if (savedService.needToSave($scope.currentTabName)) {
if (tabName !== $scope.currentTabName) {
$scope.reportNotSaved();
}
$event.preventDefault();
}
return;
};
/**
* Dig thru the els to find the parent with the data-tab-name attribute
*
* @private
* @param {JqLiteHtmlElement} el
* @returns {string}
*/
function findTabName(el) {
var name = el.attr("data-tab-name");
if (name) {
return name;
}
var parent = el.parent();
if (parent) {
return findTabName(parent);
}
return;
}
/**
* @typedef ThemeInfo - a set of properties used to configure the tabs for a given theme.
* @property {string[]} order - the list of tab names in the order they are shown.
* @property {Dictionayr<string,number>} index - the lookup table of tab names to tab indexes.
* @property {string} lastTab - the previously selected tab.
*/
/**
* @typedef ThemesInfo - lookup table of tab configuration per theme.
* @property {ThemeInfo} jupiter - the jupiter theme configuraiton
*/
/**
* @name byTheme
* @scope
* @type {ThemesInfo}
*/
$scope.byTheme = {
jupiter: {
order: CONSTANTS.JUPITER_TAB_ORDER,
index: CONSTANTS.JUPITER_TAB_INDEX,
lastTab: "",
},
};
$scope.selectedTheme = CONSTANTS.DEFAULT_THEME;
/**
* Handle theme changes.
*/
$scope.onThemeSelect = function() {
initTab();
};
/**
* Retrieve the tab information for the current selected theme.
*
* @returns {ThemeInfo}
*/
function getTabInfo() {
return $scope.byTheme[$scope.selectedTheme];
}
/**
* Check if the theme matches the current theme.
*
* @param {string} themeName
* @returns {boolean} true when they are the same, false otherwise.
*/
$scope.isTheme = function(themeName) {
return $scope.selectedTheme === themeName;
};
/**
* Check if the tab with the tabName is the active tab.
*
* @param {String} tabName
* @returns {boolean} true when the tab is active, false otherwise.
*/
$scope.isActive = function(tabName) {
return $scope.activeTab === tabName;
};
/**
* Get the active tab name.
*
* @returns {string} The name of the active tab.
*/
$scope.getActiveTab = function() {
return $scope.activeTab;
};
/**
* The name of the currently selected tab
* @type {string}
*/
$scope.currentTabName = "";
/**
* Select the initial tab.
*/
function initTab() {
var tabInfo = getTabInfo();
$scope.selectedThemeTabList = customizeService.getThemeTabList(tabInfo);
var tabName = tabInfo.lastTab || tabInfo.order[0];
$timeout(function() {
$scope.goTo(tabName);
});
}
initTab();
},
]);
app.config(["$routeProvider",
function($routeProvider) {
// Setup a route - copy this to add additional routes as necessary
$routeProvider.when("/logos", {
controller: "logoController",
templateUrl: CJT.buildFullPath("cpanel_customization/views/jupiter/logo.ptt"),
});
$routeProvider.when("/colors", {
controller: "colorsController",
templateUrl: CJT.buildFullPath("cpanel_customization/views/jupiter/colors.ptt"),
});
$routeProvider.when("/links", {
controller: "linksController",
templateUrl: CJT.buildFullPath("cpanel_customization/views/jupiter/links.ptt"),
});
$routeProvider.when("/favicon", {
controller: "faviconController",
templateUrl: CJT.buildFullPath("cpanel_customization/views/jupiter/favicon.ptt"),
});
$routeProvider.when("/public-contact", {
controller: "publicContactController",
templateUrl: CJT.buildFullPath("cpanel_customization/views/publicContact.ptt"),
});
// default route
$routeProvider.otherwise({
"redirectTo": "/" + CONSTANTS.DEFAULT_ROUTE,
});
},
]);
// Initialize the application
BOOTSTRAP();
});
return app;
};
}
);
})();