From 7d2e19179d54fe3e3218a9ad216c7ad5cb44f62a Mon Sep 17 00:00:00 2001 From: nutty Date: Mon, 10 Aug 2026 01:24:13 +1000 Subject: [PATCH] Added "Font" suggestions --- .common/core/settings-core/script.js | 135 +++++++++++++++++++-------- clocko/settings/settings.json | 10 +- 2 files changed, 99 insertions(+), 46 deletions(-) diff --git a/.common/core/settings-core/script.js b/.common/core/settings-core/script.js index 7cce186..6868574 100644 --- a/.common/core/settings-core/script.js +++ b/.common/core/settings-core/script.js @@ -170,6 +170,24 @@ function LoadJSON(settingsJson) { inputElement.setAttribute('list', 'streamer-bot-actions'); inputElement.autocomplete = 'off'; break; + case 'font': + inputElement = document.createElement('input'); + inputElement.type = 'text'; + inputElement.placeholder = 'Type to search...'; + inputElement.id = setting.id; //Added setting ID + inputElement.value = settingsMap.has(setting.id) ? settingsMap.get(setting.id) : setting.defaultValue; + inputElement.setAttribute('list', 'fonts'); + inputElement.autocomplete = 'off'; + + // Trigger permission prompt and load fonts on first click/focus + inputElement.addEventListener('focus', async function loadOnce() { + // Remove listener so it only triggers once per session + inputElement.removeEventListener('focus', loadOnce); + inputElement.placeholder = 'Type to search...'; + + await PopulateFontDatalist(); + }, { once: true }); + break; case 'button': inputElement = document.createElement('button'); inputElement.id = setting.id; //Added setting ID @@ -377,6 +395,38 @@ async function GetSBActions() { document.body.appendChild(datalistElement); } +async function PopulateFontDatalist() { + if (!('queryLocalFonts' in window)) { + console.warn("Local Font Access API is not supported in this browser. Font auto-suggestions will be disabled."); + return; + } + + try { + // Request permission and fetch local fonts + const availableFonts = await window.queryLocalFonts(); + + // Extract unique font family names and sort them alphabetically + const fontFamilies = [...new Set(availableFonts.map(font => font.family))].sort(); + + // Create the datalist element + const datalistElement = document.createElement('datalist'); + datalistElement.id = 'fonts'; + + // Append each font family as an option + fontFamilies.forEach(family => { + const option = document.createElement('option'); + option.value = family; + datalistElement.appendChild(option); + }); + + document.body.appendChild(datalistElement); + console.debug(`Loaded ${fontFamilies.length} local fonts into auto-suggest.`); + + } catch (err) { + console.error("Permission denied or error fetching local fonts:", err); + } +} + ///////////////////////// @@ -483,56 +533,56 @@ window.addEventListener('message', (event) => { }); function GetSettingDepth(setting, allSettings) { - let depth = 0; - let current = setting; + let depth = 0; + let current = setting; - while (current.showIf) { - depth++; - current = allSettings.find(s => s.id === current.showIf) || {}; - } + while (current.showIf) { + depth++; + current = allSettings.find(s => s.id === current.showIf) || {}; + } - return depth; + return depth; } function InterpolatePlaceholders() { - // Regex to match anything inside curly braces, e.g., {smtcBridgeAddress} - const tokenRegex = /\{([^}]+)\}/g; + // Regex to match anything inside curly braces, e.g., {smtcBridgeAddress} + const tokenRegex = /\{([^}]+)\}/g; - // Iterate over all settings from the original JSON in memory - settingsData.settings.forEach(setting => { - - // Only process settings that actually have a placeholder in their description - if (setting.description && setting.description.includes('{')) { - - // Start with the original untouched description from the JSON - let dynamicHTML = setting.description; - let match; + // Iterate over all settings from the original JSON in memory + settingsData.settings.forEach(setting => { - // Reset regex state (required when reusing a global regex in a loop) - tokenRegex.lastIndex = 0; + // Only process settings that actually have a placeholder in their description + if (setting.description && setting.description.includes('{')) { - // Find all {tokens} in the string - while ((match = tokenRegex.exec(setting.description)) !== null) { - const targetId = match[1]; // The exact text inside the braces - const targetInput = document.getElementById(targetId); + // Start with the original untouched description from the JSON + let dynamicHTML = setting.description; + let match; - if (targetInput) { - // Get current value, or fallback to the JSON default if the box is empty - const fallback = settingsData.settings.find(s => s.id === targetId)?.defaultValue || ''; - const currentValue = targetInput.value || fallback; - - // Replace the token with the actual value in our temporary string - dynamicHTML = dynamicHTML.replace(match[0], currentValue); - } - } + // Reset regex state (required when reusing a global regex in a loop) + tokenRegex.lastIndex = 0; - // Find the specific

tag for this setting in the DOM and overwrite its HTML - const descriptionParagraph = document.querySelector(`#item-${setting.id} p`); - if (descriptionParagraph) { - descriptionParagraph.innerHTML = dynamicHTML; - } - } - }); + // Find all {tokens} in the string + while ((match = tokenRegex.exec(setting.description)) !== null) { + const targetId = match[1]; // The exact text inside the braces + const targetInput = document.getElementById(targetId); + + if (targetInput) { + // Get current value, or fallback to the JSON default if the box is empty + const fallback = settingsData.settings.find(s => s.id === targetId)?.defaultValue || ''; + const currentValue = targetInput.value || fallback; + + // Replace the token with the actual value in our temporary string + dynamicHTML = dynamicHTML.replace(match[0], currentValue); + } + } + + // Find the specific

tag for this setting in the DOM and overwrite its HTML + const descriptionParagraph = document.querySelector(`#item-${setting.id} p`); + if (descriptionParagraph) { + descriptionParagraph.innerHTML = dynamicHTML; + } + } + }); } @@ -542,4 +592,7 @@ function InterpolatePlaceholders() { LoadSettingsFromStorage(); // Load default settings -LoadJSON(settingsJson); \ No newline at end of file +LoadJSON(settingsJson); + +// Populate local fonts for auto-suggest +PopulateFontDatalist(); \ No newline at end of file diff --git a/clocko/settings/settings.json b/clocko/settings/settings.json index dc40b2c..096c779 100644 --- a/clocko/settings/settings.json +++ b/clocko/settings/settings.json @@ -20,8 +20,8 @@ { "id": "line1Font", "label": "Font", - "description": "", - "type": "text", + "description": "Check installed fonts", + "type": "font", "defaultValue": "", "showIf": "enableLine1", "group": "Appearance" @@ -112,8 +112,8 @@ { "id": "line2Font", "label": "Font", - "description": "", - "type": "text", + "description": "Check installed PC fonts", + "type": "font", "defaultValue": "", "showIf": "enableLine2", "group": "Appearance" @@ -204,7 +204,7 @@ { "id": "line3Font", "label": "Font", - "description": "", + "description": "Check installed PC fonts", "type": "text", "defaultValue": "", "showIf": "enableLine3",