From 24a54dac56469acd5b98ce21dc123affa845cae9 Mon Sep 17 00:00:00 2001 From: IAmKontrast <57725912+IAmKontrast@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:50:14 +0200 Subject: [PATCH] Add OS-independent dark-mode setting (#47) * Add OS-independent dark-mode setting --- src/dataTypes.ts | 9 ++++++++ src/index.ts | 6 +++++- src/settings/general.ts | 42 ++++++++++++++++++++++++++++++++++++-- src/settings/settings.ts | 4 +++- src/style/main.scss | 44 +++++++++++++++++++++++++--------------- src/style/theme.ts | 24 ++++++++++++++++++++++ src/tooltip/tooltip.ts | 8 ++++---- 7 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 src/style/theme.ts diff --git a/src/dataTypes.ts b/src/dataTypes.ts index 821ecc0..71a91e6 100644 --- a/src/dataTypes.ts +++ b/src/dataTypes.ts @@ -120,6 +120,15 @@ export enum Type { Url = "url", } +/** + * Possible themes + */ +export enum Theme { + Light = "light", + Dark = "dark", + Device = "device" +} + /** * A function to select a [Type] query parameter from a given element. */ diff --git a/src/index.ts b/src/index.ts index 3b56603..f2bf52a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,14 +6,18 @@ import {initGeneralSettings} from "./settings/general"; import {runStashChecker} from "./stashChecker"; import {initStatistics} from "./settings/statistics"; import {initDisplaySettings} from "./settings/display"; +import {setTheme} from "./style/theme"; (async function () { initSettingsWindow(); initStatistics(); initGeneralSettings(); initDisplaySettings(); + + setTheme(); + await initEndpointSettings(); await initMenu(); await runStashChecker(); -})(); +})(); \ No newline at end of file diff --git a/src/settings/general.ts b/src/settings/general.ts index d6d719c..8dfe8ee 100644 --- a/src/settings/general.ts +++ b/src/settings/general.ts @@ -1,5 +1,6 @@ import {buttonDanger, getSettingsSection, newSettingsSection} from "./settings"; import {getValue, setValue, StorageKey} from "./storage"; +import {Theme} from "../dataTypes"; export enum OptionKey { showCheckMark = "showCheckMark", @@ -9,19 +10,21 @@ export enum OptionKey { checkMark = "checkMark", crossMark = "crossMark", warningMark = "warningMark", + theme = "theme", } const defaultBooleanOptions = new Map([ [OptionKey.showCheckMark, true], [OptionKey.showCrossMark, true], [OptionKey.showTags, true], - [OptionKey.showFiles, true] + [OptionKey.showFiles, true], ]); const defaultStringOptions = new Map([ [OptionKey.checkMark, "✓"], [OptionKey.crossMark, "✗"], - [OptionKey.warningMark, "!"] + [OptionKey.warningMark, "!"], + [OptionKey.theme, Theme.Device], ]); export const booleanOptions: Map = await getValue(StorageKey.BooleanOptions, defaultBooleanOptions) @@ -47,6 +50,7 @@ function populateGeneralSection(generalSection: HTMLElement) { tooltipSettings.append( checkBox(OptionKey.showTags, "Show tags"), checkBox(OptionKey.showFiles, "Show files"), + selectMenu(OptionKey.theme, "Theme", [Theme.Light, Theme.Dark, Theme.Device]), ); generalSection.appendChild(tooltipSettings); @@ -120,3 +124,37 @@ function charBox(key: OptionKey, label: string): HTMLElement { div.appendChild(inputElement) return div } + +function selectMenu(key: OptionKey, label: string, options: string[]): HTMLElement { + let div = document.createElement("div") + div.classList.add("option") + + let labelElement: HTMLLabelElement = document.createElement("label") + labelElement.htmlFor = `stashChecker-dropdown-${key}` + labelElement.innerHTML = label + + let selectElement = document.createElement("select") + selectElement.id = `stashChecker-dropdown-${key}` + selectElement.name = key + + // Set the currently selected option based on saved values + let currentSelection = stringOptions.get(key) ?? defaultStringOptions.get(key) ?? options[0] + options.forEach(option => { + let optionElement = document.createElement("option") + optionElement.value = option + optionElement.innerHTML = option + if (option === currentSelection) { + optionElement.selected = true + } + selectElement.appendChild(optionElement) + }) + + selectElement.addEventListener("change", () => { + stringOptions.set(key, selectElement.value) + void setValue(StorageKey.StringOptions, stringOptions) + }); + + div.appendChild(labelElement) + div.appendChild(selectElement) + return div +} \ No newline at end of file diff --git a/src/settings/settings.ts b/src/settings/settings.ts index d6775e9..bbafa32 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -2,6 +2,7 @@ import {clearObservers} from "../observer"; import {clearSymbols} from "../tooltip/tooltip"; import {runStashChecker} from "../stashChecker"; import {updateStatistics} from "./statistics"; +import {setTheme} from "../style/theme"; export function initSettingsWindow() { let settingsModal = document.createElement("div"); @@ -64,6 +65,7 @@ function closeSettingsWindow(this: HTMLElement, event: MouseEvent) { this.style.display = "none"; clearObservers() clearSymbols() + setTheme() void runStashChecker() } } @@ -82,4 +84,4 @@ export function buttonDanger(label: string, listener: (this: HTMLButtonElement, button.addEventListener("click", listener); button.innerHTML = label; return button -} +} \ No newline at end of file diff --git a/src/style/main.scss b/src/style/main.scss index ccb12e6..73a8c81 100644 --- a/src/style/main.scss +++ b/src/style/main.scss @@ -11,18 +11,16 @@ --stash-checker-color-card: #f2f2f2; } -@media (prefers-color-scheme: dark) { - :root { - --stash-checker-color-text: #e0e0e0; - --stash-checker-color-text-light: #707070; - --stash-checker-color-link-visited: #c7c7c7; - --stash-checker-color-link-hover: #f2f2f2; - --stash-checker-color-link-active: #039; - --stash-checker-color-border: #5a5a5a; - --stash-checker-color-border-light: #707070; - --stash-checker-color-bg: #202020; - --stash-checker-color-card: #464646; - } +.stashChecker-dark-mode { + --stash-checker-color-text: #e0e0e0; + --stash-checker-color-text-light: #707070; + --stash-checker-color-link-visited: #c7c7c7; + --stash-checker-color-link-hover: #f2f2f2; + --stash-checker-color-link-active: #039; + --stash-checker-color-border: #5a5a5a; + --stash-checker-color-border-light: #707070; + --stash-checker-color-bg: #202020; + --stash-checker-color-card: #464646; } .stashChecker { @@ -74,8 +72,8 @@ height: 100%; overflow: hidden auto; overscroll-behavior: contain; - background-color: rgb(0,0,0); // fallback - background-color: rgba(0,0,0,0.4); + background-color: rgb(0, 0, 0); // fallback + background-color: rgba(0, 0, 0, 0.4); } .stashChecker.settings { @@ -184,6 +182,10 @@ background-color: var(--stash-checker-color-bg); } +.stashChecker .option > select { + margin-left: 0.5rem; +} + .stashChecker .option > label { } @@ -207,30 +209,36 @@ font-size: 1rem; line-height: 1.5; border-radius: .25rem; - transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; + transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; } + .stashChecker.btn:not(:disabled):not(.disabled) { cursor: pointer; } + .stashChecker.btn:hover { color: #212529; text-decoration: none; } + .stashChecker.btn-primary { color: #fff; background-color: #137cbd; border-color: #137cbd; } + .stashChecker.btn-primary:hover { color: #fff; background-color: #10659a; border-color: #0e5e8f; } + .stashChecker.btn-danger { color: #fff; background-color: #db3737; border-color: #db3737; } + .stashChecker.btn-danger:hover { color: #fff; background-color: #c82424; @@ -240,15 +248,19 @@ .stashChecker.tooltip a:link { color: var(--stash-checker-color-text); } + .stashChecker.tooltip a:visited { color: var(--stash-checker-color-link-visited); } + .stashChecker.tooltip a:hover { color: var(--stash-checker-color-link-hover); } + .stashChecker.tooltip a:active { color: var(--stash-checker-color-link-active); } + .stashChecker.tooltip hr { margin-top: 0.5rem; margin-bottom: 0.5rem; @@ -266,4 +278,4 @@ .stashCheckerSymbol { font-size: inherit; -} +} \ No newline at end of file diff --git a/src/style/theme.ts b/src/style/theme.ts new file mode 100644 index 0000000..4dd0445 --- /dev/null +++ b/src/style/theme.ts @@ -0,0 +1,24 @@ +import {OptionKey, stringOptions} from "../settings/general"; +import {Theme} from "../dataTypes"; + +export function setTheme() { + + const osSetting = window.matchMedia("(prefers-color-scheme: dark)"); + + function toggleDarkMode(state: boolean | undefined) { + document.documentElement.classList.toggle("stashChecker-dark-mode", state); + } + + switch (stringOptions.get(OptionKey.theme)) { + case Theme.Light: + toggleDarkMode(false) + break; + case Theme.Dark: + toggleDarkMode(true) + break; + case Theme.Device: + default: + toggleDarkMode(osSetting.matches); + break; + } +} \ No newline at end of file diff --git a/src/tooltip/tooltip.ts b/src/tooltip/tooltip.ts index 137e40f..adae75d 100644 --- a/src/tooltip/tooltip.ts +++ b/src/tooltip/tooltip.ts @@ -67,7 +67,7 @@ function formatFileData(file: StashFile, queries: StashQuery[], target: Target, return `
${text}
` } -function formatTagPill(tag: {id: string, name: string}): string { +function formatTagPill(tag: { id: string, name: string }): string { return `${tag.name}`; } @@ -152,7 +152,7 @@ export function prefixSymbol( let queryTypes = [type]; // Specific query for this result let baseUrl = endpoint.url.replace(/\/graphql\/?$/, ""); - let query: StashQuery = { endpoint: endpoint.name, baseUrl, types: queryTypes }; + let query: StashQuery = {endpoint: endpoint.name, baseUrl, types: queryTypes}; // Add query, endpoint and display options to each new entry data.forEach((entry: StashEntry) => { entry.queries = [query] @@ -197,7 +197,7 @@ export function prefixSymbol( } symbol.style.color = "red"; tooltip = `${targetReadable} not in Stash
`; - } else if(new Set(data.map(e => e.endpoint)).size < data.length) { + } else if (new Set(data.map(e => e.endpoint)).size < data.length) { symbol.setAttribute("data-symbol", StashSymbol.Warning); symbol.innerHTML = `${stringOptions.get(OptionKey.warningMark)!} `; symbol.style.color = "orange"; @@ -223,4 +223,4 @@ export function prefixSymbol( tooltipWindow.innerHTML = tooltip; tooltipWindow.tabIndex = 0; (symbol as ReferenceElement)._tippy?.setContent(tooltipWindow); -} +} \ No newline at end of file