diff --git a/origo.js b/origo.js index 7a72cfcaf..9c16f6277 100644 --- a/origo.js +++ b/origo.js @@ -50,6 +50,7 @@ const Origo = function Origo(configPath, options = {}) { }, breakPointsPrefix: 'o-media', defaultControls: [ + { name: 'localization' }, { name: 'scaleline' }, { name: 'zoom' }, { name: 'rotate' }, @@ -67,10 +68,30 @@ const Origo = function Origo(configPath, options = {}) { const initControls = (controlDefs) => { const controls = []; + const locControlDefs = controlDefs.shift(); // Localization is first of the defaultControls; + + if (!(locControlDefs.options)) { + locControlDefs.options = { + localeId: 'sv-SE' + }; + } + + // a potential loc query param for Localization needs to be set + const localizationComponent = origoControls.Localization(locControlDefs.options); + localizationComponent.options = locControlDefs.options; + + const searchParams = new URLSearchParams(window.location.search); + if (searchParams.has('loc')) { + const localization = searchParams.get('loc'); + localizationComponent.setLocale(localization); + } + controls.push(localizationComponent); + controlDefs.forEach((def) => { if ('name' in def) { const controlName = titleCase(def.name); const controlOptions = def.options || {}; + controlOptions.localization = localizationComponent; if (controlName in origoControls) { const control = origoControls[controlName](controlOptions); control.options = Object.assign(control.options || {}, controlOptions); diff --git a/scss/_localization.scss b/scss/_localization.scss new file mode 100644 index 000000000..9cdd6cd86 --- /dev/null +++ b/scss/_localization.scss @@ -0,0 +1,3 @@ +.localization-selected { + background-color: #d0e9ff +} diff --git a/scss/origo.scss b/scss/origo.scss index 3b2003b57..2d53f2426 100644 --- a/scss/origo.scss +++ b/scss/origo.scss @@ -50,6 +50,7 @@ @import 'embedded-overlay'; @import 'spinner'; @import 'logger'; + @import 'localization'; } html, diff --git a/scss/ui/helpers/_padding.scss b/scss/ui/helpers/_padding.scss index 5fc65b6f4..7b57e9dbc 100644 --- a/scss/ui/helpers/_padding.scss +++ b/scss/ui/helpers/_padding.scss @@ -60,6 +60,37 @@ padding-top: $padding-large; } +// larger padding +.padding-larger { + padding: $padding-larger; +} + +.padding-top-larger { + padding-top: $padding-larger; +} + +.padding-right-larger { + padding-right: $padding-larger; +} + +.padding-bottom-larger { + padding-bottom: $padding-larger; +} + +.padding-left-larger { + padding-left: $padding-larger; +} + +.padding-x-larger { + padding-left: $padding-larger; + padding-right: $padding-larger; +} + +.padding-y-larger { + padding-bottom: $padding-larger; + padding-top: $padding-larger; +} + // small padding .padding-small { padding: $padding-small; diff --git a/src/controls.js b/src/controls.js index 590bb335d..e3ce402a9 100644 --- a/src/controls.js +++ b/src/controls.js @@ -23,3 +23,4 @@ export { default as Splash } from './controls/splash'; export { default as Zoom } from './controls/zoom'; export { default as Externalurl } from './controls/externalurl'; export { default as Scalepicker } from './controls/scalepicker'; +export { default as Localization } from './controls/localization'; diff --git a/src/controls/localization.js b/src/controls/localization.js new file mode 100644 index 000000000..3eaa1eda9 --- /dev/null +++ b/src/controls/localization.js @@ -0,0 +1,287 @@ +import enLocale from '../loc/en_US.json'; +import svLocale from '../loc/sv_SE.json'; +import { Component, Button, Element, Collapse } from '../ui'; + +const Localization = function Localization(options = {}) { + const { + localeId, // name of an existing locale imported above + fallbackLocaleId = 'sv-SE', + showLocMenu = false + } = options; + + let localizationMenu; + let viewer; + let locMenuId; + + let currentLocaleId = localeId || fallbackLocaleId; + + const locales = { + 'en-US': enLocale, + 'sv-SE': svLocale + }; + + function getLocaleExists(locId) { + return Object.hasOwn(locales, locId); + } + function getStoredLocales() { + return JSON.parse(localStorage.getItem('origoAddedLocales') || '[]'); + } + + const storedLocales = getStoredLocales(); + + // if there are local-stored locales at startup then add these locally + if (storedLocales.length > 0) { + storedLocales.forEach(locale => { + locales[locale.id] = locale; + }); + } + + // if there is an origoSelectedLocale locale specification in local storage then use that + const origoSelectedLocale = localStorage.getItem('origoSelectedLocale'); + if (origoSelectedLocale) { + if (getLocaleExists(origoSelectedLocale)) { + currentLocaleId = origoSelectedLocale; + } + } + + function setStoredLocales(localLocales) { + localStorage.setItem('origoAddedLocales', JSON.stringify(localLocales)); + } + + function drawLocMenu(redraw = false) { + if (redraw) { + document.getElementById(locMenuId).remove(); + } + const mapMenu = viewer.getControlByName('mapmenu'); + // eslint-disable-next-line no-use-before-define + localizationMenu = createLocalizationMenu(); + mapMenu.appendMenuItem(localizationMenu); + localizationMenu.onRender(); + locMenuId = localizationMenu.getId(); + } + + /** + * Adds an array of locales to the locales object and stores them in localStorage. + * + * @param {Array} locs - An array of locale objects to be added. Defaults to an empty array if not provided. + * If a locale id matches a current locale then the current locale will be overwritten. + * @return {boolean} True if locales were added, false otherwise. + */ + function addLocales(locs = []) { + if (locs.length > 0) { + locs.forEach(locale => { // replace if locale already appears to exist (id) + locales[locale.id] = locale; + const matchedLocaleIndex = storedLocales.findIndex(storedLocale => storedLocale.id === locale.id); + if (matchedLocaleIndex > -1) { + storedLocales[matchedLocaleIndex] = locale; + } else storedLocales.push(locale); + }); + setStoredLocales(storedLocales); + drawLocMenu(true); + localizationMenu.expand(); // to illustrate that there's a new locale available, expand the localization menu + return true; + } + return false; + } + + function getLocale(locId = currentLocaleId) { + if (Object.hasOwn(locales, locId)) { + return locales[locId]; + } return false; + } + + function getCurrentLocaleId() { + return currentLocaleId; + } + + // setLocale only changes currentLocaleId if the proposed locale exists and if origoSelectedLocale is not already in local storage. + function setLocale(locId) { + if (getLocaleExists(locId)) { + if (!origoSelectedLocale) { + currentLocaleId = locId; + return true; + } + } return false; + } + + /** + * Adds a new plugin language object to the specified existing locale. + * + * @param {string} locId - The ID of the locale to add the plugin to. + * @param {object} additionObj - The plugin language object to add to the locale. + * @return {boolean} True if the plugin language object was added, false otherwise. + */ + function addPluginToLocale(locId, additionObj) { + if (getLocaleExists(locId)) { + locales[locId].plugins = { ...locales[locId].plugins, ...additionObj }; + return true; + } return false; + } + + const recursiveSearch = ({ obj, objName = undefined, targetParentKey = undefined, targetKey }) => { + if (obj === null || obj === undefined) { + return undefined; + } + let result; + const keys = Object.keys(obj); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + // base case + if ((key === targetKey) && ((targetParentKey === undefined) || (objName === targetParentKey))) { + result = obj[key]; + break; + } + + // recursive case + const value = obj[key]; + if (typeof value === 'object') { + result = recursiveSearch({ obj: value, objName: key, targetParentKey, targetKey }); + if (result !== undefined) { + break; + } + } + // case leaf that doesn't match, next key of the current object.. (iteration in the for-loop) + } + return result; + }; + + function getStringByKeys({ targetParentKey, targetKey }) { + // first call of recursiveSearch may have a targetParentKey and won't have an objName since the top level of the json object + let result = recursiveSearch({ obj: getLocale(currentLocaleId), targetParentKey, targetKey }); + if (!(result) && (localeId !== fallbackLocaleId)) { + result = recursiveSearch({ obj: getLocale(fallbackLocaleId), targetParentKey, targetKey }); + } + return result; + } + + // UI bits start -----> + + function createCollapseHeader() { + const headerButton = Button({ + cls: 'icon-smaller compact no-grow o-tooltip', + icon: '#ic_chevron_right_24px', + iconCls: 'rotate' + }); + + const headerTitleCmp = Component({ + render() { // the title of the dropdown needs localization too + return `