From 5af877bcaa64fd0541e70cb632abe0707d2fd4c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Thu, 8 Aug 2024 10:29:31 +0200 Subject: [PATCH] feat: add Appearance panel to Settings modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This features two knobs: theming and indent. I was curious whether I could actually hand the theme switching to the parent, so I created an event dispatcher for it. It works! But I had to hack the Svelte component a little. I also need to sync it two the theme switcher in the popup and go over every component that is focused on light mode only at the moment. To limit the work I will constraint myself to the Profile page here. If the theme is meant to apply to other pages as well, we will need to persist the setting. That means a database in Anvil, I guess. I'm not willing to take that step right now. It must come at some point (e.g. when talking to Vervis to actually inform about changes made in Anvil). Signed-off-by: André Jaenisch --- package-lock.json | 4 +- package.json | 2 +- src/lib/components/molecules/Settings.svelte | 21 + .../molecules/SettingsAppearance.svelte | 68 +++ src/lib/i18n/locales/bg.json | 13 +- src/lib/i18n/locales/de.json | 521 +++++++++--------- src/lib/i18n/locales/en.json | 13 +- src/lib/i18n/locales/he.json | 13 +- src/lib/i18n/locales/pl.json | 13 +- .../molecules/SettingsAppearance.stories.ts | 26 + .../molecules/SettingsAppearance.test.ts | 82 +++ 11 files changed, 514 insertions(+), 262 deletions(-) create mode 100644 src/lib/components/molecules/SettingsAppearance.svelte create mode 100644 stories/molecules/SettingsAppearance.stories.ts create mode 100644 tests/components/molecules/SettingsAppearance.test.ts diff --git a/package-lock.json b/package-lock.json index 1e49ca4..1e2227e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "anvil", - "version": "0.0.12", + "version": "0.0.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "anvil", - "version": "0.0.12", + "version": "0.0.13", "dependencies": { "@floating-ui/dom": "1.6.8", "@fontsource/spline-sans-mono": "5.0.20", diff --git a/package.json b/package.json index 79ba940..aa5f996 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "anvil", - "version": "0.0.12", + "version": "0.0.13", "private": true, "scripts": { "dev": "vite dev", diff --git a/src/lib/components/molecules/Settings.svelte b/src/lib/components/molecules/Settings.svelte index 173b039..7f1a104 100644 --- a/src/lib/components/molecules/Settings.svelte +++ b/src/lib/components/molecules/Settings.svelte @@ -13,15 +13,34 @@ You should have received a copy of the GNU Affero General Public License along w
@@ -33,5 +52,7 @@ You should have received a copy of the GNU Affero General Public License along w {:else if activeSetting === 'ssh_gpg_keys'} + {:else if activeSetting === 'appearance'} + {/if}
diff --git a/src/lib/components/molecules/SettingsAppearance.svelte b/src/lib/components/molecules/SettingsAppearance.svelte new file mode 100644 index 0000000..ffa45bf --- /dev/null +++ b/src/lib/components/molecules/SettingsAppearance.svelte @@ -0,0 +1,68 @@ + + + + +
+
+ {$_('settings.appearance.headline')} +
+
+
+ {$_('settings.appearance.theme.headline')} +
+
+ + + +
+
+ +
+
+ {$_('settings.appearance.tab_indent.headline')} +
+

{$_('settings.appearance.tab_indent.intro')}

+ +
+
diff --git a/src/lib/i18n/locales/bg.json b/src/lib/i18n/locales/bg.json index e403b4f..0d14bac 100644 --- a/src/lib/i18n/locales/bg.json +++ b/src/lib/i18n/locales/bg.json @@ -192,7 +192,18 @@ } }, "appearance": { - "label": "" + "headline": "", + "label": "", + "tab_indent": { + "headline": "", + "intro": "" + }, + "theme": { + "auto": "", + "dark": "", + "headline": "", + "light": "" + } }, "notifications": { "label": "" diff --git a/src/lib/i18n/locales/de.json b/src/lib/i18n/locales/de.json index 13f00ec..6f3ec0d 100644 --- a/src/lib/i18n/locales/de.json +++ b/src/lib/i18n/locales/de.json @@ -1,257 +1,268 @@ { - "overlay": { - "avatar": { - "about_anvil": "Über Anvil", - "dark_mode": "", - "profile": "", - "settings": "", - "sign_out": "" - } - }, - "page": { - "import_project": { - "form": { - "avatar": "Avatar", - "components": "Komponenten", - "fields": { - "avatar": { - "label": "Bild hochladen" - }, - "description": { - "label": "Beschreibung", - "placeholder": "Beschreibe das Projekt in unter 100 Zeichen..." - }, - "issues": { - "label": "Issues" - }, - "name": { - "error": "Dieses Feld ist erforderlich", - "label": "Name", - "placeholder": "Name des Projekts..." - }, - "pr": { - "label": "Pullrequest" - }, - "repository": { - "hint": "optional: von Git-Repository importieren", - "label": "Repository", - "placeholder": "URL des Git-Repositorys" - } - }, - "submit": "Projekt erstellen" - }, - "heading": "Projekt erstellen", - "intro": "Neues F2-Projekt zu Anvil hinzufügen." - }, - "login": { - "form": { - "fields": { - "account": { - "label": "Accountname" - }, - "passphrase": { - "label": "Passwort", - "placeholder": "Passwort" - }, - "server": { - "label": "F2-Server" - } - }, - "reset": "Passwort zurücksetzen", - "submit": "Einloggen", - "validation": { - "incorrect": "Accountname oder Kennwort sind falsch.", - "missing": "Das Account-Feld ist verbindlich." - } - }, - "heading": "Einloggen", - "intro": "Die Einwahldaten ausfüllen um Anvil mit deinem F2-Account zu benutzen." - }, - "profile": { - "activities": { - "block_or_report": "{blockElementOpen}blockieren{blockElementClose} oder {reportElementOpen}melden{reportElementClose}", - "like": "Gefällt mir" - }, - "heading": "Profil für", - "history": { - "activities": { - "commits": { - "actions": { - "browse": "", - "copy": "" - }, - "number": "", - "relative_time": "" - }, - "setup": { - "description": "Das F2-Konto @{username}@{instance} wurde erfolgreich innerhalb von {created_with} erstellt", - "summary": "Kontoeinstellungen" - } - }, - "heading": "Aktivitäten" - }, - "menu": { - "actions": { - "fork": "", - "star": "", - "watch": "" - }, - "buttons": { - "avatar": "", - "issues": "", - "notifications": "", - "prs": "" - }, - "details": { - "branches": "", - "commits": "", - "files": "", - "issues": "", - "merge_requests": "", - "moderation": "", - "overview": "", - "people": "", - "repository": "", - "roles": "", - "tags": "" - } - }, - "projects": { - "actions": { - "fork": "Fork", - "star": "Favorisieren", - "watch": "Beobachten" - }, - "add_or_import": "{addElementOpen}Ein Projekt hinzufügen{addElementClose} oder {importElementOpen}Ein Projekt importieren{importElementClose}.", - "empty": "Bisher keine Projekte hinzugefügt.", - "heading": "Projekte" - }, - "repositories": { - "heading": "" - } - }, - "projects": { - "file_table": { - "updated": "Aktualisiert: {relativeTime}" - }, - "form": { - "fields": { - "more_filters": { - "submit": "Weitere Filter" - }, - "projects": { - "submit": "Meine Projekte" - }, - "search": { - "placeholder": "Suchen oder filtern", - "submit": "Absenden" - }, - "starred": { - "submit": "Favorisiert" - } - } - }, - "nav": { - "next": "Weiter", - "previous": "Zurück" - }, - "table": { - "heading": { - "last_updated": "Letzte Aktualisierung", - "name": "Name" - } - } - }, - "welcome": { - "create": "", - "intro": "", - "headline": "Willkommen bei Anvil", - "login": "", - "logo": { - "alt": "" - }, - "reset": "" - } - }, - "settings": { - "headline": "", - "account": { - "delete": "", - "f2": { - "label": "", - "placeholder": "" - }, - "headline": "", - "label": "", - "password": { - "label": "", - "reset": "" - }, - "verification": { - "label": "" - } - }, - "appearance": { - "label": "" - }, - "notifications": { - "label": "" - }, - "profile": { - "avatar": { - "headline": "", - "remove": "", - "upload": "" - }, - "bio": { - "label": "", - "placeholder": "" - }, - "extra": { - "content": { - "placeholder": "" - }, - "headline": "", - "hint": "", - "label": { - "placeholder": "" - } - }, - "headline": "", - "label": "", - "name": { - "label": "", - "placeholder": "" - }, - "pronouns": { - "label": "" - } - }, - "ssh_gpg_keys": { - "gpg": { - "add": "", - "headline": "", - "key": { - "placeholder": "" - }, - "remove": "", - "title": { - "placeholder": "" - } - }, - "headline": "", - "label": "", - "ssh": { - "add": "", - "headline": "", - "key": { - "placeholder": "" - }, - "remove": "", - "title": { - "placeholder": "" - } - } - } - } + "overlay": { + "avatar": { + "about_anvil": "Über Anvil", + "dark_mode": "", + "profile": "", + "settings": "", + "sign_out": "" + } + }, + "page": { + "import_project": { + "form": { + "avatar": "Avatar", + "components": "Komponenten", + "fields": { + "avatar": { + "label": "Bild hochladen" + }, + "description": { + "label": "Beschreibung", + "placeholder": "Beschreibe das Projekt in unter 100 Zeichen..." + }, + "issues": { + "label": "Issues" + }, + "name": { + "error": "Dieses Feld ist erforderlich", + "label": "Name", + "placeholder": "Name des Projekts..." + }, + "pr": { + "label": "Pullrequest" + }, + "repository": { + "hint": "optional: von Git-Repository importieren", + "label": "Repository", + "placeholder": "URL des Git-Repositorys" + } + }, + "submit": "Projekt erstellen" + }, + "heading": "Projekt erstellen", + "intro": "Neues F2-Projekt zu Anvil hinzufügen." + }, + "login": { + "form": { + "fields": { + "account": { + "label": "Accountname" + }, + "passphrase": { + "label": "Passwort", + "placeholder": "Passwort" + }, + "server": { + "label": "F2-Server" + } + }, + "reset": "Passwort zurücksetzen", + "submit": "Einloggen", + "validation": { + "incorrect": "Accountname oder Kennwort sind falsch.", + "missing": "Das Account-Feld ist verbindlich." + } + }, + "heading": "Einloggen", + "intro": "Die Einwahldaten ausfüllen um Anvil mit deinem F2-Account zu benutzen." + }, + "profile": { + "activities": { + "block_or_report": "{blockElementOpen}blockieren{blockElementClose} oder {reportElementOpen}melden{reportElementClose}", + "like": "Gefällt mir" + }, + "heading": "Profil für", + "history": { + "activities": { + "commits": { + "actions": { + "browse": "", + "copy": "" + }, + "number": "", + "relative_time": "" + }, + "setup": { + "description": "Das F2-Konto @{username}@{instance} wurde erfolgreich innerhalb von {created_with} erstellt", + "summary": "Kontoeinstellungen" + } + }, + "heading": "Aktivitäten" + }, + "menu": { + "actions": { + "fork": "", + "star": "", + "watch": "" + }, + "buttons": { + "avatar": "", + "issues": "", + "notifications": "", + "prs": "" + }, + "details": { + "branches": "", + "commits": "", + "files": "", + "issues": "", + "merge_requests": "", + "moderation": "", + "overview": "", + "people": "", + "repository": "", + "roles": "", + "tags": "" + } + }, + "projects": { + "actions": { + "fork": "Fork", + "star": "Favorisieren", + "watch": "Beobachten" + }, + "add_or_import": "{addElementOpen}Ein Projekt hinzufügen{addElementClose} oder {importElementOpen}Ein Projekt importieren{importElementClose}.", + "empty": "Bisher keine Projekte hinzugefügt.", + "heading": "Projekte" + }, + "repositories": { + "heading": "" + } + }, + "projects": { + "file_table": { + "updated": "Aktualisiert: {relativeTime}" + }, + "form": { + "fields": { + "more_filters": { + "submit": "Weitere Filter" + }, + "projects": { + "submit": "Meine Projekte" + }, + "search": { + "placeholder": "Suchen oder filtern", + "submit": "Absenden" + }, + "starred": { + "submit": "Favorisiert" + } + } + }, + "nav": { + "next": "Weiter", + "previous": "Zurück" + }, + "table": { + "heading": { + "last_updated": "Letzte Aktualisierung", + "name": "Name" + } + } + }, + "welcome": { + "create": "", + "intro": "", + "headline": "Willkommen bei Anvil", + "login": "", + "logo": { + "alt": "" + }, + "reset": "" + } + }, + "settings": { + "headline": "", + "account": { + "delete": "", + "f2": { + "label": "", + "placeholder": "" + }, + "headline": "", + "label": "", + "password": { + "label": "", + "reset": "" + }, + "verification": { + "label": "" + } + }, + "appearance": { + "headline": "", + "label": "", + "tab_indent": { + "headline": "", + "intro": "" + }, + "theme": { + "auto": "", + "dark": "", + "headline": "", + "light": "" + } + }, + "notifications": { + "label": "" + }, + "profile": { + "avatar": { + "headline": "", + "remove": "", + "upload": "" + }, + "bio": { + "label": "", + "placeholder": "" + }, + "extra": { + "content": { + "placeholder": "" + }, + "headline": "", + "hint": "", + "label": { + "placeholder": "" + } + }, + "headline": "", + "label": "", + "name": { + "label": "", + "placeholder": "" + }, + "pronouns": { + "label": "" + } + }, + "ssh_gpg_keys": { + "gpg": { + "add": "", + "headline": "", + "key": { + "placeholder": "" + }, + "remove": "", + "title": { + "placeholder": "" + } + }, + "headline": "", + "label": "", + "ssh": { + "add": "", + "headline": "", + "key": { + "placeholder": "" + }, + "remove": "", + "title": { + "placeholder": "" + } + } + } + } } diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json index d61f59e..dc048e9 100644 --- a/src/lib/i18n/locales/en.json +++ b/src/lib/i18n/locales/en.json @@ -192,7 +192,18 @@ } }, "appearance": { - "label": "Appearance" + "headline": "Appearance", + "label": "Appearance", + "tab_indent": { + "headline": "Tab indenting", + "intro": "Number of spaces per tab in code view." + }, + "theme": { + "auto": "Auto", + "dark": "Dark", + "headline": "Theme", + "light": "Light" + } }, "notifications": { "label": "Notifications" diff --git a/src/lib/i18n/locales/he.json b/src/lib/i18n/locales/he.json index 6724254..c8889c6 100644 --- a/src/lib/i18n/locales/he.json +++ b/src/lib/i18n/locales/he.json @@ -192,7 +192,18 @@ } }, "appearance": { - "label": "" + "headline": "", + "label": "", + "tab_indent": { + "headline": "", + "intro": "" + }, + "theme": { + "auto": "", + "dark": "", + "headline": "", + "light": "" + } }, "notifications": { "label": "" diff --git a/src/lib/i18n/locales/pl.json b/src/lib/i18n/locales/pl.json index 5a093ed..b6b8bca 100644 --- a/src/lib/i18n/locales/pl.json +++ b/src/lib/i18n/locales/pl.json @@ -192,7 +192,18 @@ } }, "appearance": { - "label": "" + "headline": "", + "label": "", + "tab_indent": { + "headline": "", + "intro": "" + }, + "theme": { + "auto": "", + "dark": "", + "headline": "", + "light": "" + } }, "notifications": { "label": "" diff --git a/stories/molecules/SettingsAppearance.stories.ts b/stories/molecules/SettingsAppearance.stories.ts new file mode 100644 index 0000000..7f54190 --- /dev/null +++ b/stories/molecules/SettingsAppearance.stories.ts @@ -0,0 +1,26 @@ +/* Stories for SettingsAppearance molecule. + * Copyright (C) 2024 André Jaenisch + * SPDX-FileCopyrightText: 2024 André Jaenisch + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. If not, see . + */ + +import type { Meta, StoryObj } from '@storybook/svelte'; + +import SettingsAppearance from '$lib/components/molecules/SettingsAppearance.svelte'; + +const meta = { + title: 'Molecules/SettingsAppearance', + component: SettingsAppearance, + tags: ['autodocs'] +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Plain: Story = {}; diff --git a/tests/components/molecules/SettingsAppearance.test.ts b/tests/components/molecules/SettingsAppearance.test.ts new file mode 100644 index 0000000..195b333 --- /dev/null +++ b/tests/components/molecules/SettingsAppearance.test.ts @@ -0,0 +1,82 @@ +/* Component test for SettingsAppearance molecule. + * Copyright (C) 2024 André Jaenisch + * SPDX-FileCopyrightText: 2024 André Jaenisch + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. If not, see . + */ + +import '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/svelte'; +import { init, locale, register } from 'svelte-i18n'; + +import SettingsAppearance from '../../../src/lib/components/molecules/SettingsAppearance.svelte'; +import enMessages from '../../../src/lib/i18n/locales/en.json'; + +describe('SettingsAppearance.svelte', () => { + beforeEach(() => { + register('en', () => import('../../../src/lib/i18n/locales/en.json')); + init({ fallbackLocale: 'en', initialLocale: 'en' }); + locale.set('en'); + }); + + it('should mount', () => { + // Arrange + // Nothing to prepare + + // Act + const { container } = render(SettingsAppearance); + + // Assert + expect(container).toBeTruthy(); + }); + + it('should have a radio group for themes', () => { + // Arrange + // Nothing to prepare + + // Act + render(SettingsAppearance); + + // Assert + expect(screen.getByLabelText(enMessages.settings.appearance.theme.light)).toBeInTheDocument(); + expect(screen.getByLabelText(enMessages.settings.appearance.theme.dark)).toBeInTheDocument(); + expect(screen.getByLabelText(enMessages.settings.appearance.theme.auto)).toBeInTheDocument(); + }); + + describe('when clicking a theme', () => { + it('should dispatch a switch-theme event', () => { + // Arrange + const listener = vi.fn(); + + // Act + const { component } = render(SettingsAppearance); + component.$on('switch-theme', listener); + const darkTheme = screen.getByLabelText(enMessages.settings.appearance.theme.dark); + darkTheme.click(); + const ev = new CustomEvent({ detail: 'dark' }); + + // Assert + expect(listener).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith(ev); + }); + }); + + // TODO: Mark up proper label and input + it.skip('should have a tab indent input', () => { + // Arrange + // Nothing to prepare + + // Act + render(SettingsAppearance); + + // Assert + expect( + screen.getByPlaceholderText(enMessages.settings.appearance.tab_indent.label) + ).toBeInTheDocument(); + }); +});