From bc6b945833556e4cb9aeed1a9bfdc43071807391 Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Thu, 20 Jun 2024 00:03:11 -0400 Subject: [PATCH 01/12] web: Core UI improvements Dark mode support has been added to the context menu as well as the the Volume and Save Manager modals. The latter two have also received significant visual upgrades. --- web/packages/core/src/ruffle-player.ts | 87 +++---- web/packages/core/src/shadow-template.ts | 226 +++++++++++------- .../core/texts/en-US/volume-controls.ftl | 3 +- 3 files changed, 186 insertions(+), 130 deletions(-) diff --git a/web/packages/core/src/ruffle-player.ts b/web/packages/core/src/ruffle-player.ts index d037c204f7eb..a62f5238b958 100644 --- a/web/packages/core/src/ruffle-player.ts +++ b/web/packages/core/src/ruffle-player.ts @@ -355,12 +355,14 @@ export class RufflePlayer extends HTMLElement { */ private addModalJavaScript(modalElement: HTMLDivElement): void { const videoHolder = modalElement.querySelector("#video-holder"); - this.container.addEventListener("click", () => { + const hideModal = () => { modalElement.classList.add("hidden"); if (videoHolder) { videoHolder.textContent = ""; } - }); + } + + this.container.addEventListener("click", hideModal); const modalArea = modalElement.querySelector(".modal-area"); if (modalArea) { modalArea.addEventListener("click", (event) => @@ -369,12 +371,7 @@ export class RufflePlayer extends HTMLElement { } const closeModal = modalElement.querySelector(".close-modal"); if (closeModal) { - closeModal.addEventListener("click", () => { - modalElement.classList.add("hidden"); - if (videoHolder) { - videoHolder.textContent = ""; - } - }); + closeModal.addEventListener("click", hideModal); } } @@ -387,9 +384,17 @@ export class RufflePlayer extends HTMLElement { private addVolumeControlsJavaScript( volumeControlsModal: HTMLDivElement, ): void { - const muteCheckbox = volumeControlsModal.querySelector( + const volumeMuteCheckbox = volumeControlsModal.querySelector( "#mute-checkbox", ) as HTMLInputElement; + const volumeMuteIcon = volumeControlsModal.querySelector( + "#volume-mute", + ) as HTMLLabelElement; + const volumeIcons = [ + volumeControlsModal.querySelector("#volume-min") as HTMLLabelElement, + volumeControlsModal.querySelector("#volume-mid") as HTMLLabelElement, + volumeControlsModal.querySelector("#volume-max") as HTMLLabelElement, + ]; const volumeSlider = volumeControlsModal.querySelector( "#volume-slider", ) as HTMLInputElement; @@ -397,45 +402,42 @@ export class RufflePlayer extends HTMLElement { "#volume-slider-text", ) as HTMLSpanElement; - const heading = volumeControlsModal.querySelector( - "#volume-controls-heading", - ) as HTMLHeadingElement; - const muteCheckboxLabel = volumeControlsModal.querySelector( - "#mute-checkbox-label", - ) as HTMLLabelElement; - const volumeSliderLabel = volumeControlsModal.querySelector( - "#volume-slider-label", - ) as HTMLLabelElement; - - // Add the texts. - heading.textContent = text("volume-controls"); - muteCheckboxLabel.textContent = text("volume-controls-mute"); - volumeSliderLabel.textContent = text("volume-controls-volume"); + const setVolumeIcon = () => { + if (this.volumeSettings.isMuted) { + volumeMuteIcon.style.display = "inline"; + volumeIcons.forEach(icon => { + icon.style.display = "none"; + }); + } else { + volumeMuteIcon.style.display = "none"; + const iconIndex = Math.round(this.volumeSettings.volume / 50); + volumeIcons.forEach((icon, i) => { + icon.style.display = i == iconIndex + ? "inline" + : "none"; + }); + } + }; // Set the controls to the current settings. - muteCheckbox.checked = this.volumeSettings.isMuted; - volumeSlider.disabled = muteCheckbox.checked; + volumeMuteCheckbox.checked = this.volumeSettings.isMuted; + volumeSlider.disabled = volumeMuteCheckbox.checked; volumeSlider.valueAsNumber = this.volumeSettings.volume; - volumeSliderLabel.style.color = muteCheckbox.checked ? "grey" : "black"; - volumeSliderText.style.color = muteCheckbox.checked ? "grey" : "black"; - volumeSliderText.textContent = String(this.volumeSettings.volume); + volumeSliderText.textContent = volumeSlider.value + "%"; + setVolumeIcon(); // Add event listeners to update the settings and controls. - muteCheckbox.addEventListener("change", () => { - volumeSlider.disabled = muteCheckbox.checked; - volumeSliderLabel.style.color = muteCheckbox.checked - ? "grey" - : "black"; - volumeSliderText.style.color = muteCheckbox.checked - ? "grey" - : "black"; - this.volumeSettings.isMuted = muteCheckbox.checked; + volumeMuteCheckbox.addEventListener("change", () => { + volumeSlider.disabled = volumeMuteCheckbox.checked; + this.volumeSettings.isMuted = volumeMuteCheckbox.checked; this.instance?.set_volume(this.volumeSettings.get_volume()); + setVolumeIcon(); }); volumeSlider.addEventListener("input", () => { - volumeSliderText.textContent = volumeSlider.value; + volumeSliderText.textContent = volumeSlider.value + "%"; this.volumeSettings.volume = volumeSlider.valueAsNumber; this.instance?.set_volume(this.volumeSettings.get_volume()); + setVolumeIcon(); }); } @@ -1303,8 +1305,9 @@ export class RufflePlayer extends HTMLElement { keyCol.title = key; const downloadCol = document.createElement("TD"); const downloadSpan = document.createElement("SPAN"); - downloadSpan.textContent = text("save-download"); downloadSpan.className = "save-option"; + downloadSpan.id = "download-save"; + downloadSpan.title = text("save-download"); downloadSpan.addEventListener("click", () => { const blob = this.base64ToBlob( solData, @@ -1325,8 +1328,9 @@ export class RufflePlayer extends HTMLElement { document.createElement("LABEL") ); replaceLabel.htmlFor = "replace-save-" + key; - replaceLabel.textContent = text("save-replace"); replaceLabel.className = "save-option"; + replaceLabel.id = "replace-save"; + replaceLabel.title = text("save-replace"); replaceInput.addEventListener("change", (event) => this.replaceSOL(event, key), ); @@ -1334,8 +1338,9 @@ export class RufflePlayer extends HTMLElement { replaceCol.appendChild(replaceLabel); const deleteCol = document.createElement("TD"); const deleteSpan = document.createElement("SPAN"); - deleteSpan.textContent = text("save-delete"); deleteSpan.className = "save-option"; + deleteSpan.id = "delete-save"; + deleteSpan.title = text("save-delete"); deleteSpan.addEventListener("click", () => this.deleteSave(key), ); diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index f7aa53d761d5..34f6e809f6b8 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -215,45 +215,38 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { background: #ffffff4c; }`, - `#context-menu-overlay { - width: 100%; - height: 100%; - z-index: 1; - position: absolute; - }`, - `#context-menu { - color: black; - background: #fafafa; + color: rgb(var(--modal-foreground-rgb)); + background-color: var(--modal-background); border: 1px solid gray; box-shadow: 0px 5px 10px -5px black; position: absolute; font-size: 14px; text-align: left; list-style: none; - padding: 0; + padding: 3px 0; margin: 0; + pointer-events: all; }`, `#context-menu .menu-item { padding: 5px 10px; - cursor: pointer; - color: black; + color: rgb(var(--modal-foreground-rgb)); }`, `#context-menu .menu-item.disabled { cursor: default; - color: gray; + color: rgba(var(--modal-foreground-rgb), 0.5); }`, `#context-menu .menu-item:not(.disabled):hover { - background: lightgray; + background-color: rgba(var(--modal-foreground-rgb), 0.15); }`, `#context-menu .menu-separator hr { border: none; - border-bottom: 1px solid lightgray; - margin: 2px; + border-bottom: 1px solid rgba(var(--modal-foreground-rgb), 0.2); + margin: 3px; }`, `#splash-screen { @@ -316,78 +309,103 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { }`, `.modal { - height: inherit; - user-select: text; + width: 100%; + height: 100%; + z-index: 1; + position: absolute; + pointer-events: none; }`, `.modal-area { position: sticky; - background: white; + background-color: var(--modal-background); + color: rgb(var(--modal-foreground-rgb)); width: fit-content; - padding: 16px 28px 16px 16px; - border: 3px solid black; margin: auto; + padding: 8px 12px; + border-radius: 12px; + box-shadow: 0 2px 6px 0px #0008; + pointer-events: all; }`, `#modal-area { - height: 500px; - max-height: calc(100% - 38px); - min-height: 80px; - }`, - - `#restore-save { - display: none; + width: min(100%, 450px); + height: min(100%, 300px); }`, - `.replace-save { - display: none; - }`, - - `.save-option { - display: inline-block; - padding: 3px 10px; - margin: 5px 2px; + `.close-modal { + width: 16px; + height: 16px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='16px' viewBox='0 -960 960 960' width='16px' fill='black'%3E%3Cpath d='M480-392 300-212q-18 18-44 18t-44-18q-18-18-18-44t18-44l180-180-180-180q-18-18-18-44t18-44q18-18 44-18t44 18l180 180 180-180q18-18 44-18t44 18q18 18 18 44t-18 44L568-480l180 180q18 18 18 44t-18 44q-18 18-44 18t-44-18L480-392Z'/%3E%3C/svg%3E"); cursor: pointer; - border-radius: 50px; - background-color: var(--ruffle-blue); - color: white; + filter: var(--modal-foreground-filter); }`, - `.close-modal { + `:not(#volume-controls) > .close-modal { position: absolute; - top: 5px; - right: 10px; - cursor: pointer; - font-size: x-large; + top: 14px; + right: 16px; }`, `.general-save-options { text-align: center; - padding-bottom: 8px; - border-bottom: 2px solid #888; + padding: 4px 0 12px; + border-bottom: 2px solid rgba(var(--modal-foreground-rgb), 0.3); + }`, + + `#backup-saves { + background-color: rgba(var(--modal-foreground-rgb), 0.2); + padding: 4px 8px; + border-radius: 6px; + cursor: pointer; }`, `#local-saves { border-collapse: collapse; overflow-y: auto; display: block; - padding-right: 16px; height: calc(100% - 45px); min-height: 30px; }`, - + `#local-saves td { - border-bottom: 1px solid #bbb; + border-bottom: 2px solid rgba(var(--modal-foreground-rgb), 0.15); height: 30px; }`, - - `#local-saves tr td:nth-child(1) { - padding-right: 1em; + + `#local-saves td:nth-child(1) { + width: 100%; word-break: break-all; }`, + + `.save-option { + display: inline-block; + width: 24px; + height: 24px; + cursor: pointer; + filter: var(--modal-foreground-filter); + vertical-align: middle; + opacity: 0.4; + }`, + + `#local-saves > tr:hover .save-option { + opacity: 1; + }`, + + `#download-save { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 -960 960 960' width='24px' fill='black'%3E%3Cpath d='M480-337q-8 0-15-2.5t-13-8.5L308-492q-12-12-11.5-28t11.5-28q12-12 28.5-12.5T365-549l75 75v-286q0-17 11.5-28.5T480-800q17 0 28.5 11.5T520-760v286l75-75q12-12 28.5-11.5T652-548q11 12 11.5 28T652-492L508-348q-6 6-13 8.5t-15 2.5ZM240-160q-33 0-56.5-23.5T160-240v-80q0-17 11.5-28.5T200-360q17 0 28.5 11.5T240-320v80h480v-80q0-17 11.5-28.5T760-360q17 0 28.5 11.5T800-320v80q0 33-23.5 56.5T720-160H240Z'/%3E%3C/svg%3E"); + }`, + + `#replace-save { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 -1080 960 1200' width='24px' fill='black'%3E%3Cpath d='M440-367v127q0 17 11.5 28.5T480-200q17 0 28.5-11.5T520-240v-127l36 36q6 6 13.5 9t15 2.5q7.5-.5 14.5-3.5t13-9q11-12 11.5-28T612-388L508-492q-6-6-13-8.5t-15-2.5q-8 0-15 2.5t-13 8.5L348-388q-12 12-11.5 28t12.5 28q12 11 28 11.5t28-11.5l35-35ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h287q16 0 30.5 6t25.5 17l194 194q11 11 17 25.5t6 30.5v447q0 33-23.5 56.5T720-80H240Zm280-560q0 17 11.5 28.5T560-600h160L520-800v160Z'/%3E%3C/svg%3E"); + }`, + + `#delete-save { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 -1020 960 1080' width='24px' fill='black'%3E%3Cpath d='M280-120q-33 0-56.5-23.5T200-200v-520q-17 0-28.5-11.5T160-760q0-17 11.5-28.5T200-800h160q0-17 11.5-28.5T400-840h160q17 0 28.5 11.5T600-800h160q17 0 28.5 11.5T800-760q0 17-11.5 28.5T760-720v520q0 33-23.5 56.5T680-120H280Zm120-160q17 0 28.5-11.5T440-320v-280q0-17-11.5-28.5T400-640q-17 0-28.5 11.5T360-600v280q0 17 11.5 28.5T400-280Zm160 0q17 0 28.5-11.5T600-320v-280q0-17-11.5-28.5T560-640q-17 0-28.5 11.5T520-600v280q0 17 11.5 28.5T560-280Z'/%3E%3C/svg%3E"); + }`, - `#local-saves tr:nth-child(even) { - background-color: #f2f2f2; + `.replace-save { + display: none; }`, `#video-holder { @@ -399,20 +417,44 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { height: calc(100% - 58px); }`, - `.slider-container { - margin-top: 10px; + `#volume-controls { display: flex; align-items: center; + gap: 6px; + }`, + + `#mute-checkbox { + display: none; + }`, + + `label[for="mute-checkbox"] { + width: 24px; + height: 24px; + line-height: 0; + cursor: pointer; + filter: var(--modal-foreground-filter); + }`, + + `#volume-mute { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 -960 960 960' width='24px' fill='black'%3E%3Cpath d='m719.13-419.35-71.67 71.68Q634.78-335 617.13-335t-30.33-12.67q-12.67-12.68-12.67-30.33t12.67-30.33L658.48-480l-71.68-71.67q-12.67-12.68-12.67-30.33t12.67-30.33Q599.48-625 617.13-625t30.33 12.67l71.67 71.68 71.67-71.68Q803.48-625 821.13-625t30.33 12.67q12.67 12.68 12.67 30.33t-12.67 30.33L779.78-480l71.68 71.67q12.67 12.68 12.67 30.33t-12.67 30.33Q838.78-335 821.13-335t-30.33-12.67l-71.67-71.68ZM278-357.87H161.22q-17.66 0-30.33-12.67-12.67-12.68-12.67-30.33v-158.26q0-17.65 12.67-30.33 12.67-12.67 30.33-12.67H278l130.15-129.91q20.63-20.63 46.98-9.45 26.35 11.19 26.35 39.77v443.44q0 28.58-26.35 39.77-26.35 11.18-46.98-9.45L278-357.87Z'/%3E%3C/svg%3E"); + }`, + + `#volume-min { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='161 -960 960 960' width='24px' fill='black'%3E%3Cpath d='M438.65-357.87H321.87q-17.65 0-30.33-12.67-12.67-12.68-12.67-30.33v-158.26q0-17.65 12.67-30.33 12.68-12.67 30.33-12.67h116.78L568.8-732.04q20.63-20.63 46.98-9.45 26.35 11.19 26.35 39.77v443.44q0 28.58-26.35 39.77-26.35 11.18-46.98-9.45L438.65-357.87Z'/%3E%3C/svg%3E"); }`, - `#volume-slider { - margin-left: 10px; - margin-right: 10px; + `#volume-mid { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='80 -960 960 960' width='24px' fill='black'%3E%3Cpath d='M357.98-357.87H241.2q-17.66 0-30.33-12.67-12.67-12.68-12.67-30.33v-158.26q0-17.65 12.67-30.33 12.67-12.67 30.33-12.67h116.78L487.65-731.8q20.63-20.64 47.1-9.57 26.47 11.07 26.47 39.65v443.44q0 28.58-26.47 39.65t-47.1-9.57L357.98-357.87ZM741.8-480q0 42.48-20.47 80.09-20.48 37.61-54.94 60.82-10.22 5.98-20.19.25-9.98-5.73-9.98-17.44v-248.44q0-11.71 9.98-17.32 9.97-5.61 20.19.37 34.46 23.71 54.94 61.45Q741.8-522.48 741.8-480Z'/%3E%3C/svg%3E"); + }`, + + `#volume-max { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='9 -960 960 960' width='24px' fill='black'%3E%3Cpath d='M754.22-480.5q0-78.52-41.88-143.9-41.88-65.38-111.91-98.62-14.47-6.74-20.47-20.96-6-14.22-.53-28.93 5.74-15.72 20.34-22.46t29.58 0q92.48 42.46 147.97 127.05 55.48 84.6 55.48 187.82t-55.48 187.82q-55.49 84.59-147.97 127.05-14.98 6.74-29.58 0-14.6-6.74-20.34-22.46-5.47-14.71.53-28.93 6-14.22 20.47-20.96 70.03-33.24 111.91-98.62 41.88-65.38 41.88-143.9ZM286.98-357.87H170.2q-17.66 0-30.33-12.67-12.67-12.68-12.67-30.33v-158.26q0-17.65 12.67-30.33 12.67-12.67 30.33-12.67h116.78L416.65-731.8q20.63-20.64 47.1-9.57 26.47 11.07 26.47 39.65v443.44q0 28.58-26.47 39.65t-47.1-9.57L286.98-357.87ZM670.8-480q0 42.48-20.47 80.09-20.48 37.61-54.94 60.82-10.22 5.98-20.19.25-9.98-5.73-9.98-17.44v-248.44q0-11.71 9.98-17.32 9.97-5.61 20.19.37 34.46 23.71 54.94 61.45Q670.8-522.48 670.8-480Z'/%3E%3C/svg%3E"); }`, `#volume-slider-text { - text-align: right; - width: 28px; + width: 4.8ch; + text-align: center; + user-select: none; }`, `.acceleration-link { @@ -423,6 +465,23 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { `.acceleration-link:hover { text-decoration: underline; }`, + + /* Handle preferred color scheme. */ + `@media (prefers-color-scheme: light) { + :host { + --modal-background: #fafafa; + --modal-foreground-rgb: 0, 0, 0; + --modal-foreground-filter: none; + } + }`, + + `@media (prefers-color-scheme: dark) { + :host { + --modal-background: #282828; + --modal-foreground-rgb: 221, 221, 221; + --modal-foreground-filter: invert(90%); + } + }`, ]; insertRules(styleElement.sheet, rules); } @@ -753,13 +812,12 @@ const loadbarInner = createElement("div", undefined, "loadbar-inner"); const saveManager = createElement("div", "save-manager", "modal hidden"); const saveModalArea = createElement("div", "modal-area", "modal-area"); const saveModalClose = createElement("span", undefined, "close-modal"); -saveModalClose.textContent = "\u00D7"; const generalSaveOptions = createElement( "div", undefined, "general-save-options", ); -const backupSaves = createElement("span", "backup-saves", "save-option"); +const backupSaves = createElement("span", "backup-saves"); const localSaves = createElement("table", "local-saves"); // Volume control elements @@ -769,20 +827,8 @@ const volumeControlsModal = createElement( "modal hidden", ); const volumeModalArea = createElement("div", undefined, "modal-area"); -const volumeModalClose = createElement("span", undefined, "close-modal"); -volumeModalClose.textContent = "\u00D7"; const volumeControls = createElement("div", "volume-controls"); -const volumeControlsHeading = createElement("h2", "volume-controls-heading"); -const muteCheckboxLabel = createLabelElement( - "mute-checkbox-label", - "mute-checkbox", -); -const muteCheckbox = createInputElement("checkbox", "mute-checkbox"); -const sliderContainer = createElement("div", undefined, "slider-container"); -const volumeSliderLabel = createLabelElement( - "volume-slider-label", - "volume-slider", -); +const volumeMuteCheckbox = createInputElement("checkbox", "mute-checkbox"); const volumeSlider = createInputElement( "range", "volume-slider", @@ -790,13 +836,20 @@ const volumeSlider = createInputElement( "100", "1", ); +const volumeMuteIcon = createLabelElement("volume-mute", "mute-checkbox"); +volumeMuteIcon.title = text("volume-controls-unmute"); +const volumeMinIcon = createLabelElement("volume-min", "mute-checkbox"); +const volumeMidIcon = createLabelElement("volume-mid", "mute-checkbox"); +const volumeMaxIcon = createLabelElement("volume-max", "mute-checkbox"); +[volumeMinIcon, volumeMidIcon, volumeMaxIcon] + .forEach(icon => icon.title = text("volume-controls-mute")); const volumeSliderText = createElement("span", "volume-slider-text"); +const volumeModalClose = createElement("span", undefined, "close-modal"); // Video modal elements const videoModal = createElement("div", "video-modal", "modal hidden"); const videoModalArea = createElement("div", undefined, "modal-area"); const videoModalClose = createElement("span", undefined, "close-modal"); -videoModalClose.textContent = "\u00D7"; const videoHolder = createElement("div", "video-holder"); // Hardware acceleration modal elements @@ -807,7 +860,6 @@ const hardwareModal = createElement( ); const hardwareModalArea = createElement("div", undefined, "modal-area"); const hardwareModalClose = createElement("span", undefined, "close-modal"); -hardwareModalClose.textContent = "\u00D7"; const hardwareModalLink = document.createElement("a"); hardwareModalLink.href = "https://github.com/ruffle-rs/ruffle/wiki/Frequently-Asked-Questions-For-Users#chrome-hardware-acceleration"; @@ -849,7 +901,7 @@ clipboardModalTextPasteText.textContent = text("clipboard-message-paste"); const contextMenuOverlay = createElement( "div", "context-menu-overlay", - "hidden", + "modal hidden", ); const contextMenu = createElement("ul", "context-menu"); @@ -899,15 +951,15 @@ appendElement(saveModalArea, localSaves); // Volume control append appendElement(ruffleShadowTemplate.content, volumeControlsModal); appendElement(volumeControlsModal, volumeModalArea); -appendElement(volumeModalArea, volumeModalClose); appendElement(volumeModalArea, volumeControls); -appendElement(volumeControls, volumeControlsHeading); -appendElement(volumeControls, muteCheckboxLabel); -appendElement(volumeControls, muteCheckbox); -appendElement(volumeControls, sliderContainer); -appendElement(sliderContainer, volumeSliderLabel); -appendElement(sliderContainer, volumeSlider); -appendElement(sliderContainer, volumeSliderText); +appendElement(volumeControls, volumeMuteCheckbox); +appendElement(volumeControls, volumeMuteIcon); +appendElement(volumeControls, volumeMinIcon); +appendElement(volumeControls, volumeMidIcon); +appendElement(volumeControls, volumeMaxIcon); +appendElement(volumeControls, volumeSlider); +appendElement(volumeControls, volumeSliderText); +appendElement(volumeControls, volumeModalClose); // Video modal append appendElement(ruffleShadowTemplate.content, videoModal); appendElement(videoModal, videoModalArea); diff --git a/web/packages/core/texts/en-US/volume-controls.ftl b/web/packages/core/texts/en-US/volume-controls.ftl index 82c0fe06825a..a7aad054f3d9 100644 --- a/web/packages/core/texts/en-US/volume-controls.ftl +++ b/web/packages/core/texts/en-US/volume-controls.ftl @@ -1,3 +1,2 @@ -volume-controls = Volume controls volume-controls-mute = Mute -volume-controls-volume = Volume +volume-controls-unmute = Unmute From 5a9a635da5640aa3641a0c6b44ba340e07fe6baf Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Thu, 20 Jun 2024 23:19:14 -0400 Subject: [PATCH 02/12] web: Improve modal/context menu behavior - Open modals now dim the player and prevent interaction with the underlying content until it is closed - Right-clicking outside of an open modal now closes it instead of displaying the browser context menu - Fixed a regression where right-clicking with the Ruffle context menu open would open the browser context menu --- web/packages/core/src/ruffle-player.ts | 16 +++++++++++----- web/packages/core/src/shadow-template.ts | 17 +++++++++-------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/web/packages/core/src/ruffle-player.ts b/web/packages/core/src/ruffle-player.ts index a62f5238b958..531dd038fd89 100644 --- a/web/packages/core/src/ruffle-player.ts +++ b/web/packages/core/src/ruffle-player.ts @@ -362,7 +362,7 @@ export class RufflePlayer extends HTMLElement { } } - this.container.addEventListener("click", hideModal); + modalElement.parentNode!.addEventListener("click", hideModal); const modalArea = modalElement.querySelector(".modal-area"); if (modalArea) { modalArea.addEventListener("click", (event) => @@ -1610,15 +1610,21 @@ export class RufflePlayer extends HTMLElement { } private showContextMenu(event: MouseEvent | PointerEvent): void { - const modalOpen = Array.from( - this.shadow.querySelectorAll(".modal"), - ).some((modal) => !modal.classList.contains("hidden")); - if (this.panicked || modalOpen) { + if (this.panicked) { return; } event.preventDefault(); + let modalOpen = false; + this.shadow.querySelectorAll(".modal:not(.hidden)").forEach((modal) => { + modal.classList.add("hidden"); + modalOpen = true; + }); + if (modalOpen) { + return; + } + if (event.type === "contextmenu") { this.contextMenuSupported = true; document.documentElement.addEventListener( diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index 34f6e809f6b8..338b7c849384 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -215,6 +215,13 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { background: #ffffff4c; }`, + `#context-menu-overlay, .modal { + width: 100%; + height: 100%; + z-index: 1; + position: absolute; + }`, + `#context-menu { color: rgb(var(--modal-foreground-rgb)); background-color: var(--modal-background); @@ -226,7 +233,6 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { list-style: none; padding: 3px 0; margin: 0; - pointer-events: all; }`, `#context-menu .menu-item { @@ -309,11 +315,7 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { }`, `.modal { - width: 100%; - height: 100%; - z-index: 1; - position: absolute; - pointer-events: none; + background-color: #0008; }`, `.modal-area { @@ -325,7 +327,6 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { padding: 8px 12px; border-radius: 12px; box-shadow: 0 2px 6px 0px #0008; - pointer-events: all; }`, `#modal-area { @@ -901,7 +902,7 @@ clipboardModalTextPasteText.textContent = text("clipboard-message-paste"); const contextMenuOverlay = createElement( "div", "context-menu-overlay", - "modal hidden", + "hidden", ); const contextMenu = createElement("ul", "context-menu"); From b267b6ce4bcb91fdfd94df5a40d6d20c735c54af Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Thu, 20 Jun 2024 23:43:25 -0400 Subject: [PATCH 03/12] web: Run formatter because I forgot to before --- web/packages/core/src/ruffle-player.ts | 22 +++++++++++++--------- web/packages/core/src/shadow-template.ts | 21 +++++++++++---------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/web/packages/core/src/ruffle-player.ts b/web/packages/core/src/ruffle-player.ts index 531dd038fd89..e33e39078603 100644 --- a/web/packages/core/src/ruffle-player.ts +++ b/web/packages/core/src/ruffle-player.ts @@ -360,8 +360,8 @@ export class RufflePlayer extends HTMLElement { if (videoHolder) { videoHolder.textContent = ""; } - } - + }; + modalElement.parentNode!.addEventListener("click", hideModal); const modalArea = modalElement.querySelector(".modal-area"); if (modalArea) { @@ -391,9 +391,15 @@ export class RufflePlayer extends HTMLElement { "#volume-mute", ) as HTMLLabelElement; const volumeIcons = [ - volumeControlsModal.querySelector("#volume-min") as HTMLLabelElement, - volumeControlsModal.querySelector("#volume-mid") as HTMLLabelElement, - volumeControlsModal.querySelector("#volume-max") as HTMLLabelElement, + volumeControlsModal.querySelector( + "#volume-min", + ) as HTMLLabelElement, + volumeControlsModal.querySelector( + "#volume-mid", + ) as HTMLLabelElement, + volumeControlsModal.querySelector( + "#volume-max", + ) as HTMLLabelElement, ]; const volumeSlider = volumeControlsModal.querySelector( "#volume-slider", @@ -405,16 +411,14 @@ export class RufflePlayer extends HTMLElement { const setVolumeIcon = () => { if (this.volumeSettings.isMuted) { volumeMuteIcon.style.display = "inline"; - volumeIcons.forEach(icon => { + volumeIcons.forEach((icon) => { icon.style.display = "none"; }); } else { volumeMuteIcon.style.display = "none"; const iconIndex = Math.round(this.volumeSettings.volume / 50); volumeIcons.forEach((icon, i) => { - icon.style.display = i == iconIndex - ? "inline" - : "none"; + icon.style.display = i === iconIndex ? "inline" : "none"; }); } }; diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index 338b7c849384..eabe0e9d335a 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -353,7 +353,7 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { padding: 4px 0 12px; border-bottom: 2px solid rgba(var(--modal-foreground-rgb), 0.3); }`, - + `#backup-saves { background-color: rgba(var(--modal-foreground-rgb), 0.2); padding: 4px 8px; @@ -368,17 +368,17 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { height: calc(100% - 45px); min-height: 30px; }`, - + `#local-saves td { border-bottom: 2px solid rgba(var(--modal-foreground-rgb), 0.15); height: 30px; }`, - + `#local-saves td:nth-child(1) { width: 100%; word-break: break-all; }`, - + `.save-option { display: inline-block; width: 24px; @@ -388,19 +388,19 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { vertical-align: middle; opacity: 0.4; }`, - + `#local-saves > tr:hover .save-option { opacity: 1; }`, - + `#download-save { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 -960 960 960' width='24px' fill='black'%3E%3Cpath d='M480-337q-8 0-15-2.5t-13-8.5L308-492q-12-12-11.5-28t11.5-28q12-12 28.5-12.5T365-549l75 75v-286q0-17 11.5-28.5T480-800q17 0 28.5 11.5T520-760v286l75-75q12-12 28.5-11.5T652-548q11 12 11.5 28T652-492L508-348q-6 6-13 8.5t-15 2.5ZM240-160q-33 0-56.5-23.5T160-240v-80q0-17 11.5-28.5T200-360q17 0 28.5 11.5T240-320v80h480v-80q0-17 11.5-28.5T760-360q17 0 28.5 11.5T800-320v80q0 33-23.5 56.5T720-160H240Z'/%3E%3C/svg%3E"); }`, - + `#replace-save { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 -1080 960 1200' width='24px' fill='black'%3E%3Cpath d='M440-367v127q0 17 11.5 28.5T480-200q17 0 28.5-11.5T520-240v-127l36 36q6 6 13.5 9t15 2.5q7.5-.5 14.5-3.5t13-9q11-12 11.5-28T612-388L508-492q-6-6-13-8.5t-15-2.5q-8 0-15 2.5t-13 8.5L348-388q-12 12-11.5 28t12.5 28q12 11 28 11.5t28-11.5l35-35ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h287q16 0 30.5 6t25.5 17l194 194q11 11 17 25.5t6 30.5v447q0 33-23.5 56.5T720-80H240Zm280-560q0 17 11.5 28.5T560-600h160L520-800v160Z'/%3E%3C/svg%3E"); }`, - + `#delete-save { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 -1020 960 1080' width='24px' fill='black'%3E%3Cpath d='M280-120q-33 0-56.5-23.5T200-200v-520q-17 0-28.5-11.5T160-760q0-17 11.5-28.5T200-800h160q0-17 11.5-28.5T400-840h160q17 0 28.5 11.5T600-800h160q17 0 28.5 11.5T800-760q0 17-11.5 28.5T760-720v520q0 33-23.5 56.5T680-120H280Zm120-160q17 0 28.5-11.5T440-320v-280q0-17-11.5-28.5T400-640q-17 0-28.5 11.5T360-600v280q0 17 11.5 28.5T400-280Zm160 0q17 0 28.5-11.5T600-320v-280q0-17-11.5-28.5T560-640q-17 0-28.5 11.5T520-600v280q0 17 11.5 28.5T560-280Z'/%3E%3C/svg%3E"); }`, @@ -842,8 +842,9 @@ volumeMuteIcon.title = text("volume-controls-unmute"); const volumeMinIcon = createLabelElement("volume-min", "mute-checkbox"); const volumeMidIcon = createLabelElement("volume-mid", "mute-checkbox"); const volumeMaxIcon = createLabelElement("volume-max", "mute-checkbox"); -[volumeMinIcon, volumeMidIcon, volumeMaxIcon] - .forEach(icon => icon.title = text("volume-controls-mute")); +[volumeMinIcon, volumeMidIcon, volumeMaxIcon].forEach( + (icon) => (icon.title = text("volume-controls-mute")), +); const volumeSliderText = createElement("span", "volume-slider-text"); const volumeModalClose = createElement("span", undefined, "close-modal"); From 698a9d847236d2ddae11b460a0daefbea7fce013 Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:27:20 -0400 Subject: [PATCH 04/12] web: Don't unnecessarily populate Save Manager --- web/packages/core/src/ruffle-player.ts | 40 ++++++++++++++++++-------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/web/packages/core/src/ruffle-player.ts b/web/packages/core/src/ruffle-player.ts index e33e39078603..f31d20ee44c5 100644 --- a/web/packages/core/src/ruffle-player.ts +++ b/web/packages/core/src/ruffle-player.ts @@ -1271,6 +1271,29 @@ export class RufflePlayer extends HTMLElement { } } + /** + * Check if there are any saves. + * + * @returns True if there is at least one save. + */ + private checkSaves(): boolean { + if (!this.saveManager.querySelector("#local-saves")) { + return false; + } + try { + if (localStorage === null) { + return false; + } + } catch (e: unknown) { + return false; + } + return Object.keys(localStorage).some((key) => { + const solName = key.split("/").pop(); + const solData = localStorage.getItem(key); + return solName && solData && this.isB64SOL(solData); + }); + } + /** * Delete local save. * @@ -1287,17 +1310,10 @@ export class RufflePlayer extends HTMLElement { * Puts the local save SOL file keys in a table. */ private populateSaves(): void { - const saveTable = this.saveManager.querySelector("#local-saves"); - if (!saveTable) { - return; - } - try { - if (localStorage === null) { - return; - } - } catch (e: unknown) { + if (!this.checkSaves()) { return; } + const saveTable = this.saveManager.querySelector("#local-saves")!; saveTable.textContent = ""; Object.keys(localStorage).forEach((key) => { const solName = key.split("/").pop(); @@ -1394,6 +1410,7 @@ export class RufflePlayer extends HTMLElement { * Opens the save manager. */ private async openSaveManager(): Promise { + this.populateSaves(); this.saveManager.classList.remove("hidden"); } @@ -1530,9 +1547,8 @@ export class RufflePlayer extends HTMLElement { navigator.clipboard.writeText(this.getPanicData()), }); } - this.populateSaves(); - const localSaveTable = this.saveManager.querySelector("#local-saves"); - if (localSaveTable && localSaveTable.textContent !== "") { + + if (this.checkSaves()) { items.push({ text: text("context-menu-open-save-manager"), onClick: this.openSaveManager.bind(this), From 078d1c85d30bea18a2dc8be7b48950101e3c60e0 Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:35:01 -0400 Subject: [PATCH 05/12] web: Enforce nowrap on context menu items This resolves (literal) edge cases where opening the context menu too close to the right-hand side of the player would inadvertently wrap the lengthiest menu item. --- web/packages/core/src/shadow-template.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index eabe0e9d335a..1ef381e9a844 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -231,6 +231,7 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { font-size: 14px; text-align: left; list-style: none; + white-space: nowrap; padding: 3px 0; margin: 0; }`, From 51d625978d666c6b6a5869a298ec0c385dd0b6aa Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:37:56 -0400 Subject: [PATCH 06/12] web: Use transforms for modals/context menu instead of position This lets the context menu escape the bounds of the player and keeps modals in the center of the player at small widths. --- web/packages/core/src/ruffle-player.ts | 33 ++++++++++++++---------- web/packages/core/src/shadow-template.ts | 9 ++++--- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/web/packages/core/src/ruffle-player.ts b/web/packages/core/src/ruffle-player.ts index f31d20ee44c5..15ebb7a9982f 100644 --- a/web/packages/core/src/ruffle-player.ts +++ b/web/packages/core/src/ruffle-player.ts @@ -1718,22 +1718,29 @@ export class RufflePlayer extends HTMLElement { } } - // Place a context menu in the top-left corner, so - // its `clientWidth` and `clientHeight` are not clamped. - this.contextMenuElement.style.left = "0"; - this.contextMenuElement.style.top = "0"; this.contextMenuOverlay.classList.remove("hidden"); - const rect = this.getBoundingClientRect(); - const x = event.clientX - rect.x; - const y = event.clientY - rect.y; - const maxX = rect.width - this.contextMenuElement.clientWidth - 1; - const maxY = rect.height - this.contextMenuElement.clientHeight - 1; + const playerRect = this.getBoundingClientRect(); + const contextMenuRect = this.contextMenuElement.getBoundingClientRect(); - this.contextMenuElement.style.left = - Math.floor(Math.min(x, maxX)) + "px"; - this.contextMenuElement.style.top = - Math.floor(Math.min(y, maxY)) + "px"; + // Keep the entire context menu inside the viewport. + // TODO: Allow the context menu to escape the document body while being mindful of scrollbars. + const overflowX = Math.max( + 0, + event.clientX + + contextMenuRect.width - + document.documentElement.clientWidth, + ); + const overflowY = Math.max( + 0, + event.clientY + + contextMenuRect.height - + document.documentElement.clientHeight, + ); + const x = event.clientX - playerRect.x - overflowX; + const y = event.clientY - playerRect.y - overflowY; + + this.contextMenuElement.style.transform = `translate(${x}px, ${y}px)`; } private hideContextMenu(): void { diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index 1ef381e9a844..9b4039cd8f2c 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -320,19 +320,20 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { }`, `.modal-area { - position: sticky; + position: relative; + left: 50%; + transform: translateX(-50%); background-color: var(--modal-background); color: rgb(var(--modal-foreground-rgb)); width: fit-content; - margin: auto; padding: 8px 12px; border-radius: 12px; box-shadow: 0 2px 6px 0px #0008; }`, `#modal-area { - width: min(100%, 450px); - height: min(100%, 300px); + width: 450px; + height: 300px; }`, `.close-modal { From 93e8581b20715e9286ed21458e0f196c061a1b7e Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Fri, 21 Jun 2024 21:13:13 -0400 Subject: [PATCH 07/12] web: Don't close modal upon right click There was a general consensus in the Ruffle Discord that this functionality was awkward and unneeded (on top of already not working as intended). --- web/packages/core/src/ruffle-player.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/web/packages/core/src/ruffle-player.ts b/web/packages/core/src/ruffle-player.ts index 15ebb7a9982f..1ae55b185722 100644 --- a/web/packages/core/src/ruffle-player.ts +++ b/web/packages/core/src/ruffle-player.ts @@ -1636,12 +1636,7 @@ export class RufflePlayer extends HTMLElement { event.preventDefault(); - let modalOpen = false; - this.shadow.querySelectorAll(".modal:not(.hidden)").forEach((modal) => { - modal.classList.add("hidden"); - modalOpen = true; - }); - if (modalOpen) { + if (this.shadow.querySelectorAll(".modal:not(.hidden)").length !== 0) { return; } From 073169979d3e291f6c134a8fd8f80c566594a130 Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:41:37 -0400 Subject: [PATCH 08/12] web: Improve video modal --- web/packages/core/src/shadow-template.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index 9b4039cd8f2c..b0ac64d0ab5c 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -411,13 +411,22 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { display: none; }`, + `#video-modal .modal-area { + width: 95%; + height: 95%; + box-sizing: border-box; + }`, + `#video-holder { - padding-top: 20px; + height: 100%; + box-sizing: border-box; + padding: 36px 4px 6px; }`, `#video-holder video { - max-width: 100%; - height: calc(100% - 58px); + width: 100%; + height: 100%; + background-color: black; }`, `#volume-controls { From a972e27dcfad68efc73664d3f9e89f1e26de1703 Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Sun, 23 Jun 2024 20:59:36 -0400 Subject: [PATCH 09/12] web: Improve hardware acceleration modal --- web/packages/core/src/ruffle-player.ts | 2 +- web/packages/core/src/shadow-template.ts | 41 +++++++++++++--------- web/packages/core/texts/en-US/messages.ftl | 3 +- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/web/packages/core/src/ruffle-player.ts b/web/packages/core/src/ruffle-player.ts index 1ae55b185722..88aaf6056405 100644 --- a/web/packages/core/src/ruffle-player.ts +++ b/web/packages/core/src/ruffle-player.ts @@ -285,7 +285,7 @@ export class RufflePlayer extends HTMLElement { this.addVolumeControlsJavaScript(this.volumeControls); const backupSaves = ( - this.saveManager.querySelector("#backup-saves") + this.saveManager.querySelector(".modal-button") ); if (backupSaves) { backupSaves.addEventListener("click", this.backupSaves.bind(this)); diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index b0ac64d0ab5c..3dd4c14f5d6e 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -344,6 +344,16 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { filter: var(--modal-foreground-filter); }`, + `.modal-button { + display: inline-block; + background-color: rgba(var(--modal-foreground-rgb), 0.2); + color: rgb(var(--modal-foreground-rgb)); + text-decoration: none; + padding: 4px 8px; + border-radius: 6px; + cursor: pointer; + }`, + `:not(#volume-controls) > .close-modal { position: absolute; top: 14px; @@ -352,17 +362,10 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { `.general-save-options { text-align: center; - padding: 4px 0 12px; + padding-bottom: 8px; border-bottom: 2px solid rgba(var(--modal-foreground-rgb), 0.3); }`, - `#backup-saves { - background-color: rgba(var(--modal-foreground-rgb), 0.2); - padding: 4px 8px; - border-radius: 6px; - cursor: pointer; - }`, - `#local-saves { border-collapse: collapse; overflow-y: auto; @@ -469,13 +472,16 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { user-select: none; }`, - `.acceleration-link { - color: var(--ruffle-blue); - text-decoration: none; + `#hardware-acceleration-modal .modal-area { + text-align: center; + padding: 16px 48px; + width: 95%; + box-sizing: border-box; }`, - `.acceleration-link:hover { - text-decoration: underline; + `#acceleration-text { + display: block; + margin-bottom: 8px; }`, /* Handle preferred color scheme. */ @@ -829,7 +835,7 @@ const generalSaveOptions = createElement( undefined, "general-save-options", ); -const backupSaves = createElement("span", "backup-saves"); +const backupSaves = createElement("span", undefined, "modal-button"); const localSaves = createElement("table", "local-saves"); // Volume control elements @@ -873,12 +879,14 @@ const hardwareModal = createElement( ); const hardwareModalArea = createElement("div", undefined, "modal-area"); const hardwareModalClose = createElement("span", undefined, "close-modal"); +const hardwareModalText = createElement("span", "acceleration-text"); +hardwareModalText.textContent = text("enable-hardware-acceleration"); const hardwareModalLink = document.createElement("a"); hardwareModalLink.href = "https://github.com/ruffle-rs/ruffle/wiki/Frequently-Asked-Questions-For-Users#chrome-hardware-acceleration"; hardwareModalLink.target = "_blank"; -hardwareModalLink.className = "acceleration-link"; -hardwareModalLink.textContent = text("enable-hardware-acceleration"); +hardwareModalLink.className = "modal-button"; +hardwareModalLink.textContent = text("enable-hardware-acceleration-link"); // Clipboard message const clipboardModal = createElement("div", "clipboard-modal", "modal hidden"); @@ -982,6 +990,7 @@ appendElement(videoModalArea, videoHolder); appendElement(ruffleShadowTemplate.content, hardwareModal); appendElement(hardwareModal, hardwareModalArea); appendElement(hardwareModalArea, hardwareModalClose); +appendElement(hardwareModalArea, hardwareModalText); appendElement(hardwareModalArea, hardwareModalLink); // Clipboard modal append appendElement(ruffleShadowTemplate.content, clipboardModal); diff --git a/web/packages/core/texts/en-US/messages.ftl b/web/packages/core/texts/en-US/messages.ftl index 1ae7d4328604..a4a950894090 100644 --- a/web/packages/core/texts/en-US/messages.ftl +++ b/web/packages/core/texts/en-US/messages.ftl @@ -10,7 +10,8 @@ update-ruffle = Update Ruffle ruffle-demo = Web Demo ruffle-desktop = Desktop Application ruffle-wiki = View Ruffle Wiki -enable-hardware-acceleration = It looks like hardware acceleration is not enabled. While Ruffle may work, it could be unreasonably slow. You can find out how to enable hardware acceleration by following this link. +enable-hardware-acceleration = It looks like hardware acceleration is disabled. While Ruffle may work, it could be very slow. You can find out how to enable hardware acceleration by following the link below: +enable-hardware-acceleration-link = FAQ - Chrome Hardware Acceleration view-error-details = View Error Details open-in-new-tab = Open in a new tab click-to-unmute = Click to unmute From 0f5f3839fe890f53741e84077afd580fe9b236b1 Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Tue, 25 Jun 2024 00:15:16 -0400 Subject: [PATCH 10/12] web: Improve clipboard modal --- web/packages/core/src/shadow-template.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index 3dd4c14f5d6e..f4cc80c1091a 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -484,6 +484,15 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { margin-bottom: 8px; }`, + `#clipboard-modal h2 { + margin-top: 4px; + margin-right: 36px; + }`, + + `#clipboard-modal p:last-child { + margin-bottom: 2px; + }`, + /* Handle preferred color scheme. */ `@media (prefers-color-scheme: light) { :host { @@ -892,7 +901,6 @@ hardwareModalLink.textContent = text("enable-hardware-acceleration-link"); const clipboardModal = createElement("div", "clipboard-modal", "modal hidden"); const clipboardModalArea = createElement("div", undefined, "modal-area"); const clipboardModalClose = createElement("span", undefined, "close-modal"); -clipboardModalClose.textContent = "\u00D7"; const clipboardModalHeading = createElement("h2", undefined); clipboardModalHeading.textContent = text("clipboard-message-title"); const clipboardModalTextDescription = createElement( From 17c88f257d788f45c163f983cf401f72b50da3c5 Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Sat, 29 Jun 2024 13:02:07 -0400 Subject: [PATCH 11/12] web: Display save manager correctly in quirks mode --- web/packages/core/src/shadow-template.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/packages/core/src/shadow-template.ts b/web/packages/core/src/shadow-template.ts index f4cc80c1091a..eae1e60ba1b8 100644 --- a/web/packages/core/src/shadow-template.ts +++ b/web/packages/core/src/shadow-template.ts @@ -367,6 +367,7 @@ export function applyStaticStyles(styleElement: HTMLStyleElement) { }`, `#local-saves { + color: inherit; border-collapse: collapse; overflow-y: auto; display: block; From 844bf022673470d1c345dde028989b72b21794d7 Mon Sep 17 00:00:00 2001 From: Wumbo <58399748+WumboSpasm@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:37:02 -0400 Subject: [PATCH 12/12] web: Fix keyboard input test (hopefully) --- .../selfhosted/test/integration_tests/keyboard_input/test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/packages/selfhosted/test/integration_tests/keyboard_input/test.ts b/web/packages/selfhosted/test/integration_tests/keyboard_input/test.ts index e26d4ba4bc74..8d6fa97ea165 100644 --- a/web/packages/selfhosted/test/integration_tests/keyboard_input/test.ts +++ b/web/packages/selfhosted/test/integration_tests/keyboard_input/test.ts @@ -11,6 +11,8 @@ describe("Key up and down events work", () => { it("'a' key is recognised", async () => { const player = await browser.$(""); await player.click(); + // Extra safety click in case there's a modal + await player.click(); await browser.keys("a"); const actualOutput = await getTraceOutput(browser, player);