refactor: ♻️ Rewrite settings backend

This commit is contained in:
Jesse Wierzbinski 2024-07-21 18:53:16 +02:00
parent 80b1fc87f7
commit 78e4fa0f04
No known key found for this signature in database
6 changed files with 133 additions and 126 deletions

View file

@ -36,14 +36,14 @@
<script lang="ts" setup>
import { Switch } from "@ark-ui/vue";
import { type Setting, type SettingIds, SettingType } from "~/settings";
import { type SettingIds, SettingType } from "~/settings";
const props = defineProps<{
setting: Setting;
id: SettingIds;
}>();
const checked = ref(!!props.setting.value);
const setting = useSetting(props.setting.id as SettingIds);
const setting = useSetting(props.id);
const checked = ref(setting.value.value as boolean);
watch(checked, (c) => {
setting.value.value = c;

View file

@ -1,6 +1,7 @@
<template>
<HoverCard.Root :positioning="{
placement: 'bottom',
strategy: 'fixed',
}" v-if="isEnabled.value">
<HoverCard.Trigger :as-child="true">
<slot />

View file

@ -1,6 +1,6 @@
import type { Account, Emoji } from "@lysand-org/client/types";
import { renderToString } from "vue/server-renderer";
import { SettingIds, type Settings, getSettingById } from "~/settings";
import { SettingIds, type Settings } from "~/settings";
import MentionComponent from "../components/social-elements/notes/mention.vue";
/**
@ -13,7 +13,7 @@ export const useParsedContent = (
content: MaybeRef<string>,
emojis: MaybeRef<Emoji[]>,
mentions: MaybeRef<Account[]> = ref([]),
settings: MaybeRef<Settings> = ref([]),
settings: MaybeRef<Settings | null> = ref(null),
): Ref<string | null> => {
const result = ref(null as string | null);
@ -27,10 +27,8 @@ export const useParsedContent = (
const contentHtml = document.createElement("div");
contentHtml.innerHTML = toValue(content);
const shouldRenderEmoji = getSettingById(
toValue(settings),
SettingIds.CustomEmojis,
)?.value;
const shouldRenderEmoji =
toValue(settings)?.[SettingIds.CustomEmojis].value;
// Replace emoji shortcodes with images
if (shouldRenderEmoji) {

View file

@ -3,7 +3,7 @@ import {
type Setting,
type SettingIds,
type Settings,
parseFromJson,
mergeSettings,
settings as settingsJson,
} from "~/settings";
@ -13,18 +13,17 @@ const useSettings = () => {
read(raw) {
const json = StorageSerializers.object.read(raw);
return parseFromJson(json);
return mergeSettings(json);
},
write(value) {
// key/value, with key being id and value being the value
const json = value.reduce(
(acc, setting) => {
acc[setting.id] = setting.value;
return acc;
},
{} as Record<string, unknown>,
const json = Object.fromEntries(
Object.entries(value).map(([key, value]) => [
key,
value.value,
]),
);
// flatMap object values to .value
return StorageSerializers.object.write(json);
},
},
@ -33,24 +32,21 @@ const useSettings = () => {
export const settings = useSettings();
export const useSetting = <T extends Setting = Setting>(id: SettingIds) => {
const setting: Ref<T> = ref<T>(
settings.value.find((s) => s.id === id) as T,
) as unknown as Ref<T>;
export const useSetting = <Id extends SettingIds>(
id: Id,
): Ref<Settings[Id]> => {
const setting = ref(settings.value[id]) as Ref<Settings[Id]>;
watch(settings, (newSettings) => {
setting.value = newSettings.find((s) => s.id === id) as T;
setting.value = newSettings[id];
});
watch(setting, (newSetting) => {
settings.value = settings.value.map((s) =>
s.id === id ? newSetting : s,
) as Settings;
settings.value = {
...settings.value,
[id]: newSetting,
};
});
return setting;
};
export const getSetting = <T extends Setting = Setting>(id: SettingIds) => {
return settingsJson.find((s) => s.id === id) as T;
};

View file

@ -1,22 +1,19 @@
<template>
<SettingsSidebar>
<template #behaviour>
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
settings,
<Renderer :id="id" v-for="id of (Object.keys(getSettingsForPage(
SettingPages.Behaviour,
)" :key="setting.id" />
)) as SettingIds[])" :key="id" />
</template>
<template #appearance>
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
settings,
<Renderer :id="id" v-for="id of (Object.keys(getSettingsForPage(
SettingPages.Appearance,
)" :key="setting.id" />
)) as SettingIds[])" :key="id" />
</template>
<template #advanced>
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
settings,
<Renderer :id="id" v-for="id of (Object.keys(getSettingsForPage(
SettingPages.Advanced,
)" :key="setting.id" />
)) as SettingIds[])" :key="id" />
</template>
<template #account>
<ProfileEditor />
@ -28,7 +25,7 @@
import ProfileEditor from "~/components/settings/profile-editor.vue";
import Renderer from "~/components/settings/renderer.vue";
import SettingsSidebar from "~/components/sidebars/settings-sidebar.vue";
import { SettingPages, getSettingsForPath } from "~/settings";
import { SettingIds, SettingPages, getSettingsForPage } from "~/settings";
definePageMeta({
layout: "app",

View file

@ -7,27 +7,51 @@ export enum SettingType {
Code = "code",
}
export type Setting<T = SettingType> = {
id: string;
export type Setting = {
title: string;
description: string;
notImplemented?: boolean;
type: T;
value: T extends SettingType.String | SettingType.Code
? string
: T extends SettingType.Boolean
? boolean
: T extends SettingType.Float | SettingType.Integer
? number
: T extends SettingType.Enum
? string
: never;
min?: T extends SettingType.Float | SettingType.Integer ? number : never;
max?: T extends SettingType.Float | SettingType.Integer ? number : never;
step?: T extends SettingType.Float | SettingType.Integer ? number : never;
options?: T extends SettingType.Enum ? string[] : never;
language?: T extends SettingType.Code ? string : never;
path: SettingPages;
type: SettingType;
value: unknown;
page: SettingPages;
};
export type StringSetting = Setting & {
type: SettingType.String;
value: string;
};
export type BooleanSetting = Setting & {
type: SettingType.Boolean;
value: boolean;
};
export type EnumSetting = Setting & {
type: SettingType.Enum;
value: string;
options: string[];
};
export type FloatSetting = Setting & {
type: SettingType.Float;
value: number;
min: number;
max: number;
step: number;
};
export type IntegerSetting = Setting & {
type: SettingType.Integer;
value: number;
min: number;
max: number;
step: number;
};
export type CodeSetting = Setting & {
type: SettingType.Code;
value: string;
language: string;
};
export enum SettingPages {
@ -37,27 +61,6 @@ export enum SettingPages {
Appearance = "appearance",
}
export const getSettingsForPath = (
settingsToFilterIn: Settings,
path: SettingPages,
) => settingsToFilterIn.filter((setting) => setting.path === path);
export const getSettingById = (settingsToFilterIn: Settings, id: SettingIds) =>
settingsToFilterIn.find((setting) => setting.id === id);
export const parseFromJson = (json: Record<string, unknown>) => {
const finalSettings = structuredClone(settings);
// Override the default values with the values from the JSON except for the user value
for (const setting of finalSettings) {
if (setting.id in json) {
setting.value = json[setting.id] as (typeof setting)["value"];
}
}
return finalSettings;
};
export enum SettingIds {
Mfm = "mfm",
CustomCSS = "custom-css",
@ -72,103 +75,115 @@ export enum SettingIds {
ConfirmFavourite = "confirm-favourite",
}
export const settings = [
{
id: SettingIds.Mfm,
export const settings: Record<SettingIds, Setting> = {
[SettingIds.Mfm]: {
title: "Render MFM",
description: "Render Misskey-Flavoured Markdown",
type: SettingType.Boolean,
value: false,
path: SettingPages.Behaviour,
page: SettingPages.Behaviour,
notImplemented: true,
} as Setting<SettingType.Boolean>,
{
id: SettingIds.CustomCSS,
} as BooleanSetting,
[SettingIds.CustomCSS]: {
title: "Custom CSS",
description: "Custom CSS for the UI",
type: SettingType.Code,
value: "",
language: "css",
path: SettingPages.Appearance,
} as Setting<SettingType.Code>,
{
id: SettingIds.Theme,
page: SettingPages.Appearance,
} as CodeSetting,
[SettingIds.Theme]: {
title: "Theme",
description: "UI theme",
type: SettingType.Enum,
value: "dark",
options: ["light", "dark"],
path: SettingPages.Appearance,
} as Setting<SettingType.Enum>,
{
id: SettingIds.CustomEmojis,
page: SettingPages.Appearance,
} as EnumSetting,
[SettingIds.CustomEmojis]: {
title: "Render Custom Emojis",
description: "Render custom emojis",
type: SettingType.Boolean,
value: true,
path: SettingPages.Behaviour,
} as Setting<SettingType.Boolean>,
{
id: SettingIds.ShowContentWarning,
page: SettingPages.Behaviour,
} as BooleanSetting,
[SettingIds.ShowContentWarning]: {
title: "Show Content Warning",
description: "Show content warnings on notes marked sensitive/spoiler",
type: SettingType.Boolean,
value: true,
path: SettingPages.Behaviour,
} as Setting<SettingType.Boolean>,
{
id: SettingIds.PopupAvatarHover,
page: SettingPages.Behaviour,
} as BooleanSetting,
[SettingIds.PopupAvatarHover]: {
title: "Popup Profile Hover",
description: "Show profile popup when hovering over a user's avatar",
type: SettingType.Boolean,
value: true,
path: SettingPages.Behaviour,
} as Setting<SettingType.Boolean>,
{
id: SettingIds.InfiniteScroll,
page: SettingPages.Behaviour,
} as BooleanSetting,
[SettingIds.InfiniteScroll]: {
title: "Infinite Scroll",
description:
"Automatically load more notes when reaching the bottom of the page",
type: SettingType.Boolean,
value: true,
path: SettingPages.Behaviour,
} as Setting<SettingType.Boolean>,
{
id: SettingIds.ConfirmDelete,
page: SettingPages.Behaviour,
} as BooleanSetting,
[SettingIds.ConfirmDelete]: {
title: "Confirm Delete",
description: "Confirm before deleting a note",
type: SettingType.Boolean,
value: false,
path: SettingPages.Behaviour,
page: SettingPages.Behaviour,
notImplemented: true,
} as Setting<SettingType.Boolean>,
{
id: SettingIds.ConfirmFollow,
} as BooleanSetting,
[SettingIds.ConfirmFollow]: {
title: "Confirm Follow",
description: "Confirm before following/unfollowing a user",
type: SettingType.Boolean,
value: false,
path: SettingPages.Behaviour,
page: SettingPages.Behaviour,
notImplemented: true,
} as Setting<SettingType.Boolean>,
{
id: SettingIds.ConfirmReblog,
} as BooleanSetting,
[SettingIds.ConfirmReblog]: {
title: "Confirm Reblog",
description: "Confirm before reblogging a note",
type: SettingType.Boolean,
value: false,
path: SettingPages.Behaviour,
page: SettingPages.Behaviour,
notImplemented: true,
} as Setting<SettingType.Boolean>,
{
id: SettingIds.ConfirmFavourite,
} as BooleanSetting,
[SettingIds.ConfirmFavourite]: {
title: "Confirm Favourite",
description: "Confirm before favouriting a note",
type: SettingType.Boolean,
value: false,
path: SettingPages.Behaviour,
page: SettingPages.Behaviour,
notImplemented: true,
} as Setting<SettingType.Boolean>,
];
} as BooleanSetting,
};
export const getSettingsForPage = (page: SettingPages): Partial<Settings> => {
return Object.fromEntries(
Object.entries(settings).filter(([, setting]) => setting.page === page),
);
};
/**
* Merge a partly defined Settings object with the default settings
* Useful when there is an update to the settings in the backend
*/
export const mergeSettings = (
settingsToMerge: Record<SettingIds, Setting["value"]>,
): Settings => {
const finalSettings = structuredClone(settings);
for (const [key, value] of Object.entries(settingsToMerge)) {
if (key in settings) {
finalSettings[key as SettingIds].value = value;
}
}
return finalSettings;
};
export type Settings = typeof settings;