mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-24 05:07:06 +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:
@@ -4,8 +4,11 @@ import type { FrameworkProvider } from '../provider';
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
export class Component<Props = {}> {
|
export class Component<Props = {}> {
|
||||||
readonly framework: FrameworkProvider;
|
readonly framework: FrameworkProvider;
|
||||||
|
|
||||||
readonly props: Props;
|
readonly props: Props;
|
||||||
|
|
||||||
|
protected readonly disposables: (() => void)[] = [];
|
||||||
|
|
||||||
get eventBus() {
|
get eventBus() {
|
||||||
return this.framework.eventBus;
|
return this.framework.eventBus;
|
||||||
}
|
}
|
||||||
@@ -19,7 +22,9 @@ export class Component<Props = {}> {
|
|||||||
CONSTRUCTOR_CONTEXT.current = {};
|
CONSTRUCTOR_CONTEXT.current = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {}
|
dispose() {
|
||||||
|
this.disposables.forEach(dispose => dispose());
|
||||||
|
}
|
||||||
|
|
||||||
[Symbol.dispose]() {
|
[Symbol.dispose]() {
|
||||||
this.dispose();
|
this.dispose();
|
||||||
|
|||||||
-183
@@ -1,183 +0,0 @@
|
|||||||
import {
|
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
MenuTrigger,
|
|
||||||
RadioGroup,
|
|
||||||
type RadioItem,
|
|
||||||
Slider,
|
|
||||||
} from '@affine/component';
|
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
|
||||||
import { useI18n } from '@affine/i18n';
|
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
|
|
||||||
import { menuTrigger, settingWrapper } from '../style.css';
|
|
||||||
import { EdgelessSnapshot } from './snapshot';
|
|
||||||
|
|
||||||
export const ConnecterSettings = () => {
|
|
||||||
const t = useI18n();
|
|
||||||
|
|
||||||
const [connecterStyle, setConnecterStyle] = useState<'general' | 'scribbled'>(
|
|
||||||
'general'
|
|
||||||
);
|
|
||||||
const [connectorShape, setConnectorShape] = useState<
|
|
||||||
'elbowed' | 'curve' | 'straight'
|
|
||||||
>('elbowed');
|
|
||||||
const [borderStyle, setBorderStyle] = useState<'solid' | 'dash'>('solid');
|
|
||||||
const [borderThickness, setBorderThickness] = useState<number[]>([3]);
|
|
||||||
|
|
||||||
const connecterStyleItems = useMemo<RadioItem[]>(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
value: 'general',
|
|
||||||
label: t['com.affine.settings.editorSettings.edgeless.style.general'](),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'scribbled',
|
|
||||||
label:
|
|
||||||
t['com.affine.settings.editorSettings.edgeless.style.scribbled'](),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[t]
|
|
||||||
);
|
|
||||||
const connecterShapeItems = useMemo<RadioItem[]>(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
value: 'elbowed',
|
|
||||||
label:
|
|
||||||
t[
|
|
||||||
'com.affine.settings.editorSettings.edgeless.connecter.connector-shape.elbowed'
|
|
||||||
](),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'curve',
|
|
||||||
label:
|
|
||||||
t[
|
|
||||||
'com.affine.settings.editorSettings.edgeless.connecter.connector-shape.curve'
|
|
||||||
](),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'straight',
|
|
||||||
label:
|
|
||||||
t[
|
|
||||||
'com.affine.settings.editorSettings.edgeless.connecter.connector-shape.straight'
|
|
||||||
](),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[t]
|
|
||||||
);
|
|
||||||
const borderStyleItems = useMemo<RadioItem[]>(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
value: 'solid',
|
|
||||||
label:
|
|
||||||
t['com.affine.settings.editorSettings.edgeless.note.border.solid'](),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'dash',
|
|
||||||
label:
|
|
||||||
t['com.affine.settings.editorSettings.edgeless.note.border.dash'](),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[t]
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EdgelessSnapshot
|
|
||||||
title={t['com.affine.settings.editorSettings.edgeless.connecter']()}
|
|
||||||
option={['mock-option']}
|
|
||||||
type="mock-type"
|
|
||||||
/>
|
|
||||||
<SettingRow
|
|
||||||
name={t[
|
|
||||||
'com.affine.settings.editorSettings.edgeless.connecter.color'
|
|
||||||
]()}
|
|
||||||
desc={''}
|
|
||||||
>
|
|
||||||
<Menu items={<MenuItem>Grey</MenuItem>}>
|
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
|
||||||
Grey
|
|
||||||
</MenuTrigger>
|
|
||||||
</Menu>
|
|
||||||
</SettingRow>
|
|
||||||
<SettingRow
|
|
||||||
name={t['com.affine.settings.editorSettings.edgeless.style']()}
|
|
||||||
desc={''}
|
|
||||||
>
|
|
||||||
<RadioGroup
|
|
||||||
items={connecterStyleItems}
|
|
||||||
value={connecterStyle}
|
|
||||||
width={250}
|
|
||||||
className={settingWrapper}
|
|
||||||
onChange={setConnecterStyle}
|
|
||||||
/>
|
|
||||||
</SettingRow>
|
|
||||||
<SettingRow
|
|
||||||
name={t[
|
|
||||||
'com.affine.settings.editorSettings.edgeless.connecter.connector-shape'
|
|
||||||
]()}
|
|
||||||
desc={''}
|
|
||||||
>
|
|
||||||
<RadioGroup
|
|
||||||
items={connecterShapeItems}
|
|
||||||
value={connectorShape}
|
|
||||||
width={250}
|
|
||||||
className={settingWrapper}
|
|
||||||
onChange={setConnectorShape}
|
|
||||||
/>
|
|
||||||
</SettingRow>
|
|
||||||
<SettingRow
|
|
||||||
name={t[
|
|
||||||
'com.affine.settings.editorSettings.edgeless.connecter.border-style'
|
|
||||||
]()}
|
|
||||||
desc={''}
|
|
||||||
>
|
|
||||||
<RadioGroup
|
|
||||||
items={borderStyleItems}
|
|
||||||
value={borderStyle}
|
|
||||||
width={250}
|
|
||||||
className={settingWrapper}
|
|
||||||
onChange={setBorderStyle}
|
|
||||||
/>
|
|
||||||
</SettingRow>
|
|
||||||
<SettingRow
|
|
||||||
name={t[
|
|
||||||
'com.affine.settings.editorSettings.edgeless.connecter.border-thickness'
|
|
||||||
]()}
|
|
||||||
desc={''}
|
|
||||||
>
|
|
||||||
<Slider
|
|
||||||
value={borderThickness}
|
|
||||||
onValueChange={setBorderThickness}
|
|
||||||
min={1}
|
|
||||||
max={5}
|
|
||||||
step={1}
|
|
||||||
nodes={[1, 2, 3, 4, 5]}
|
|
||||||
/>
|
|
||||||
</SettingRow>
|
|
||||||
<SettingRow
|
|
||||||
name={t[
|
|
||||||
'com.affine.settings.editorSettings.edgeless.connecter.start-endpoint'
|
|
||||||
]()}
|
|
||||||
desc={''}
|
|
||||||
>
|
|
||||||
<Menu items={<MenuItem>None</MenuItem>}>
|
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
|
||||||
None
|
|
||||||
</MenuTrigger>
|
|
||||||
</Menu>
|
|
||||||
</SettingRow>
|
|
||||||
<SettingRow
|
|
||||||
name={t[
|
|
||||||
'com.affine.settings.editorSettings.edgeless.connecter.end-endpoint'
|
|
||||||
]()}
|
|
||||||
desc={''}
|
|
||||||
>
|
|
||||||
<Menu items={<MenuItem>Arrow</MenuItem>}>
|
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
|
||||||
Arrow
|
|
||||||
</MenuTrigger>
|
|
||||||
</Menu>
|
|
||||||
</SettingRow>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+278
@@ -0,0 +1,278 @@
|
|||||||
|
import {
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
MenuTrigger,
|
||||||
|
RadioGroup,
|
||||||
|
type RadioItem,
|
||||||
|
Slider,
|
||||||
|
} from '@affine/component';
|
||||||
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
|
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
||||||
|
import { useI18n } from '@affine/i18n';
|
||||||
|
import {
|
||||||
|
ConnectorMode,
|
||||||
|
LineColor,
|
||||||
|
PointStyle,
|
||||||
|
StrokeStyle,
|
||||||
|
} from '@blocksuite/blocks';
|
||||||
|
import { useFramework, useLiveData } from '@toeverything/infra';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { menuTrigger, settingWrapper } from '../style.css';
|
||||||
|
import { EdgelessSnapshot } from './snapshot';
|
||||||
|
|
||||||
|
enum ConnecterStyle {
|
||||||
|
General = 'general',
|
||||||
|
Scribbled = 'scribbled',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConnectorSettings = () => {
|
||||||
|
const t = useI18n();
|
||||||
|
const framework = useFramework();
|
||||||
|
const { editorSetting } = framework.get(EditorSettingService);
|
||||||
|
const settings = useLiveData(editorSetting.settings$);
|
||||||
|
|
||||||
|
const connecterStyleItems = useMemo<RadioItem[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
value: ConnecterStyle.General,
|
||||||
|
label: t['com.affine.settings.editorSettings.edgeless.style.general'](),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ConnecterStyle.Scribbled,
|
||||||
|
label:
|
||||||
|
t['com.affine.settings.editorSettings.edgeless.style.scribbled'](),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
const connecterStyle: ConnecterStyle = settings.connector.rough
|
||||||
|
? ConnecterStyle.Scribbled
|
||||||
|
: ConnecterStyle.General;
|
||||||
|
const setConnecterStyle = useCallback(
|
||||||
|
(value: ConnecterStyle) => {
|
||||||
|
const isRough = value === ConnecterStyle.Scribbled;
|
||||||
|
editorSetting.set('connector', {
|
||||||
|
rough: isRough,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[editorSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
const connectorShapeItems = useMemo<RadioItem[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
value: ConnectorMode.Orthogonal as any,
|
||||||
|
label:
|
||||||
|
t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.connecter.connector-shape.elbowed'
|
||||||
|
](),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ConnectorMode.Curve as any,
|
||||||
|
label:
|
||||||
|
t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.connecter.connector-shape.curve'
|
||||||
|
](),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ConnectorMode.Straight as any,
|
||||||
|
label:
|
||||||
|
t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.connecter.connector-shape.straight'
|
||||||
|
](),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
const connectorShape: ConnectorMode = settings.connector.mode;
|
||||||
|
const setConnectorShape = useCallback(
|
||||||
|
(value: ConnectorMode) => {
|
||||||
|
editorSetting.set('connector', {
|
||||||
|
mode: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[editorSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
const borderStyleItems = useMemo<RadioItem[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
value: StrokeStyle.Solid,
|
||||||
|
label:
|
||||||
|
t['com.affine.settings.editorSettings.edgeless.note.border.solid'](),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: StrokeStyle.Dash,
|
||||||
|
label:
|
||||||
|
t['com.affine.settings.editorSettings.edgeless.note.border.dash'](),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
const borderStyle: StrokeStyle = settings.connector.strokeStyle;
|
||||||
|
const setBorderStyle = useCallback(
|
||||||
|
(value: StrokeStyle) => {
|
||||||
|
editorSetting.set('connector', {
|
||||||
|
strokeStyle: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[editorSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
const borderThickness = settings.connector.strokeWidth;
|
||||||
|
const setBorderThickness = useCallback(
|
||||||
|
(value: number[]) => {
|
||||||
|
editorSetting.set('connector', {
|
||||||
|
strokeWidth: value[0],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[editorSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
const colorItems = useMemo(() => {
|
||||||
|
const { stroke } = settings.connector;
|
||||||
|
return Object.entries(LineColor).map(([name, value]) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('connector', { stroke: value });
|
||||||
|
};
|
||||||
|
const isSelected = stroke === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
|
const startEndPointItems = useMemo(() => {
|
||||||
|
const { frontEndpointStyle } = settings.connector;
|
||||||
|
return Object.entries(PointStyle).map(([name, value]) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('connector', { frontEndpointStyle: value });
|
||||||
|
};
|
||||||
|
const isSelected = frontEndpointStyle === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
|
const endEndPointItems = useMemo(() => {
|
||||||
|
const { rearEndpointStyle } = settings.connector;
|
||||||
|
return Object.entries(PointStyle).map(([name, value]) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('connector', { rearEndpointStyle: value });
|
||||||
|
};
|
||||||
|
const isSelected = rearEndpointStyle === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EdgelessSnapshot
|
||||||
|
title={t['com.affine.settings.editorSettings.edgeless.connecter']()}
|
||||||
|
option={['mock-option']}
|
||||||
|
type="mock-type"
|
||||||
|
/>
|
||||||
|
<SettingRow
|
||||||
|
name={t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.connecter.color'
|
||||||
|
]()}
|
||||||
|
desc={''}
|
||||||
|
>
|
||||||
|
<Menu items={colorItems}>
|
||||||
|
<MenuTrigger className={menuTrigger}>
|
||||||
|
{String(settings.connector.stroke)}
|
||||||
|
</MenuTrigger>
|
||||||
|
</Menu>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t['com.affine.settings.editorSettings.edgeless.style']()}
|
||||||
|
desc={''}
|
||||||
|
>
|
||||||
|
<RadioGroup
|
||||||
|
items={connecterStyleItems}
|
||||||
|
value={connecterStyle}
|
||||||
|
width={250}
|
||||||
|
className={settingWrapper}
|
||||||
|
onChange={setConnecterStyle}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.connecter.connector-shape'
|
||||||
|
]()}
|
||||||
|
desc={''}
|
||||||
|
>
|
||||||
|
<RadioGroup
|
||||||
|
items={connectorShapeItems}
|
||||||
|
value={connectorShape}
|
||||||
|
width={250}
|
||||||
|
className={settingWrapper}
|
||||||
|
onChange={setConnectorShape}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.connecter.border-style'
|
||||||
|
]()}
|
||||||
|
desc={''}
|
||||||
|
>
|
||||||
|
<RadioGroup
|
||||||
|
items={borderStyleItems}
|
||||||
|
value={borderStyle}
|
||||||
|
width={250}
|
||||||
|
className={settingWrapper}
|
||||||
|
onChange={setBorderStyle}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.connecter.border-thickness'
|
||||||
|
]()}
|
||||||
|
desc={''}
|
||||||
|
>
|
||||||
|
<Slider
|
||||||
|
value={[borderThickness]}
|
||||||
|
onValueChange={setBorderThickness}
|
||||||
|
min={2}
|
||||||
|
max={12}
|
||||||
|
step={2}
|
||||||
|
nodes={[2, 4, 6, 8, 10, 12]}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.connecter.start-endpoint'
|
||||||
|
]()}
|
||||||
|
desc={''}
|
||||||
|
>
|
||||||
|
<Menu items={startEndPointItems}>
|
||||||
|
<MenuTrigger className={menuTrigger}>
|
||||||
|
{String(settings.connector.frontEndpointStyle)}
|
||||||
|
</MenuTrigger>
|
||||||
|
</Menu>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.connecter.end-endpoint'
|
||||||
|
]()}
|
||||||
|
desc={''}
|
||||||
|
>
|
||||||
|
<Menu items={endEndPointItems}>
|
||||||
|
<MenuTrigger className={menuTrigger}>
|
||||||
|
{String(settings.connector.rearEndpointStyle)}
|
||||||
|
</MenuTrigger>
|
||||||
|
</Menu>
|
||||||
|
</SettingRow>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import { SettingWrapper } from '@affine/component/setting-components';
|
import { SettingWrapper } from '@affine/component/setting-components';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
|
|
||||||
import { ConnecterSettings } from './connecter';
|
import { ConnectorSettings } from './connector';
|
||||||
import { MindMapSettings } from './mind-map';
|
import { MindMapSettings } from './mind-map';
|
||||||
import { NoteSettings } from './note';
|
import { NoteSettings } from './note';
|
||||||
import { PenSettings } from './pen';
|
import { PenSettings } from './pen';
|
||||||
@@ -15,7 +15,7 @@ export const Edgeless = () => {
|
|||||||
<NoteSettings />
|
<NoteSettings />
|
||||||
<TextSettings />
|
<TextSettings />
|
||||||
<ShapeSettings />
|
<ShapeSettings />
|
||||||
<ConnecterSettings />
|
<ConnectorSettings />
|
||||||
<PenSettings />
|
<PenSettings />
|
||||||
<MindMapSettings />
|
<MindMapSettings />
|
||||||
</SettingWrapper>
|
</SettingWrapper>
|
||||||
|
|||||||
+123
-23
@@ -7,39 +7,139 @@ import {
|
|||||||
Slider,
|
Slider,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
|
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useMemo, useState } from 'react';
|
import {
|
||||||
|
NoteBackgroundColor,
|
||||||
|
NoteShadow,
|
||||||
|
StrokeStyle,
|
||||||
|
} from '@blocksuite/blocks';
|
||||||
|
import { useFramework, useLiveData } from '@toeverything/infra';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { menuTrigger, settingWrapper } from '../style.css';
|
import { menuTrigger, settingWrapper } from '../style.css';
|
||||||
import { EdgelessSnapshot } from './snapshot';
|
import { EdgelessSnapshot } from './snapshot';
|
||||||
|
|
||||||
|
const CORNER_SIZE = [
|
||||||
|
{ name: 'None', value: 0 },
|
||||||
|
{ name: 'Small', value: 8 },
|
||||||
|
{ name: 'Medium', value: 16 },
|
||||||
|
{ name: 'Large', value: 24 },
|
||||||
|
{ name: 'Huge', value: 32 },
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const NoteSettings = () => {
|
export const NoteSettings = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const [borderStyle, setBorderStyle] = useState<'solid' | 'dash' | 'none'>(
|
const framework = useFramework();
|
||||||
'solid'
|
const { editorSetting } = framework.get(EditorSettingService);
|
||||||
);
|
const settings = useLiveData(editorSetting.settings$);
|
||||||
const [borderThickness, setBorderThickness] = useState<number[]>([3]);
|
|
||||||
|
|
||||||
const borderStyleItems = useMemo<RadioItem[]>(
|
const borderStyleItems = useMemo<RadioItem[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
value: 'solid',
|
value: StrokeStyle.Solid,
|
||||||
label:
|
label:
|
||||||
t['com.affine.settings.editorSettings.edgeless.note.border.solid'](),
|
t['com.affine.settings.editorSettings.edgeless.note.border.solid'](),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'dash',
|
value: StrokeStyle.Dash,
|
||||||
label:
|
label:
|
||||||
t['com.affine.settings.editorSettings.edgeless.note.border.dash'](),
|
t['com.affine.settings.editorSettings.edgeless.note.border.dash'](),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'none',
|
value: StrokeStyle.None,
|
||||||
label:
|
label:
|
||||||
t['com.affine.settings.editorSettings.edgeless.note.border.none'](),
|
t['com.affine.settings.editorSettings.edgeless.note.border.none'](),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[t]
|
[t]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { borderStyle } = settings['affine:note'].edgeless.style;
|
||||||
|
const setBorderStyle = useCallback(
|
||||||
|
(value: StrokeStyle) => {
|
||||||
|
editorSetting.set('affine:note', {
|
||||||
|
edgeless: {
|
||||||
|
style: {
|
||||||
|
borderStyle: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[editorSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { borderSize } = settings['affine:note'].edgeless.style;
|
||||||
|
const setBorderSize = useCallback(
|
||||||
|
(value: number[]) => {
|
||||||
|
editorSetting.set('affine:note', {
|
||||||
|
edgeless: {
|
||||||
|
style: {
|
||||||
|
borderSize: value[0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[editorSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
const backgroundItems = useMemo(() => {
|
||||||
|
const { background } = settings['affine:note'];
|
||||||
|
return Object.entries(NoteBackgroundColor).map(([name, value]) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('affine:note', { background: value });
|
||||||
|
};
|
||||||
|
const isSelected = background === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
|
const cornerItems = useMemo(() => {
|
||||||
|
const { borderRadius } = settings['affine:note'].edgeless.style;
|
||||||
|
return CORNER_SIZE.map(({ name, value }) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('affine:note', {
|
||||||
|
edgeless: {
|
||||||
|
style: {
|
||||||
|
borderRadius: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const isSelected = borderRadius === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
|
const shadowItems = useMemo(() => {
|
||||||
|
const { shadowType } = settings['affine:note'].edgeless.style;
|
||||||
|
return Object.entries(NoteShadow).map(([name, value]) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('affine:note', {
|
||||||
|
edgeless: {
|
||||||
|
style: {
|
||||||
|
shadowType: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const isSelected = shadowType === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EdgelessSnapshot
|
<EdgelessSnapshot
|
||||||
@@ -53,9 +153,9 @@ export const NoteSettings = () => {
|
|||||||
]()}
|
]()}
|
||||||
desc={''}
|
desc={''}
|
||||||
>
|
>
|
||||||
<Menu items={<MenuItem>Yellow</MenuItem>}>
|
<Menu items={backgroundItems}>
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
<MenuTrigger className={menuTrigger}>
|
||||||
Yellow
|
{String(settings['affine:note'].background)}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
@@ -63,9 +163,9 @@ export const NoteSettings = () => {
|
|||||||
name={t['com.affine.settings.editorSettings.edgeless.note.corners']()}
|
name={t['com.affine.settings.editorSettings.edgeless.note.corners']()}
|
||||||
desc={''}
|
desc={''}
|
||||||
>
|
>
|
||||||
<Menu items={<MenuItem>Small</MenuItem>}>
|
<Menu items={cornerItems}>
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
<MenuTrigger className={menuTrigger}>
|
||||||
Small
|
{String(settings['affine:note'].edgeless.style.borderRadius)}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
@@ -73,9 +173,9 @@ export const NoteSettings = () => {
|
|||||||
name={t['com.affine.settings.editorSettings.edgeless.note.shadow']()}
|
name={t['com.affine.settings.editorSettings.edgeless.note.shadow']()}
|
||||||
desc={''}
|
desc={''}
|
||||||
>
|
>
|
||||||
<Menu items={<MenuItem>Box shadow</MenuItem>}>
|
<Menu items={shadowItems}>
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
<MenuTrigger className={menuTrigger}>
|
||||||
Box shadow
|
{String(settings['affine:note'].edgeless.style.shadowType)}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
@@ -98,12 +198,12 @@ export const NoteSettings = () => {
|
|||||||
desc={''}
|
desc={''}
|
||||||
>
|
>
|
||||||
<Slider
|
<Slider
|
||||||
value={borderThickness}
|
value={[borderSize]}
|
||||||
onValueChange={setBorderThickness}
|
onValueChange={setBorderSize}
|
||||||
min={1}
|
min={2}
|
||||||
max={5}
|
max={12}
|
||||||
step={1}
|
step={2}
|
||||||
nodes={[1, 2, 3, 4, 5]}
|
nodes={[2, 4, 6, 8, 10, 12]}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</>
|
</>
|
||||||
|
|||||||
+41
-10
@@ -1,14 +1,45 @@
|
|||||||
import { Menu, MenuItem, MenuTrigger, Slider } from '@affine/component';
|
import { Menu, MenuItem, MenuTrigger, Slider } from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
|
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useState } from 'react';
|
import { LineColor } from '@blocksuite/blocks';
|
||||||
|
import { useFramework, useLiveData } from '@toeverything/infra';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { menuTrigger } from '../style.css';
|
import { menuTrigger } from '../style.css';
|
||||||
import { EdgelessSnapshot } from './snapshot';
|
import { EdgelessSnapshot } from './snapshot';
|
||||||
|
|
||||||
export const PenSettings = () => {
|
export const PenSettings = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const [borderThickness, setBorderThickness] = useState<number[]>([3]);
|
const framework = useFramework();
|
||||||
|
const { editorSetting } = framework.get(EditorSettingService);
|
||||||
|
const settings = useLiveData(editorSetting.settings$);
|
||||||
|
|
||||||
|
const colorItems = useMemo(() => {
|
||||||
|
const { color } = settings.brush;
|
||||||
|
return Object.entries(LineColor).map(([name, value]) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('brush', { color: value });
|
||||||
|
};
|
||||||
|
const isSelected = color === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
|
const borderThickness = settings.brush.lineWidth;
|
||||||
|
const setBorderThickness = useCallback(
|
||||||
|
(value: number[]) => {
|
||||||
|
editorSetting.set('brush', {
|
||||||
|
lineWidth: value[0],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[editorSetting]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EdgelessSnapshot
|
<EdgelessSnapshot
|
||||||
@@ -20,9 +51,9 @@ export const PenSettings = () => {
|
|||||||
name={t['com.affine.settings.editorSettings.edgeless.pen.color']()}
|
name={t['com.affine.settings.editorSettings.edgeless.pen.color']()}
|
||||||
desc={''}
|
desc={''}
|
||||||
>
|
>
|
||||||
<Menu items={<MenuItem>Yellow</MenuItem>}>
|
<Menu items={colorItems}>
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
<MenuTrigger className={menuTrigger}>
|
||||||
Yellow
|
{String(settings.brush.color)}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
@@ -31,12 +62,12 @@ export const PenSettings = () => {
|
|||||||
desc={''}
|
desc={''}
|
||||||
>
|
>
|
||||||
<Slider
|
<Slider
|
||||||
value={borderThickness}
|
value={[borderThickness]}
|
||||||
onValueChange={setBorderThickness}
|
onValueChange={setBorderThickness}
|
||||||
min={1}
|
min={2}
|
||||||
max={5}
|
max={12}
|
||||||
step={1}
|
step={2}
|
||||||
nodes={[1, 2, 3, 4, 5]}
|
nodes={[2, 4, 6, 8, 10, 12]}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</>
|
</>
|
||||||
|
|||||||
+107
-24
@@ -6,37 +6,46 @@ import {
|
|||||||
type RadioItem,
|
type RadioItem,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
|
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useMemo, useState } from 'react';
|
import {
|
||||||
|
FontFamily,
|
||||||
|
FontFamilyMap,
|
||||||
|
FontStyle,
|
||||||
|
FontWeight,
|
||||||
|
LineColor,
|
||||||
|
TextAlign,
|
||||||
|
} from '@blocksuite/blocks';
|
||||||
|
import { useFramework, useLiveData } from '@toeverything/infra';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { menuTrigger, settingWrapper } from '../style.css';
|
import { menuTrigger, settingWrapper } from '../style.css';
|
||||||
import { EdgelessSnapshot } from './snapshot';
|
import { EdgelessSnapshot } from './snapshot';
|
||||||
|
|
||||||
export const TextSettings = () => {
|
export const TextSettings = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
const framework = useFramework();
|
||||||
const [textAlignment, setTextAlignment] = useState<
|
const { editorSetting } = framework.get(EditorSettingService);
|
||||||
'left' | 'center' | 'right'
|
const settings = useLiveData(editorSetting.settings$);
|
||||||
>('left');
|
|
||||||
|
|
||||||
const alignItems = useMemo<RadioItem[]>(
|
const alignItems = useMemo<RadioItem[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
value: 'left',
|
value: TextAlign.Left,
|
||||||
label:
|
label:
|
||||||
t[
|
t[
|
||||||
'com.affine.settings.editorSettings.edgeless.text.alignment.left'
|
'com.affine.settings.editorSettings.edgeless.text.alignment.left'
|
||||||
](),
|
](),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'center',
|
value: TextAlign.Center,
|
||||||
label:
|
label:
|
||||||
t[
|
t[
|
||||||
'com.affine.settings.editorSettings.edgeless.text.alignment.center'
|
'com.affine.settings.editorSettings.edgeless.text.alignment.center'
|
||||||
](),
|
](),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'right',
|
value: TextAlign.Right,
|
||||||
label:
|
label:
|
||||||
t[
|
t[
|
||||||
'com.affine.settings.editorSettings.edgeless.text.alignment.right'
|
'com.affine.settings.editorSettings.edgeless.text.alignment.right'
|
||||||
@@ -46,6 +55,76 @@ export const TextSettings = () => {
|
|||||||
[t]
|
[t]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { textAlign } = settings['affine:edgeless-text'];
|
||||||
|
const setTextAlign = useCallback(
|
||||||
|
(value: TextAlign) => {
|
||||||
|
editorSetting.set('affine:edgeless-text', {
|
||||||
|
textAlign: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[editorSetting]
|
||||||
|
);
|
||||||
|
|
||||||
|
const colorItems = useMemo(() => {
|
||||||
|
const { color } = settings['affine:edgeless-text'];
|
||||||
|
return Object.entries(LineColor).map(([name, value]) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('affine:edgeless-text', { color: value });
|
||||||
|
};
|
||||||
|
const isSelected = color === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
|
const fontFamilyItems = useMemo(() => {
|
||||||
|
const { fontFamily } = settings['affine:edgeless-text'];
|
||||||
|
return Object.entries(FontFamily).map(([name, value]) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('affine:edgeless-text', { fontFamily: value });
|
||||||
|
};
|
||||||
|
const isSelected = fontFamily === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
|
const fontStyleItems = useMemo(() => {
|
||||||
|
const { fontStyle } = settings['affine:edgeless-text'];
|
||||||
|
return Object.entries(FontStyle).map(([name, value]) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('affine:edgeless-text', { fontStyle: value });
|
||||||
|
};
|
||||||
|
const isSelected = fontStyle === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
|
const fontWeightItems = useMemo(() => {
|
||||||
|
const { fontWeight } = settings['affine:edgeless-text'];
|
||||||
|
return Object.entries(FontWeight).map(([name, value]) => {
|
||||||
|
const handler = () => {
|
||||||
|
editorSetting.set('affine:edgeless-text', { fontWeight: value });
|
||||||
|
};
|
||||||
|
const isSelected = fontWeight === value;
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} onSelect={handler} selected={isSelected}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [editorSetting, settings]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EdgelessSnapshot
|
<EdgelessSnapshot
|
||||||
@@ -57,29 +136,33 @@ export const TextSettings = () => {
|
|||||||
name={t['com.affine.settings.editorSettings.edgeless.text.color']()}
|
name={t['com.affine.settings.editorSettings.edgeless.text.color']()}
|
||||||
desc={''}
|
desc={''}
|
||||||
>
|
>
|
||||||
<Menu items={<MenuItem>Blue</MenuItem>}>
|
<Menu items={colorItems}>
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
<MenuTrigger className={menuTrigger}>
|
||||||
Blue
|
{String(settings['affine:edgeless-text'].color)}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['com.affine.settings.editorSettings.edgeless.text.font']()}
|
name={t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.text.font-family'
|
||||||
|
]()}
|
||||||
desc={''}
|
desc={''}
|
||||||
>
|
>
|
||||||
<Menu items={<MenuItem>Inter</MenuItem>}>
|
<Menu items={fontFamilyItems}>
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
<MenuTrigger className={menuTrigger}>
|
||||||
Inter
|
{FontFamilyMap[settings['affine:edgeless-text'].fontFamily]}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t['com.affine.settings.editorSettings.edgeless.text.font-size']()}
|
name={t[
|
||||||
|
'com.affine.settings.editorSettings.edgeless.text.font-style'
|
||||||
|
]()}
|
||||||
desc={''}
|
desc={''}
|
||||||
>
|
>
|
||||||
<Menu items={<MenuItem>15px</MenuItem>}>
|
<Menu items={fontStyleItems}>
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
<MenuTrigger className={menuTrigger}>
|
||||||
15px
|
{String(settings['affine:edgeless-text'].fontStyle)}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
@@ -89,9 +172,9 @@ export const TextSettings = () => {
|
|||||||
]()}
|
]()}
|
||||||
desc={''}
|
desc={''}
|
||||||
>
|
>
|
||||||
<Menu items={<MenuItem>Regular</MenuItem>}>
|
<Menu items={fontWeightItems}>
|
||||||
<MenuTrigger className={menuTrigger} disabled>
|
<MenuTrigger className={menuTrigger}>
|
||||||
Regular
|
{settings['affine:edgeless-text'].fontWeight}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
@@ -101,10 +184,10 @@ export const TextSettings = () => {
|
|||||||
>
|
>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
items={alignItems}
|
items={alignItems}
|
||||||
value={textAlignment}
|
value={textAlign}
|
||||||
width={250}
|
width={250}
|
||||||
className={settingWrapper}
|
className={settingWrapper}
|
||||||
onChange={setTextAlignment}
|
onChange={setTextAlign}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</>
|
</>
|
||||||
|
|||||||
+5
@@ -3,6 +3,7 @@ import {
|
|||||||
AIPageRootBlockSpec,
|
AIPageRootBlockSpec,
|
||||||
} from '@affine/core/blocksuite/presets/ai';
|
} from '@affine/core/blocksuite/presets/ai';
|
||||||
import { mixpanel } from '@affine/core/mixpanel';
|
import { mixpanel } from '@affine/core/mixpanel';
|
||||||
|
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
||||||
import {
|
import {
|
||||||
BlockFlavourIdentifier,
|
BlockFlavourIdentifier,
|
||||||
BlockServiceIdentifier,
|
BlockServiceIdentifier,
|
||||||
@@ -54,6 +55,7 @@ function withAffineRootService(Service: typeof PageRootService) {
|
|||||||
export function createPageRootBlockSpec(
|
export function createPageRootBlockSpec(
|
||||||
framework: FrameworkProvider
|
framework: FrameworkProvider
|
||||||
): ExtensionType[] {
|
): ExtensionType[] {
|
||||||
|
const editorSettingService = framework.get(EditorSettingService);
|
||||||
return [
|
return [
|
||||||
...AIPageRootBlockSpec,
|
...AIPageRootBlockSpec,
|
||||||
{
|
{
|
||||||
@@ -67,6 +69,7 @@ export function createPageRootBlockSpec(
|
|||||||
},
|
},
|
||||||
ConfigExtension('affine:page', {
|
ConfigExtension('affine:page', {
|
||||||
linkedWidget: createLinkedWidgetConfig(framework),
|
linkedWidget: createLinkedWidgetConfig(framework),
|
||||||
|
editorSetting: editorSettingService.editorSetting.settingSignal,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -74,6 +77,7 @@ export function createPageRootBlockSpec(
|
|||||||
export function createEdgelessRootBlockSpec(
|
export function createEdgelessRootBlockSpec(
|
||||||
framework: FrameworkProvider
|
framework: FrameworkProvider
|
||||||
): ExtensionType[] {
|
): ExtensionType[] {
|
||||||
|
const editorSettingService = framework.get(EditorSettingService);
|
||||||
return [
|
return [
|
||||||
...AIEdgelessRootBlockSpec,
|
...AIEdgelessRootBlockSpec,
|
||||||
{
|
{
|
||||||
@@ -87,6 +91,7 @@ export function createEdgelessRootBlockSpec(
|
|||||||
},
|
},
|
||||||
ConfigExtension('affine:page', {
|
ConfigExtension('affine:page', {
|
||||||
linkedWidget: createLinkedWidgetConfig(framework),
|
linkedWidget: createLinkedWidgetConfig(framework),
|
||||||
|
editorSetting: editorSettingService.editorSetting.settingSignal,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-34
@@ -1,7 +1,6 @@
|
|||||||
import { Framework, GlobalState, MemoryMemento } from '@toeverything/infra';
|
import { Framework, GlobalState, MemoryMemento } from '@toeverything/infra';
|
||||||
import { expect, test } from 'vitest';
|
import { expect, test } from 'vitest';
|
||||||
|
|
||||||
import { unflattenObject } from '../../../utils/unflatten-object';
|
|
||||||
import { EditorSetting } from '../entities/editor-setting';
|
import { EditorSetting } from '../entities/editor-setting';
|
||||||
import { GlobalStateEditorSettingProvider } from '../impls/global-state';
|
import { GlobalStateEditorSettingProvider } from '../impls/global-state';
|
||||||
import { EditorSettingProvider } from '../provider/editor-setting-provider';
|
import { EditorSettingProvider } from '../provider/editor-setting-provider';
|
||||||
@@ -23,27 +22,23 @@ test('editor setting service', () => {
|
|||||||
const editorSettingService = provider.get(EditorSettingService);
|
const editorSettingService = provider.get(EditorSettingService);
|
||||||
|
|
||||||
// default value
|
// default value
|
||||||
expect(editorSettingService.editorSetting.settings$.value).toMatchObject({
|
expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Sans');
|
||||||
fontFamily: 'Sans',
|
|
||||||
'connector.stroke': '#000000',
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// set plain object
|
||||||
editorSettingService.editorSetting.set('fontFamily', 'Serif');
|
editorSettingService.editorSetting.set('fontFamily', 'Serif');
|
||||||
expect(editorSettingService.editorSetting.settings$.value).toMatchObject({
|
expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Serif');
|
||||||
fontFamily: 'Serif',
|
|
||||||
});
|
|
||||||
|
|
||||||
// nested object, should be serialized
|
// set nested object
|
||||||
editorSettingService.editorSetting.set('connector.stroke', {
|
editorSettingService.editorSetting.set('connector', {
|
||||||
|
stroke: {
|
||||||
|
dark: '#000000',
|
||||||
|
light: '#ffffff',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(editorSettingService.editorSetting.get('connector').stroke).toEqual({
|
||||||
dark: '#000000',
|
dark: '#000000',
|
||||||
light: '#ffffff',
|
light: '#ffffff',
|
||||||
});
|
});
|
||||||
expect(
|
|
||||||
(
|
|
||||||
editorSettingService.editorSetting
|
|
||||||
.provider as GlobalStateEditorSettingProvider
|
|
||||||
).get('connector.stroke')
|
|
||||||
).toBe('{"dark":"#000000","light":"#ffffff"}');
|
|
||||||
|
|
||||||
// invalid font family
|
// invalid font family
|
||||||
editorSettingService.editorSetting.provider.set(
|
editorSettingService.editorSetting.provider.set(
|
||||||
@@ -51,22 +46,6 @@ test('editor setting service', () => {
|
|||||||
JSON.stringify('abc')
|
JSON.stringify('abc')
|
||||||
);
|
);
|
||||||
|
|
||||||
// should fallback to default value
|
// fallback to default value
|
||||||
expect(editorSettingService.editorSetting.settings$.value['fontFamily']).toBe(
|
expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Sans');
|
||||||
'Sans'
|
|
||||||
);
|
|
||||||
|
|
||||||
// expend demo
|
|
||||||
const expended = unflattenObject(
|
|
||||||
editorSettingService.editorSetting.settings$.value
|
|
||||||
);
|
|
||||||
expect(expended).toMatchObject({
|
|
||||||
fontFamily: 'Sans',
|
|
||||||
connector: {
|
|
||||||
stroke: {
|
|
||||||
dark: '#000000',
|
|
||||||
light: '#ffffff',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 { 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 type { EditorSettingProvider } from '../provider/editor-setting-provider';
|
||||||
import { EditorSettingSchema } from '../schema';
|
import { EditorSettingSchema } from '../schema';
|
||||||
@@ -7,17 +14,30 @@ import { EditorSettingSchema } from '../schema';
|
|||||||
export class EditorSetting extends Entity {
|
export class EditorSetting extends Entity {
|
||||||
constructor(public readonly provider: EditorSettingProvider) {
|
constructor(public readonly provider: EditorSettingProvider) {
|
||||||
super();
|
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);
|
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>(
|
set<K extends keyof EditorSettingSchema>(
|
||||||
key: K,
|
key: K,
|
||||||
value: EditorSettingSchema[K]
|
value: DeepPartial<EditorSettingSchema[K]>
|
||||||
) {
|
) {
|
||||||
const schema = EditorSettingSchema.shape[key];
|
const schema = EditorSettingSchema.shape[key];
|
||||||
|
const curValue = this.get(key);
|
||||||
this.provider.set(key, JSON.stringify(schema.parse(value)));
|
const nextValue = isObject(curValue) ? merge(curValue, value) : value;
|
||||||
|
this.provider.set(key, JSON.stringify(schema.parse(nextValue)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private watchAll(): Observable<EditorSettingSchema> {
|
private watchAll(): Observable<EditorSettingSchema> {
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
|
import {
|
||||||
|
BrushSchema,
|
||||||
|
ConnectorSchema,
|
||||||
|
EdgelessTextSchema,
|
||||||
|
NoteSchema,
|
||||||
|
ShapeSchema,
|
||||||
|
} from '@blocksuite/affine-shared/utils';
|
||||||
import { z } from 'zod';
|
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 type FontFamily = 'Sans' | 'Serif' | 'Mono' | 'Custom';
|
||||||
|
|
||||||
export const fontStyleOptions = [
|
export const fontStyleOptions = [
|
||||||
@@ -12,20 +28,6 @@ export const fontStyleOptions = [
|
|||||||
value: string;
|
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({
|
const AffineEditorSettingSchema = z.object({
|
||||||
fontFamily: z.enum(['Sans', 'Serif', 'Mono', 'Custom']).default('Sans'),
|
fontFamily: z.enum(['Sans', 'Serif', 'Mono', 'Custom']).default('Sans'),
|
||||||
customFontFamily: z.string().default(''),
|
customFontFamily: z.string().default(''),
|
||||||
@@ -36,44 +38,8 @@ const AffineEditorSettingSchema = z.object({
|
|||||||
displayBiDirectionalLink: z.boolean().default(true),
|
displayBiDirectionalLink: z.boolean().default(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
|
export const EditorSettingSchema = BSEditorSettingSchema.merge(
|
||||||
x: infer I
|
AffineEditorSettingSchema
|
||||||
) => 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 type EditorSettingSchema = z.infer<typeof EditorSettingSchema>;
|
export type EditorSettingSchema = z.infer<typeof EditorSettingSchema>;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { UserQuotaChanged } from '../../cloud/services/user-quota';
|
|||||||
export class TelemetryService extends Service {
|
export class TelemetryService extends Service {
|
||||||
private prevQuota: NonNullable<QuotaQuery['currentUser']>['quota'] | null =
|
private prevQuota: NonNullable<QuotaQuery['currentUser']>['quota'] | null =
|
||||||
null;
|
null;
|
||||||
private readonly disposables: (() => void)[] = [];
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly auth: AuthService,
|
private readonly auth: AuthService,
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { expect, test } from 'vitest';
|
|
||||||
|
|
||||||
import { unflattenObject } from '../unflatten-object';
|
|
||||||
|
|
||||||
test('unflattenObject', () => {
|
|
||||||
const ob = {
|
|
||||||
'a.b.c': 1,
|
|
||||||
d: 2,
|
|
||||||
};
|
|
||||||
const result = unflattenObject(ob);
|
|
||||||
expect(result).toEqual({
|
|
||||||
a: {
|
|
||||||
b: {
|
|
||||||
c: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
d: 2,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1270,6 +1270,8 @@
|
|||||||
"com.affine.settings.editorSettings.edgeless.text": "Text",
|
"com.affine.settings.editorSettings.edgeless.text": "Text",
|
||||||
"com.affine.settings.editorSettings.edgeless.text.color": "Text color",
|
"com.affine.settings.editorSettings.edgeless.text.color": "Text color",
|
||||||
"com.affine.settings.editorSettings.edgeless.text.font": "Font",
|
"com.affine.settings.editorSettings.edgeless.text.font": "Font",
|
||||||
|
"com.affine.settings.editorSettings.edgeless.text.font-family": "Font Family",
|
||||||
|
"com.affine.settings.editorSettings.edgeless.text.font-style": "Font Style",
|
||||||
"com.affine.settings.editorSettings.edgeless.text.font-size": "Font size",
|
"com.affine.settings.editorSettings.edgeless.text.font-size": "Font size",
|
||||||
"com.affine.settings.editorSettings.edgeless.text.font-weight": "Font weight",
|
"com.affine.settings.editorSettings.edgeless.text.font-weight": "Font weight",
|
||||||
"com.affine.settings.editorSettings.edgeless.text.alignment": "Alignment",
|
"com.affine.settings.editorSettings.edgeless.text.alignment": "Alignment",
|
||||||
|
|||||||
Reference in New Issue
Block a user