mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 01:56:27 +08:00
feat(core): add actions to editor settings (#8030)
# What Changed? - Add actions of following edgeless-elements editor settings: - note - connector - edgeless text - pen
This commit is contained in:
+13
-34
@@ -1,7 +1,6 @@
|
||||
import { Framework, GlobalState, MemoryMemento } from '@toeverything/infra';
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
import { unflattenObject } from '../../../utils/unflatten-object';
|
||||
import { EditorSetting } from '../entities/editor-setting';
|
||||
import { GlobalStateEditorSettingProvider } from '../impls/global-state';
|
||||
import { EditorSettingProvider } from '../provider/editor-setting-provider';
|
||||
@@ -23,27 +22,23 @@ test('editor setting service', () => {
|
||||
const editorSettingService = provider.get(EditorSettingService);
|
||||
|
||||
// default value
|
||||
expect(editorSettingService.editorSetting.settings$.value).toMatchObject({
|
||||
fontFamily: 'Sans',
|
||||
'connector.stroke': '#000000',
|
||||
});
|
||||
expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Sans');
|
||||
|
||||
// set plain object
|
||||
editorSettingService.editorSetting.set('fontFamily', 'Serif');
|
||||
expect(editorSettingService.editorSetting.settings$.value).toMatchObject({
|
||||
fontFamily: 'Serif',
|
||||
});
|
||||
expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Serif');
|
||||
|
||||
// nested object, should be serialized
|
||||
editorSettingService.editorSetting.set('connector.stroke', {
|
||||
// set nested object
|
||||
editorSettingService.editorSetting.set('connector', {
|
||||
stroke: {
|
||||
dark: '#000000',
|
||||
light: '#ffffff',
|
||||
},
|
||||
});
|
||||
expect(editorSettingService.editorSetting.get('connector').stroke).toEqual({
|
||||
dark: '#000000',
|
||||
light: '#ffffff',
|
||||
});
|
||||
expect(
|
||||
(
|
||||
editorSettingService.editorSetting
|
||||
.provider as GlobalStateEditorSettingProvider
|
||||
).get('connector.stroke')
|
||||
).toBe('{"dark":"#000000","light":"#ffffff"}');
|
||||
|
||||
// invalid font family
|
||||
editorSettingService.editorSetting.provider.set(
|
||||
@@ -51,22 +46,6 @@ test('editor setting service', () => {
|
||||
JSON.stringify('abc')
|
||||
);
|
||||
|
||||
// should fallback to default value
|
||||
expect(editorSettingService.editorSetting.settings$.value['fontFamily']).toBe(
|
||||
'Sans'
|
||||
);
|
||||
|
||||
// expend demo
|
||||
const expended = unflattenObject(
|
||||
editorSettingService.editorSetting.settings$.value
|
||||
);
|
||||
expect(expended).toMatchObject({
|
||||
fontFamily: 'Sans',
|
||||
connector: {
|
||||
stroke: {
|
||||
dark: '#000000',
|
||||
light: '#ffffff',
|
||||
},
|
||||
},
|
||||
});
|
||||
// fallback to default value
|
||||
expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Sans');
|
||||
});
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import {
|
||||
createSignalFromObservable,
|
||||
type Signal,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import type { DeepPartial } from '@blocksuite/global/utils';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
import { isObject, merge } from 'lodash-es';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import type { EditorSettingProvider } from '../provider/editor-setting-provider';
|
||||
import { EditorSettingSchema } from '../schema';
|
||||
@@ -7,17 +14,30 @@ import { EditorSettingSchema } from '../schema';
|
||||
export class EditorSetting extends Entity {
|
||||
constructor(public readonly provider: EditorSettingProvider) {
|
||||
super();
|
||||
|
||||
const { signal, cleanup } = createSignalFromObservable<
|
||||
Partial<EditorSettingSchema>
|
||||
>(this.settings$, {});
|
||||
this.settingSignal = signal;
|
||||
this.disposables.push(cleanup);
|
||||
}
|
||||
|
||||
settings$ = LiveData.from<EditorSettingSchema>(this.watchAll(), null as any);
|
||||
|
||||
settingSignal: Signal<Partial<EditorSettingSchema>>;
|
||||
|
||||
get<K extends keyof EditorSettingSchema>(key: K) {
|
||||
return this.settings$.value[key];
|
||||
}
|
||||
|
||||
set<K extends keyof EditorSettingSchema>(
|
||||
key: K,
|
||||
value: EditorSettingSchema[K]
|
||||
value: DeepPartial<EditorSettingSchema[K]>
|
||||
) {
|
||||
const schema = EditorSettingSchema.shape[key];
|
||||
|
||||
this.provider.set(key, JSON.stringify(schema.parse(value)));
|
||||
const curValue = this.get(key);
|
||||
const nextValue = isObject(curValue) ? merge(curValue, value) : value;
|
||||
this.provider.set(key, JSON.stringify(schema.parse(nextValue)));
|
||||
}
|
||||
|
||||
private watchAll(): Observable<EditorSettingSchema> {
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
import {
|
||||
BrushSchema,
|
||||
ConnectorSchema,
|
||||
EdgelessTextSchema,
|
||||
NoteSchema,
|
||||
ShapeSchema,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { z } from 'zod';
|
||||
|
||||
// TODO import from BlockSuite
|
||||
export const BSEditorSettingSchema = z.object({
|
||||
connector: ConnectorSchema,
|
||||
brush: BrushSchema,
|
||||
shape: ShapeSchema,
|
||||
'affine:edgeless-text': EdgelessTextSchema,
|
||||
'affine:note': NoteSchema,
|
||||
});
|
||||
|
||||
export type FontFamily = 'Sans' | 'Serif' | 'Mono' | 'Custom';
|
||||
|
||||
export const fontStyleOptions = [
|
||||
@@ -12,20 +28,6 @@ export const fontStyleOptions = [
|
||||
value: string;
|
||||
}[];
|
||||
|
||||
const BSEditorSettingSchema = z.object({
|
||||
// TODO: import from bs
|
||||
connector: z.object({
|
||||
stroke: z
|
||||
.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
dark: z.string(),
|
||||
light: z.string(),
|
||||
}),
|
||||
])
|
||||
.default('#000000'),
|
||||
}),
|
||||
});
|
||||
const AffineEditorSettingSchema = z.object({
|
||||
fontFamily: z.enum(['Sans', 'Serif', 'Mono', 'Custom']).default('Sans'),
|
||||
customFontFamily: z.string().default(''),
|
||||
@@ -36,44 +38,8 @@ const AffineEditorSettingSchema = z.object({
|
||||
displayBiDirectionalLink: z.boolean().default(true),
|
||||
});
|
||||
|
||||
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
|
||||
x: infer I
|
||||
) => void
|
||||
? I
|
||||
: never;
|
||||
|
||||
type FlattenZodObject<O, Prefix extends string = ''> =
|
||||
O extends z.ZodObject<infer T>
|
||||
? {
|
||||
[A in keyof T]: T[A] extends z.ZodObject<any>
|
||||
? A extends string
|
||||
? FlattenZodObject<T[A], `${Prefix}${A}.`>
|
||||
: never
|
||||
: A extends string
|
||||
? { [key in `${Prefix}${A}`]: T[A] }
|
||||
: never;
|
||||
}[keyof T]
|
||||
: never;
|
||||
|
||||
function flattenZodObject<S extends z.ZodObject<any>>(
|
||||
schema: S,
|
||||
target: z.ZodObject<any> = z.object({}),
|
||||
prefix = ''
|
||||
) {
|
||||
for (const key in schema.shape) {
|
||||
const value = schema.shape[key];
|
||||
if (value instanceof z.ZodObject) {
|
||||
flattenZodObject(value, target, prefix + key + '.');
|
||||
} else {
|
||||
target.shape[prefix + key] = value;
|
||||
}
|
||||
}
|
||||
type Result = UnionToIntersection<FlattenZodObject<S>>;
|
||||
return target as Result extends z.ZodRawShape ? z.ZodObject<Result> : never;
|
||||
}
|
||||
|
||||
export const EditorSettingSchema = flattenZodObject(
|
||||
BSEditorSettingSchema.merge(AffineEditorSettingSchema)
|
||||
export const EditorSettingSchema = BSEditorSettingSchema.merge(
|
||||
AffineEditorSettingSchema
|
||||
);
|
||||
|
||||
export type EditorSettingSchema = z.infer<typeof EditorSettingSchema>;
|
||||
|
||||
@@ -19,7 +19,6 @@ import { UserQuotaChanged } from '../../cloud/services/user-quota';
|
||||
export class TelemetryService extends Service {
|
||||
private prevQuota: NonNullable<QuotaQuery['currentUser']>['quota'] | null =
|
||||
null;
|
||||
private readonly disposables: (() => void)[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly auth: AuthService,
|
||||
|
||||
Reference in New Issue
Block a user