From 019b6c8ce7788d4b74ab5e102276eb0b0506014a Mon Sep 17 00:00:00 2001 From: Mikko Tapionlinna Date: Wed, 4 Sep 2024 15:44:32 +0300 Subject: [PATCH] UHF-10464: Update editor with language settings and lates styles --- .../assets/css/cookie-banner-admin-ui.css | 59 ++++- .../assets/js/cookie-banner-admin-ui.js | 204 +++++++++++++++--- .../assets/json/siteSettings.schema.json | 29 +-- .../src/Form/HdbtCookieBannerForm.php | 2 +- 4 files changed, 252 insertions(+), 42 deletions(-) diff --git a/modules/hdbt_cookie_banner/assets/css/cookie-banner-admin-ui.css b/modules/hdbt_cookie_banner/assets/css/cookie-banner-admin-ui.css index b7432911b..88d34538a 100644 --- a/modules/hdbt_cookie_banner/assets/css/cookie-banner-admin-ui.css +++ b/modules/hdbt_cookie_banner/assets/css/cookie-banner-admin-ui.css @@ -1,18 +1,73 @@ -#editor_holder { - margin: 16px; +/* Set max width to form */ +.hdbt-cookie-banner { + max-width: 1200px; } +/* Not all form controls should be 100% wide, for example select elements are better at auto */ .form-control { width: auto; } +/* Only inputs should be 100% wide */ input.form-control { width: 100%; } +/* Hide unused cruft */ .json-editor-btntype-deletelast, .json-editor-btntype-deleteall, .h3.je-object__title:has([style*="display: none;"]+.sr-only), .btn-group.je-object__controls { display: none; } + +/* First level wells should not have grey background */ +[data-schemapath="root"]>.well { + background: transparent; + border: 0 none; + padding: 0; + box-shadow: none; +} + +/* Handle button width with grandparent grid that is inherited with subgrid */ +div:has( > .je-object__container) { + display: grid; + grid-template-columns: [column-1] 1fr [column-2] auto; +} + +/* Add separator line between elements */ +.je-object__container + .je-object__container { + border-top: 1px solid #ccc; +} + +/* Inherit the grid from grandparent and set grid rows here */ +:not([data-schemaid="root"]).je-object__container { + grid-column: span 2; + display: grid; + grid-template-columns: subgrid; + grid-template-rows: [row-1] 1fr [row-2] auto; +} + +/* By default, take two columns on all elements */ +.je-object__container > * { + grid-column: 1 / span 2; +} + +/* Title should be 1 column wide */ +.je-object__container > .je-object__title { + grid-column: 1 / span 1; + grid-row: 1; +} + +/* Btn group should be 1 column wide and on the first row */ +.je-object__container > .btn-group { + grid-column: 2 / span 1; + grid-row: 1; + margin-top: 18px; +} + +/* JSON Textarea size */ +textarea { + width: 100%; + height: 90dvh; +} diff --git a/modules/hdbt_cookie_banner/assets/js/cookie-banner-admin-ui.js b/modules/hdbt_cookie_banner/assets/js/cookie-banner-admin-ui.js index 74a6d77f7..4ac75a87f 100644 --- a/modules/hdbt_cookie_banner/assets/js/cookie-banner-admin-ui.js +++ b/modules/hdbt_cookie_banner/assets/js/cookie-banner-admin-ui.js @@ -1,34 +1,119 @@ (function (Drupal, drupalSettings) { Drupal.behaviors.cookieBannerAdminUi = { attach: function attach() { - const element = document.getElementById('editor_holder'); - const textarea = document.getElementById('edit-site-settings'); - let isUpdatingFromEditor = false; // Flag to prevent loop - - try { - const schema = JSON.parse(drupalSettings.cookieBannerAdminUi.siteSettingsSchema); - const startval = JSON.parse(textarea.value); - - const options = { - theme: 'bootstrap3', - iconlib: 'bootstrap', - show_opt_in: true, - disable_edit_json: true, - disable_properties: true, - disable_array_delete_all_rows: true, - disable_array_delete_last_row: true, - prompt_before_delete: true, - schema: schema, - startval: startval, + + // Small schema to handle languages + const languageSchema = { + "title": "Supported languages", + "description": "List all languages you wish banner to support.", + "type": "array", + "format": "table", + "options": { + "disable_collapse": true + }, + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "minLength": 2, + "title": "Code (eg. \"fi\")" + }, + "name": { + "type": "string", + "minLength": 1, + "title": "Name (ex. \"Finnish\")" + } + }, + "required": [ + "code", + "name" + ], + "title": "Language" + }, + "uniqueItems": true, + "minItems": 1 + } + const defaultLanguages = [ + { "code": "fi", "name": "Finnish" }, + { "code": "sv", "name": "Swedish" }, + { "code": "en", "name": "English" } + ]; + + // JSON editor options for both forms + const editorOptions = { + theme: 'bootstrap3', + iconlib: 'bootstrap', + show_opt_in: true, + disable_edit_json: true, + disable_properties: true, + disable_array_delete_all_rows: true, + disable_array_delete_last_row: true, + keep_oneof_values: false, + prompt_before_delete: true, + } + + /** + * Gets the schema object for site settings + * @returns {Promise} A promise that resolves with the schema object + */ + function getSchema(){ + try { + const schema = JSON.parse(drupalSettings.cookieBannerAdminUi.siteSettingsSchema); + return schema; + } catch (error) { + console.error('Error fetching the schema:', error); + } + return {}; + } + + /** + * Initializes the language editor and returns a reference to it + * @param {object} defaultLanguages that contains code and name for each language + * @param {object} languageSchema JSON schema for the language editor + * @param {object} editorOptions for the JSON editor + * @returns reference to the language editor + */ + function initializeLanguageEditor(defaultLanguages, languageSchema, editorOptions){ + const langOptions = { + ...editorOptions, + schema: languageSchema, + startval: defaultLanguages }; - // Initialize the JSON Editor - const editor = new JSONEditor(element, options); + const languageElement = document.getElementById('language_holder'); + const languageEditor = new JSONEditor(languageElement, langOptions); + return languageEditor; + } + + /** + * Initializes the banner editor and returns a reference to it + * @param {object} schema JSON schema of siteSettings.json for the banner editor + * @param {object} editorOptions for the JSON editor + * @returns reference to the banner editor + */ + function initializeBannerEditor(schema, editorOptions){ + let isUpdatingFromEditor = false; // Flag to prevent loop + let startval = {}; + let textarea = null; + try { + textarea = document.getElementById('edit-site-settings'); + startval = JSON.parse(textarea.value); + } catch (error) { + console.error('Error parsing the textarea value:', error); + } + + const bannerElement = document.getElementById('editor_holder'); + const bannerEditor = new JSONEditor(bannerElement, { + ...editorOptions, + schema, + startval + }); // Listen for changes in the JSON editor - editor.on('change', function() { + bannerEditor.on('change', function() { if (!isUpdatingFromEditor) { - const updatedData = editor.getValue(); + const updatedData = bannerEditor.getValue(); textarea.value = JSON.stringify(updatedData, null, 2); } }); @@ -40,16 +125,83 @@ // Prevent triggering the editor change event isUpdatingFromEditor = true; - editor.setValue(updatedTextareaData); + bannerEditor.setValue(updatedTextareaData); isUpdatingFromEditor = false; } catch (e) { console.error('Invalid JSON in textarea:', e); } }); - } catch (error) { - console.error('Error fetching the schema:', error); + return bannerEditor; + } + + /** + * Updates the schema with the new languages + * @param {object} languages JSON generated by the language editor + * @param {object} schema JSON schema of siteSettings.json + * @returns {object} updated schema with the new languages + */ + function updateSchema(languages, schema){ + const newSchema = schema; + + const localisedText = { + "type": "object", + "title": "Localised text", + "properties": {}, + "required": [], + "additionalProperties": false + }; + + const fallbackLanguageEnum = languages.map(lang => lang.code); + const fallbackLanguageEnumTitles = languages.map(lang => lang.code + " (" + lang.name + ")"); + + languages.forEach(lang => { + localisedText.properties[lang.code] = { + "type": "string", + "title": lang.code + " (" + lang.name + ")" + }; + localisedText.required.push(lang.code); + }); + + newSchema["$defs"].LocalisedText = localisedText; + newSchema.properties.fallbackLanguage.enum = fallbackLanguageEnum; + newSchema.properties.fallbackLanguage.options.enum_titles = fallbackLanguageEnumTitles; + + return newSchema; + } + + /** + * Initializes the language and banner editors + */ + async function initializeEditor(){ + const languageEditor = initializeLanguageEditor(defaultLanguages, languageSchema, editorOptions); + let schema = getSchema(); + let bannerEditor = initializeBannerEditor(schema, editorOptions); + + const debounce = (func, delay) => { + let timeoutId; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + func.apply(null, args); + }, delay); + }; + }; + + languageEditor.on('change', debounce(function() { + const errors = languageEditor.validate(); + if (!errors.length) { + const languages = languageEditor.getValue(); + schema = updateSchema(languages, schema); + bannerEditor.destroy(); + bannerEditor = initializeBannerEditor(schema, editorOptions); + } + }, 300)); } + + // Initialize the editor once the page has loaded + window.onload = initializeEditor; + } }; })(Drupal, drupalSettings); diff --git a/modules/hdbt_cookie_banner/assets/json/siteSettings.schema.json b/modules/hdbt_cookie_banner/assets/json/siteSettings.schema.json index 627195fc4..9630afd14 100644 --- a/modules/hdbt_cookie_banner/assets/json/siteSettings.schema.json +++ b/modules/hdbt_cookie_banner/assets/json/siteSettings.schema.json @@ -107,7 +107,7 @@ "expiration": { "$ref": "#/$defs/LocalisedUniversalText", "title": "Expiration", - "description": "Describe to end user when the cookie or item expires. (eg. \"100 days\") If it does not expire (like localStorage), enter dash \"-\". If it's a session cookie, enter fi:\"Istunto\", sv,en:\"Session\".", + "description": "Describe to end user when the cookie or item expires. (eg. \"100 days\") If it does not expire (like localStorage), enter dash \"-\". If it's a session cookie, enter fi:\"Istunto\", sv:\"Session\", en:\"Session\".", "options": { "disable_collapse": true } @@ -155,7 +155,7 @@ }, "cookies": { "title": "Cookies", - "headerTemplate": "Cookies for {{groupId}} {{groupI}} {{self.groupI}} {{parent.groupId}} {{self.groupId}} {{parent.parent.groupId}} {{self.parent.groupId}}", + "headerTemplate": "Cookies", "description": "Cookies or or items toggled together in this group", "type": "array", "items": { @@ -180,6 +180,11 @@ ] } }, + "title": "Site settings", + "description": "Configure HDS cookie banner settings.json to adjust allowed cookies, translations and other features.", + "options": { + "disable_collapse": true + }, "type": "object", "properties": { "siteName": { @@ -193,7 +198,10 @@ "$ref": "#/$defs/UniversalLocalisedText" }, { - "default": "" + "default": "", + "options": { + "disable_collapse": true + } } ] }, @@ -210,7 +218,7 @@ "monitorInterval": { "type": "integer", "title": "Monitor interval (ms)", - "description": "How often banner checks for rogue cookies in milliseconds. Disabled by default. 500 ms could be a good default.", + "description": "How often banner checks for unallowed cookies in milliseconds. Disabled by default. 500 ms is a good default.", "options": { "input_width": "calc(5ch + 28px)" }, @@ -233,9 +241,9 @@ ], "options": { "enum_titles": [ - "fi = Finnish", - "sv = Swedish", - "en = English" + "fi (Finnish)", + "sv (Swedish)", + "en (English)" ] }, "default": "en" @@ -817,10 +825,5 @@ "requiredGroups", "optionalGroups", "translations" - ], - "title": "HDS cookie banner", - "description": "Configure HDS cookie banner settings.json to adjust allowed cookies, translations and other features.", - "options": { - "disable_collapse": true - } + ] } diff --git a/modules/hdbt_cookie_banner/src/Form/HdbtCookieBannerForm.php b/modules/hdbt_cookie_banner/src/Form/HdbtCookieBannerForm.php index f69e2f0a7..d8469a712 100644 --- a/modules/hdbt_cookie_banner/src/Form/HdbtCookieBannerForm.php +++ b/modules/hdbt_cookie_banner/src/Form/HdbtCookieBannerForm.php @@ -80,7 +80,7 @@ protected function getEditableConfigNames(): array { public function buildForm(array $form, FormStateInterface $form_state): array { $form['json_editor'] = [ '#type' => 'markup', - '#markup' => '
', + '#markup' => '

HDS Cookie Consent Settings

', '#attached' => [ 'library' => [ 'hdbt_cookie_banner/cookie_banner_admin_ui',