From e951d188fbf3bd5a9efeaf837c9e1e1f57cae86a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 23 Feb 2026 17:58:47 +0100 Subject: [PATCH] Improve element gallery header (#6239) * Improve header of Element Gallery. * Add click to copy url to screenshot header and each screenshot row. --- screenshots/html/screenshots.css | 91 +++++++++++++++++++++++++++++++- screenshots/html/script.js | 85 +++++++++++++++++++++++------ 2 files changed, 160 insertions(+), 16 deletions(-) diff --git a/screenshots/html/screenshots.css b/screenshots/html/screenshots.css index 5d91c44352..f946c82e81 100644 --- a/screenshots/html/screenshots.css +++ b/screenshots/html/screenshots.css @@ -60,11 +60,14 @@ br { } form { + display: flex; + align-items: center; } label { margin-left: 10px; margin-right: 6px; + white-space: nowrap; } input { @@ -72,7 +75,13 @@ input { } #width_input { - width: 80px; + width: 60px; + height: 32px; + background: #21262e; + color: white; + border: 1px solid #30363d; + border-radius: 6px; + padding-left: 8px; } #screenshots_container { @@ -98,6 +107,7 @@ input { background: #101318; color: #FFF; padding: 0px; + z-index: 100; } #header { top: 0; @@ -114,6 +124,7 @@ input { font-size: 1.5rem; font-weight: bold; margin-top: 5px; + white-space: nowrap; } .fullstop--green { @@ -162,3 +173,81 @@ a { text-align: center; vertical-align: middle; } + +.multiselect { + width: 200px; + margin-left: 10px; + margin-right: 10px; + position: relative; +} + +.selectBox { + position: relative; +} + +.selectBox select { + width: 100%; + height: 32px; + font-weight: bold; + background: #21262e; + color: white; + border: 1px solid #30363d; + border-radius: 6px; + padding-left: 8px; +} + +.overSelect { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + cursor: pointer; +} + +#checkboxes { + display: none; + border: 1px solid #30363d; + background-color: #161b22; + position: absolute; + z-index: 10; + width: 100%; + max-height: 600px; + overflow-y: auto; + box-shadow: 0 8px 16px rgba(0,0,0,0.5); + border-radius: 6px; + margin-top: 4px; +} + +#checkboxes label { + display: block; + margin: 0; + padding: 8px 12px; + cursor: pointer; +} + +#checkboxes label:hover { + background-color: #0DBD8B; +} + +#checkboxes input { + vertical-align: middle; +} + +#lines { + margin-left: 16px; + white-space: nowrap; +} + +.copy-message { + position: absolute; + bottom: 60px; + right: 20px; + background-color: #0DBD8B; + color: white; + padding: 10px 20px; + border: 1px solid #30363d; + border-radius: 6px; + font-size: 16px; + box-shadow: 0 4px 8px rgba(0,0,0,0.2); +} diff --git a/screenshots/html/script.js b/screenshots/html/script.js index e581aa812b..4a9586d591 100644 --- a/screenshots/html/script.js +++ b/screenshots/html/script.js @@ -29,7 +29,7 @@ const dataLanguages = screenshots[0]; const urlParams = new URLSearchParams(window.location.search); // Get the wanted languages from the url params, or default to "de" and "fr", and ensure "en" is always there -const wantedLanguages = (urlParams.get(URL_PARAM_LANGUAGES) ? urlParams.get(URL_PARAM_LANGUAGES).split(',') : ['de', 'fr']) + ["en"]; +const wantedLanguages = [...new Set((urlParams.get(URL_PARAM_LANGUAGES) ? urlParams.get(URL_PARAM_LANGUAGES).split(',') : ['de', 'fr']).concat('en'))]; // Map dataLanguages to visibleLanguages, set to 1 if the language is in wantedLanguages, 0 otherwise let visibleLanguages = dataLanguages.map((language) => wantedLanguages.includes(language) ? 1 : 0); // Read width from the url params, and ensure it's a multiple of 25 and is between 75 and 500 @@ -45,7 +45,7 @@ if (width) { imageWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, Math.round(width / WIDTH_STEP) * WIDTH_STEP)); } // Read showAllScreenshots from the url params -let showAllScreenshots = urlParams.get(URL_PARAM_ALL_SCREENSHOTS) === 1; +let showAllScreenshots = urlParams.get(URL_PARAM_ALL_SCREENSHOTS) === "1"; // Read the minimum date of modification from the url params let minModifiedDayTime = urlParams.get(URL_PARAM_IF_MODIFIED_AFTER); @@ -81,17 +81,40 @@ function updatePageUrl() { function addForm() { // Insert the form into the div with id form_container const form = document.createElement('form'); - const languageLabel = document.createElement('label'); - languageLabel.textContent = 'Languages:'; - form.appendChild(languageLabel); - // Add a check box per entry in the dataLanguages + + const multiSelectDiv = document.createElement('div'); + multiSelectDiv.className = 'multiselect'; + form.appendChild(multiSelectDiv); + + const selectBoxDiv = document.createElement('div'); + selectBoxDiv.className = 'selectBox'; + selectBoxDiv.onclick = () => { + const checkboxes = document.getElementById("checkboxes"); + checkboxes.style.display = checkboxes.style.display === "block" ? "none" : "block"; + }; + multiSelectDiv.appendChild(selectBoxDiv); + + const select = document.createElement('select'); + const option = document.createElement('option'); + option.textContent = "Select languages"; + select.appendChild(option); + selectBoxDiv.appendChild(select); + + const overSelectDiv = document.createElement('div'); + overSelectDiv.className = 'overSelect'; + selectBoxDiv.appendChild(overSelectDiv); + + const checkboxesDiv = document.createElement('div'); + checkboxesDiv.id = 'checkboxes'; + multiSelectDiv.appendChild(checkboxesDiv); + for (let i = 0; i < dataLanguages.length; i++){ const label = document.createElement('label'); - const text = document.createTextNode(dataLanguages[i]); const input = document.createElement('input'); input.type = 'checkbox'; - input.disabled = i == 0; - input.name = dataLanguages[i]; + const language = dataLanguages[i]; + input.disabled = language === "en"; + input.name = language; input.checked = visibleLanguages[i] == 1; input.onchange = (e) => { if (e.target.checked) { @@ -103,14 +126,12 @@ function addForm() { addTable(); }; label.appendChild(input); - label.appendChild(text); - form.appendChild(label); + label.appendChild(document.createTextNode(` ${language}`)); + checkboxesDiv.appendChild(label); } - // Add a break line - form.appendChild(document.createElement('br')); // Add a label with the text "Width" const label = document.createElement('label'); - label.textContent = 'Screenshots width:'; + label.textContent = 'Width:'; form.appendChild(label); // Add a input text to input the width of the image const widthInput = document.createElement('input'); @@ -128,7 +149,7 @@ function addForm() { form.appendChild(widthInput); // Add a label with the text "Show all screenshots" const label2 = document.createElement('label'); - label2.textContent = 'Show all screenshots:'; + label2.textContent = 'Show all:'; label2.title = 'Show all screenshots, including those with no translated versions.'; const input2 = document.createElement('input'); input2.type = 'checkbox'; @@ -211,6 +232,34 @@ function createImageElement(fullFile, modifiedDayTime) { return img; } +function updateUrlAndScrollTo(id) { + // If the current URL already contains the fragment id, remove it, otherwise add it + if (window.location.hash === id) { + history.replaceState(null, '', window.location.pathname + window.location.search); + } else { + history.replaceState(null, '', `${window.location.pathname}${window.location.search}#${id}`); + } + const element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } + // Also copy the URL to the clipboard + navigator.clipboard.writeText(window.location.href); + // And show a message that the URL has been copied to the clipboard + // First check if there is already a message, if so, remove it, or the shadow will accumulate. + const existingMessage = document.querySelector('.copy-message'); + if (existingMessage) { + document.body.removeChild(existingMessage); + } + const message = document.createElement('div'); + message.className = 'copy-message'; + message.textContent = 'URL copied to clipboard!'; + document.body.appendChild(message); + setTimeout(() => { + document.body.removeChild(message); + }, 2000); +} + function addTable() { var linesCounter = 0; // Remove any previous table @@ -244,6 +293,9 @@ function addTable() { } const tr = document.createElement('tr'); tr.id = niceName + screenshotCounter; + tr.onclick = () => { + updateUrlAndScrollTo(tr.id); + } let hasTranslatedFiles = false; for (let languageIndex = 0; languageIndex < dataLanguages.length; languageIndex++) { if (visibleLanguages[languageIndex] == 0) { @@ -284,6 +336,9 @@ function addTable() { currentHeaderValue = niceName; const trHead = document.createElement('tr'); trHead.id = niceName; + trHead.onclick = () => { + updateUrlAndScrollTo(trHead.id); + } const tdHead = document.createElement('td'); tdHead.colSpan = numVisibleLanguages; tdHead.className = "view-header";