feat: add notifications to settings

This is currently a mock as we have no notification mechanism in place.
Also, I need to figure out how to talk to Vervis' API first.

That being said, this step allows for translations at least.

Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
This commit is contained in:
André Jaenisch 2024-08-14 17:13:07 +02:00
parent 119b681d4d
commit e95bb2e117
No known key found for this signature in database
GPG key ID: 5A668E771F1ED854
10 changed files with 549 additions and 272 deletions

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "anvil", "name": "anvil",
"version": "0.0.13", "version": "0.0.14",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "anvil", "name": "anvil",
"version": "0.0.13", "version": "0.0.14",
"dependencies": { "dependencies": {
"@floating-ui/dom": "1.6.8", "@floating-ui/dom": "1.6.8",
"@fontsource/spline-sans-mono": "5.0.20", "@fontsource/spline-sans-mono": "5.0.20",

View file

@ -1,6 +1,6 @@
{ {
"name": "anvil", "name": "anvil",
"version": "0.0.13", "version": "0.0.14",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",

View file

@ -15,6 +15,7 @@ You should have received a copy of the GNU Affero General Public License along w
import SettingsAccount from './SettingsAccount.svelte'; import SettingsAccount from './SettingsAccount.svelte';
import SettingsAppearance from './SettingsAppearance.svelte'; import SettingsAppearance from './SettingsAppearance.svelte';
import SettingsKeys from './SettingsKeys.svelte'; import SettingsKeys from './SettingsKeys.svelte';
import SettingsNotifications from './SettingsNotifications.svelte';
import SettingsSidebar from '../atoms/SettingsSidebar.svelte'; import SettingsSidebar from '../atoms/SettingsSidebar.svelte';
import SettingsProfile from './SettingsProfile.svelte'; import SettingsProfile from './SettingsProfile.svelte';
@ -36,5 +37,7 @@ You should have received a copy of the GNU Affero General Public License along w
<SettingsKeys /> <SettingsKeys />
{:else if activeSetting === 'appearance'} {:else if activeSetting === 'appearance'}
<SettingsAppearance /> <SettingsAppearance />
{:else if activeSetting === 'notifications'}
<SettingsNotifications />
{/if} {/if}
</div> </div>

View file

@ -0,0 +1,98 @@
<!--
SettingsNotifications 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 <http://www.gnu.org/licenses/>.
-->
<script>
import { SlideToggle } from '@skeletonlabs/skeleton';
import { _ } from 'svelte-i18n';
import { Eye24, EyeClosed24, Mention24 } from 'svelte-octicons';
const username = 'jane';
const instance = 'server.com';
let defaultNofication = 'mentioned';
let isNotifiedOnMention = false;
let labelYes = 'On';
let labelNo = 'Off';
</script>
<div class="flex flex-col flex-1 gap-6 pe-8 pb-8 ps-8 pt-20 bg-surface-100-800-token">
<div class="text-surface-500-400-token text-xl font-semibold">
{$_('settings.notifications.headline')}
</div>
<div class="flex flex-col gap-2">
<div class="text-surface-500-400-token font-semibold py-1">
{$_('settings.notifications.mentions.headline')}
</div>
<p class="text-sm leading-tight">
{@html $_('settings.notifications.mentions.intro', {
values: { blockElementOpen: '<strong>', blockElementClose: '</strong>', instance, username }
})}
</p>
<SlideToggle
class="flex gap-8"
name="mentions"
active="bg-primary-500"
size="sm"
bind:checked={isNotifiedOnMention}
>
{#if isNotifiedOnMention}
{$_('settings.notifications.mentions.labelYes')}
{:else}
{$_('settings.notifications.mentions.labelNo')}
{/if}
</SlideToggle>
</div>
<div class="flex flex-col gap-2">
<div class="text-surface-500-400-token font-semibold py-1">
{$_('settings.notifications.default.headline')}
</div>
<p class="text-sm text-surface-500-400-token leading-tight">
{$_('settings.notifications.default.intro')}
</p>
<div class="flex flex-col gap-2">
<label class="label flex items-center gap-2">
<input
type="radio"
class="me-2"
name="theme"
value="light"
checked={defaultNofication === 'all'}
/>
<Eye24 fill="currentColor" />
{$_('settings.notifications.default.notification.all')}
</label>
<label class="label flex items-center gap-2">
<input
type="radio"
class="me-2"
name="theme"
value="dark"
checked={defaultNofication === 'off'}
/>
<EyeClosed24 fill="currentColor" />
{$_('settings.notifications.default.notification.off')}
</label>
<label class="label flex items-center gap-2">
<input
type="radio"
class="me-2"
name="theme"
value="auto"
checked={defaultNofication === 'mentioned'}
/>
<Mention24 fill="currentColor" />
{$_('settings.notifications.default.notification.mentioned')}
</label>
</div>
</div>
</div>

View file

@ -206,7 +206,23 @@
} }
}, },
"notifications": { "notifications": {
"label": "" "default": {
"headline": "",
"intro": "",
"notification": {
"all": "",
"off": "",
"mentioned": ""
}
},
"headline": "",
"label": "",
"mentions": {
"headline": "",
"intro": "",
"labelNo": "",
"labelYes": ""
}
}, },
"profile": { "profile": {
"avatar": { "avatar": {

View file

@ -1,268 +1,284 @@
{ {
"overlay": { "overlay": {
"avatar": { "avatar": {
"about_anvil": "Über Anvil", "about_anvil": "Über Anvil",
"dark_mode": "Nachtmodus", "dark_mode": "Nachtmodus",
"profile": "Profil", "profile": "Profil",
"settings": "Einstellungen", "settings": "Einstellungen",
"sign_out": "Ausloggen" "sign_out": "Ausloggen"
} }
}, },
"page": { "page": {
"import_project": { "import_project": {
"form": { "form": {
"avatar": "Avatar", "avatar": "Avatar",
"components": "Komponenten", "components": "Komponenten",
"fields": { "fields": {
"avatar": { "avatar": {
"label": "Bild hochladen" "label": "Bild hochladen"
}, },
"description": { "description": {
"label": "Beschreibung", "label": "Beschreibung",
"placeholder": "Beschreibe das Projekt in unter 100 Zeichen..." "placeholder": "Beschreibe das Projekt in unter 100 Zeichen..."
}, },
"issues": { "issues": {
"label": "Issues" "label": "Issues"
}, },
"name": { "name": {
"error": "Dieses Feld ist erforderlich", "error": "Dieses Feld ist erforderlich",
"label": "Name", "label": "Name",
"placeholder": "Name des Projekts..." "placeholder": "Name des Projekts..."
}, },
"pr": { "pr": {
"label": "Pullrequest" "label": "Pullrequest"
}, },
"repository": { "repository": {
"hint": "optional: von Git-Repository importieren", "hint": "optional: von Git-Repository importieren",
"label": "Repository", "label": "Repository",
"placeholder": "URL des Git-Repositorys" "placeholder": "URL des Git-Repositorys"
} }
}, },
"submit": "Projekt erstellen" "submit": "Projekt erstellen"
}, },
"heading": "Projekt erstellen", "heading": "Projekt erstellen",
"intro": "Neues F2-Projekt zu Anvil hinzufügen." "intro": "Neues F2-Projekt zu Anvil hinzufügen."
}, },
"login": { "login": {
"form": { "form": {
"fields": { "fields": {
"account": { "account": {
"label": "Accountname" "label": "Accountname"
}, },
"passphrase": { "passphrase": {
"label": "Passwort", "label": "Passwort",
"placeholder": "Passwort" "placeholder": "Passwort"
}, },
"server": { "server": {
"label": "F2-Server" "label": "F2-Server"
} }
}, },
"reset": "Passwort zurücksetzen", "reset": "Passwort zurücksetzen",
"submit": "Einloggen", "submit": "Einloggen",
"validation": { "validation": {
"incorrect": "Accountname oder Kennwort sind falsch.", "incorrect": "Accountname oder Kennwort sind falsch.",
"missing": "Das Account-Feld ist verbindlich." "missing": "Das Account-Feld ist verbindlich."
} }
}, },
"heading": "Einloggen", "heading": "Einloggen",
"intro": "Die Einwahldaten ausfüllen um Anvil mit deinem F2-Account zu benutzen." "intro": "Die Einwahldaten ausfüllen um Anvil mit deinem F2-Account zu benutzen."
}, },
"profile": { "profile": {
"activities": { "activities": {
"block_or_report": "{blockElementOpen}blockieren{blockElementClose} oder {reportElementOpen}melden{reportElementClose}", "block_or_report": "{blockElementOpen}blockieren{blockElementClose} oder {reportElementOpen}melden{reportElementClose}",
"like": "Gefällt mir" "like": "Gefällt mir"
}, },
"heading": "Profil für", "heading": "Profil für",
"history": { "history": {
"activities": { "activities": {
"commits": { "commits": {
"actions": { "actions": {
"browse": "", "browse": "",
"copy": "Kopieren" "copy": "Kopieren"
}, },
"number": "", "number": "",
"relative_time": "" "relative_time": ""
}, },
"setup": { "setup": {
"description": "Das F2-Konto @{username}@{instance} wurde erfolgreich innerhalb von {created_with} erstellt", "description": "Das F2-Konto @{username}@{instance} wurde erfolgreich innerhalb von {created_with} erstellt",
"summary": "Kontoeinstellungen" "summary": "Kontoeinstellungen"
} }
}, },
"heading": "Aktivitäten" "heading": "Aktivitäten"
}, },
"menu": { "menu": {
"actions": { "actions": {
"fork": "", "fork": "",
"star": "", "star": "",
"watch": "" "watch": ""
}, },
"buttons": { "buttons": {
"avatar": "", "avatar": "",
"issues": "", "issues": "",
"notifications": "Benachrichtigungen", "notifications": "Benachrichtigungen",
"prs": "" "prs": ""
}, },
"details": { "details": {
"branches": "", "branches": "",
"commits": "Commits", "commits": "Commits",
"files": "Dateien", "files": "Dateien",
"issues": "", "issues": "",
"merge_requests": "", "merge_requests": "",
"moderation": "Moderation", "moderation": "Moderation",
"overview": "Übersicht", "overview": "Übersicht",
"people": "", "people": "",
"repository": "", "repository": "",
"roles": "Rollen", "roles": "Rollen",
"tags": "Schlagworte" "tags": "Schlagworte"
} }
}, },
"projects": { "projects": {
"actions": { "actions": {
"fork": "Fork", "fork": "Fork",
"star": "Favorisieren", "star": "Favorisieren",
"watch": "Beobachten" "watch": "Beobachten"
}, },
"add_or_import": "{addElementOpen}Ein Projekt hinzufügen{addElementClose} oder {importElementOpen}Ein Projekt importieren{importElementClose}.", "add_or_import": "{addElementOpen}Ein Projekt hinzufügen{addElementClose} oder {importElementOpen}Ein Projekt importieren{importElementClose}.",
"empty": "Bisher keine Projekte hinzugefügt.", "empty": "Bisher keine Projekte hinzugefügt.",
"heading": "Projekte" "heading": "Projekte"
}, },
"repositories": { "repositories": {
"heading": "" "heading": ""
} }
}, },
"projects": { "projects": {
"file_table": { "file_table": {
"updated": "Aktualisiert: {relativeTime}" "updated": "Aktualisiert: {relativeTime}"
}, },
"form": { "form": {
"fields": { "fields": {
"more_filters": { "more_filters": {
"submit": "Weitere Filter" "submit": "Weitere Filter"
}, },
"projects": { "projects": {
"submit": "Meine Projekte" "submit": "Meine Projekte"
}, },
"search": { "search": {
"placeholder": "Suchen oder filtern", "placeholder": "Suchen oder filtern",
"submit": "Absenden" "submit": "Absenden"
}, },
"starred": { "starred": {
"submit": "Favorisiert" "submit": "Favorisiert"
} }
} }
}, },
"nav": { "nav": {
"next": "Weiter", "next": "Weiter",
"previous": "Zurück" "previous": "Zurück"
}, },
"table": { "table": {
"heading": { "heading": {
"last_updated": "Letzte Aktualisierung", "last_updated": "Letzte Aktualisierung",
"name": "Name" "name": "Name"
} }
} }
}, },
"welcome": { "welcome": {
"create": "", "create": "",
"intro": "", "intro": "",
"headline": "Willkommen bei Anvil", "headline": "Willkommen bei Anvil",
"login": "", "login": "",
"logo": { "logo": {
"alt": "" "alt": ""
}, },
"reset": "" "reset": ""
} }
}, },
"settings": { "settings": {
"headline": "", "headline": "",
"account": { "account": {
"delete": "", "delete": "",
"f2": { "f2": {
"label": "", "label": "",
"placeholder": "" "placeholder": ""
}, },
"headline": "", "headline": "",
"label": "", "label": "",
"password": { "password": {
"label": "", "label": "",
"reset": "" "reset": ""
}, },
"verification": { "verification": {
"label": "" "label": ""
} }
}, },
"appearance": { "appearance": {
"headline": "", "headline": "",
"label": "", "label": "",
"tab_indent": { "tab_indent": {
"headline": "", "headline": "",
"intro": "" "intro": ""
}, },
"theme": { "theme": {
"auto": "", "auto": "",
"dark": "", "dark": "",
"headline": "", "headline": "",
"light": "" "light": ""
} }
}, },
"notifications": { "notifications": {
"label": "" "default": {
}, "headline": "",
"profile": { "intro": "",
"avatar": { "notification": {
"headline": "", "all": "",
"remove": "", "off": "",
"upload": "" "mentioned": ""
}, }
"bio": { },
"label": "", "headline": "",
"placeholder": "" "label": "",
}, "mentions": {
"extra": { "headline": "",
"content": { "intro": "",
"placeholder": "" "labelNo": "",
}, "labelYes": ""
"headline": "", }
"hint": "", },
"label": { "profile": {
"placeholder": "" "avatar": {
} "headline": "",
}, "remove": "",
"headline": "", "upload": ""
"label": "", },
"name": { "bio": {
"label": "", "label": "",
"placeholder": "" "placeholder": ""
}, },
"pronouns": { "extra": {
"label": "" "content": {
} "placeholder": ""
}, },
"ssh_gpg_keys": { "headline": "",
"gpg": { "hint": "",
"add": "", "label": {
"headline": "", "placeholder": ""
"key": { }
"placeholder": "" },
}, "headline": "",
"remove": "", "label": "",
"title": { "name": {
"placeholder": "" "label": "",
} "placeholder": ""
}, },
"headline": "", "pronouns": {
"label": "", "label": ""
"ssh": { }
"add": "", },
"headline": "", "ssh_gpg_keys": {
"key": { "gpg": {
"placeholder": "" "add": "",
}, "headline": "",
"remove": "", "key": {
"title": { "placeholder": ""
"placeholder": "" },
} "remove": "",
} "title": {
} "placeholder": ""
} }
},
"headline": "",
"label": "",
"ssh": {
"add": "",
"headline": "",
"key": {
"placeholder": ""
},
"remove": "",
"title": {
"placeholder": ""
}
}
}
}
} }

View file

@ -206,7 +206,23 @@
} }
}, },
"notifications": { "notifications": {
"label": "Notifications" "default": {
"headline": "Default notification",
"intro": "The default notification setting for starred projects",
"notification": {
"all": "All activity",
"off": "Turn off",
"mentioned": "Mentions only"
}
},
"headline": "Notifications",
"label": "Notifications",
"mentions": {
"headline": "Mentions",
"intro": "Receive notifications when {blockElementOpen}@{username}@{instance}{blockElementClose} is mentioned.",
"labelNo": "Off",
"labelYes": "On"
}
}, },
"profile": { "profile": {
"avatar": { "avatar": {

View file

@ -206,7 +206,23 @@
} }
}, },
"notifications": { "notifications": {
"label": "" "default": {
"headline": "",
"intro": "",
"notification": {
"all": "",
"off": "",
"mentioned": ""
}
},
"headline": "",
"label": "",
"mentions": {
"headline": "",
"intro": "",
"labelNo": "",
"labelYes": ""
}
}, },
"profile": { "profile": {
"avatar": { "avatar": {

View file

@ -0,0 +1,26 @@
/* Stories for SettingsNotifications 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 <http://www.gnu.org/licenses/>.
*/
import type { Meta, StoryObj } from '@storybook/svelte';
import SettingsNotifications from '$lib/components/molecules/SettingsNotifications.svelte';
const meta = {
title: 'Molecules/SettingsNotifications',
component: SettingsNotifications,
tags: ['autodocs']
} satisfies Meta<SettingsNotifications>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Plain: Story = {};

View file

@ -0,0 +1,86 @@
/* Component test for SettingsNotifications 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 <http://www.gnu.org/licenses/>.
*/
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/svelte';
import { init, locale, register } from 'svelte-i18n';
import SettingsNotifications from '../../../src/lib/components/molecules/SettingsNotifications.svelte';
import enMessages from '../../../src/lib/i18n/locales/en.json';
describe('SettingsNotifications.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(SettingsNotifications);
// Assert
expect(container).toBeTruthy();
});
it('should have a slide toggle', () => {
// Arrange
// Nothing to prepare
// Act
render(SettingsNotifications);
// Assert
expect(
screen.getByLabelText(enMessages.settings.notifications.mentions.labelNo)
).toBeInTheDocument();
});
describe('when slide is toggled', () => {
it('should update the label', async () => {
// Arrange
// Nothing to prepare
// Act
render(SettingsNotifications);
const slideToggle = screen.getByLabelText(enMessages.settings.notifications.mentions.labelNo);
await slideToggle.click();
// Assert
expect(
screen.getByLabelText(enMessages.settings.notifications.mentions.labelYes)
).toBeInTheDocument();
});
});
it('should have a default notification radio group', () => {
// Arrange
// Nothing to prepare
// Act
render(SettingsNotifications);
// Assert
expect(
screen.getByLabelText(enMessages.settings.notifications.default.notification.all)
).toBeInTheDocument();
expect(
screen.getByLabelText(enMessages.settings.notifications.default.notification.off)
).toBeInTheDocument();
expect(
screen.getByLabelText(enMessages.settings.notifications.default.notification.mentioned)
).toBeInTheDocument();
});
});