Migrate pl-fe schemas to Valibot

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-10-13 22:24:06 +02:00
parent 95f38374c1
commit 7abeee3d1c
5 changed files with 92 additions and 74 deletions

View file

@ -1,85 +1,89 @@
import { z } from 'zod';
import * as v from 'valibot';
import { locales } from 'pl-fe/messages';
import { coerceObject } from '../utils';
const skinToneSchema = z.union([
z.literal(1), z.literal(2), z.literal(3), z.literal(4), z.literal(5), z.literal(6),
]);
const skinToneSchema = v.picklist([1, 2, 3, 4, 5, 6]);
const settingsSchema = z.object({
onboarded: z.boolean().catch(false),
skinTone: skinToneSchema.catch(1),
reduceMotion: z.boolean().catch(false),
underlineLinks: z.boolean().catch(false),
autoPlayGif: z.boolean().catch(true),
displayMedia: z.enum(['default', 'hide_all', 'show_all']).catch('default'),
displaySpoilers: z.boolean().catch(false),
unfollowModal: z.boolean().catch(true),
boostModal: z.boolean().catch(false),
deleteModal: z.boolean().catch(true),
missingDescriptionModal: z.boolean().catch(true),
defaultPrivacy: z.enum(['public', 'unlisted', 'private', 'direct']).catch('public'),
defaultContentType: z.enum(['text/plain', 'text/markdown']).catch('text/plain'),
themeMode: z.enum(['system', 'light', 'dark', 'black']).catch('system'),
locale: z.string().catch(navigator.language).pipe(z.enum(locales)).catch('en'),
showExplanationBox: z.boolean().catch(true),
explanationBox: z.boolean().catch(true),
autoloadTimelines: z.boolean().catch(true),
autoloadMore: z.boolean().catch(true),
preserveSpoilers: z.boolean().catch(false),
autoTranslate: z.boolean().catch(false),
knownLanguages: z.array(z.string()).catch([]),
const settingsSchema = v.object({
onboarded: v.fallback(v.boolean(), false),
skinTone: v.fallback(skinToneSchema, 1),
reduceMotion: v.fallback(v.boolean(), false),
underlineLinks: v.fallback(v.boolean(), false),
autoPlayGif: v.fallback(v.boolean(), true),
displayMedia: v.fallback(v.picklist(['default', 'hide_all', 'show_all']), 'default'),
displaySpoilers: v.fallback(v.boolean(), false),
unfollowModal: v.fallback(v.boolean(), true),
boostModal: v.fallback(v.boolean(), false),
deleteModal: v.fallback(v.boolean(), true),
missingDescriptionModal: v.fallback(v.boolean(), true),
defaultPrivacy: v.fallback(v.picklist(['public', 'unlisted', 'private', 'direct']), 'public'),
defaultContentType: v.fallback(v.picklist(['text/plain', 'text/markdown']), 'text/plain'),
themeMode: v.fallback(v.picklist(['system', 'light', 'dark', 'black']), 'system'),
locale: v.fallback(
v.pipe(
v.fallback(v.string(), navigator.language),
v.picklist(locales),
),
'en',
),
showExplanationBox: v.fallback(v.boolean(), true),
explanationBox: v.fallback(v.boolean(), true),
autoloadTimelines: v.fallback(v.boolean(), true),
autoloadMore: v.fallback(v.boolean(), true),
preserveSpoilers: v.fallback(v.boolean(), false),
autoTranslate: v.fallback(v.boolean(), false),
knownLanguages: v.fallback(v.array(v.string()), []),
systemFont: z.boolean().catch(false),
demetricator: z.boolean().catch(false),
systemFont: v.fallback(v.boolean(), false),
demetricator: v.fallback(v.boolean(), false),
isDeveloper: z.boolean().catch(false),
isDeveloper: v.fallback(v.boolean(), false),
chats: coerceObject({
mainWindow: z.enum(['minimized', 'open']).catch('minimized'),
sound: z.boolean().catch(true),
mainWindow: v.fallback(v.picklist(['minimized', 'open']), 'minimized'),
sound: v.fallback(v.boolean(), true),
}),
timelines: z.record(coerceObject({
timelines: v.fallback(v.record(v.string(), coerceObject({
shows: coerceObject({
reblog: z.boolean().catch(true),
reply: z.boolean().catch(true),
direct: z.boolean().catch(false),
reblog: v.fallback(v.boolean(), true),
reply: v.fallback(v.boolean(), true),
direct: v.fallback(v.boolean(), false),
}),
other: coerceObject({
onlyMedia: z.boolean().catch(false),
onlyMedia: v.fallback(v.boolean(), false),
}),
})).catch({}),
})), {}),
account_timeline: coerceObject({
shows: coerceObject({
pinned: z.boolean().catch(true),
pinned: v.fallback(v.boolean(), true),
}),
}),
remote_timeline: coerceObject({
pinnedHosts: z.string().array().catch([]),
pinnedHosts: v.fallback(v.array(v.string()), []),
}),
notifications: coerceObject({
quickFilter: coerceObject({
active: z.string().catch('all'),
advanced: z.boolean().catch(false),
show: z.boolean().catch(true),
active: v.fallback(v.string(), 'all'),
advanced: v.fallback(v.boolean(), false),
show: v.fallback(v.boolean(), true),
}),
sounds: z.record(z.boolean()).catch({}),
sounds: v.fallback(v.record(v.string(), v.boolean()), {}),
}),
frequentlyUsedEmojis: z.record(z.number()).catch({}),
frequentlyUsedLanguages: z.record(z.number()).catch({}),
frequentlyUsedEmojis: v.fallback(v.record(v.string(), v.number()), {}),
frequentlyUsedLanguages: v.fallback(v.record(v.string(), v.number()), {}),
saved: z.boolean().catch(true),
saved: v.fallback(v.boolean(), true),
demo: z.boolean().catch(false),
demo: v.fallback(v.boolean(), false),
});
type Settings = z.infer<typeof settingsSchema>;
type Settings = v.InferOutput<typeof settingsSchema>;
export { settingsSchema, type Settings };

View file

@ -1,20 +1,26 @@
import { z } from 'zod';
import * as v from 'valibot';
import { coerceObject } from './utils';
const mrfSimpleSchema = coerceObject({
accept: z.string().array().catch([]),
avatar_removal: z.string().array().catch([]),
banner_removal: z.string().array().catch([]),
federated_timeline_removal: z.string().array().catch([]),
followers_only: z.string().array().catch([]),
media_nsfw: z.string().array().catch([]),
media_removal: z.string().array().catch([]),
reject: z.string().array().catch([]),
reject_deletes: z.string().array().catch([]),
report_removal: z.string().array().catch([]),
});
const mrfSimpleSchema = coerceObject(v.entriesFromList(
[
'accept',
'avatar_removal',
'banner_removal',
'federated_timeline_removal',
'followers_only',
'media_nsfw',
'media_removal',
'reject',
'reject_deletes',
'report_removal',
],
v.fallback(v.array(v.string()), []),
));
type MRFSimple = z.infer<typeof mrfSimpleSchema>;
(window as any).v = v;
(window as any).mrfSimpleSchema = mrfSimpleSchema;
type MRFSimple = v.InferOutput<typeof mrfSimpleSchema>;
export { mrfSimpleSchema, type MRFSimple };

View file

@ -1,3 +1,4 @@
import * as v from 'valibot';
import z from 'zod';
import type { CustomEmoji } from 'pl-api';
@ -18,8 +19,13 @@ const makeCustomEmojiMap = (customEmojis: CustomEmoji[]) =>
result[`:${emoji.shortcode}:`] = emoji;
return result;
}, {});
/** zod schema to force the value into an object, if it isn't already. */
const coerceObject = <T extends z.ZodRawShape>(shape: T) =>
z.object({}).passthrough().catch({}).pipe(z.object(shape));
const coerceObject = <T extends v.ObjectEntries>(shape: T) =>
v.pipe(
v.any(),
v.transform((input) => typeof input === 'object' ? input : {}),
v.object(shape),
);
export { filteredArray, makeCustomEmojiMap, coerceObject };

View file

@ -1,4 +1,5 @@
import { produce } from 'immer';
import * as v from 'valibot';
import { create } from 'zustand';
import { settingsSchema, type Settings } from 'pl-fe/schemas/pl-fe/settings';
@ -6,7 +7,7 @@ import { settingsSchema, type Settings } from 'pl-fe/schemas/pl-fe/settings';
import type { Emoji } from 'pl-fe/features/emoji';
import type { APIEntity } from 'pl-fe/types/entities';
const settingsSchemaPartial = settingsSchema.partial();
const settingsSchemaPartial = v.partial(settingsSchema);
type State = {
defaultSettings: Settings;
@ -35,22 +36,22 @@ const changeSetting = (object: APIEntity, path: string[], value: any) => {
const mergeSettings = (state: State) => state.settings = { ...state.defaultSettings, ...state.userSettings };
const useSettingsStore = create<State>((set) => ({
defaultSettings: settingsSchema.parse({}),
defaultSettings: v.parse(settingsSchema, {}),
userSettings: {},
settings: settingsSchema.parse({}),
settings: v.parse(settingsSchema, {}),
loadDefaultSettings: (settings: APIEntity) => set(produce((state: State) => {
if (typeof settings !== 'object') return;
state.defaultSettings = settingsSchema.parse(settings);
state.defaultSettings = v.parse(settingsSchema, settings);
mergeSettings(state);
})),
loadUserSettings: (settings?: APIEntity) => set(produce((state: State) => {
if (typeof settings !== 'object') return;
state.userSettings = settingsSchemaPartial.parse(settings);
state.userSettings = v.parse(settingsSchemaPartial, settings);
mergeSettings(state);
})),

View file

@ -4,8 +4,9 @@ import {
Set as ImmutableSet,
} from 'immutable';
import trimStart from 'lodash/trimStart';
import * as v from 'valibot';
import { type MRFSimple, mrfSimpleSchema } from 'pl-fe/schemas/pleroma';
import { mrfSimpleSchema } from 'pl-fe/schemas/pleroma';
type Config = ImmutableMap<string, any>;
type Policy = Record<string, any>;
@ -18,7 +19,7 @@ const find = (
config.isSuperset(ImmutableMap({ group, key })),
);
const toSimplePolicy = (configs: ImmutableList<Config>): MRFSimple => {
const toSimplePolicy = (configs: ImmutableList<Config>) => {
const config = find(configs, ':pleroma', ':mrf_simple');
const reducer = (acc: ImmutableMap<string, any>, curr: ImmutableMap<string, any>) => {
@ -30,9 +31,9 @@ const toSimplePolicy = (configs: ImmutableList<Config>): MRFSimple => {
if (config?.get) {
const value = config.get('value', ImmutableList());
const result = value.reduce(reducer, ImmutableMap());
return mrfSimpleSchema.parse(result.toJS());
return v.parse(mrfSimpleSchema, result.toJS());
} else {
return mrfSimpleSchema.parse({});
return v.parse(mrfSimpleSchema, {});
}
};