mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-09 02:53:45 +00:00
Compare commits
8 Commits
hwang/inte
...
eyhn/fix/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73bbb6c9d2 | ||
|
|
76bc48dd9c | ||
|
|
d28a6300d9 | ||
|
|
9a4cb5d69a | ||
|
|
3a220da1b6 | ||
|
|
499f986261 | ||
|
|
87767df0fd | ||
|
|
bc77d7a648 |
1
packages/common/env/src/global.ts
vendored
1
packages/common/env/src/global.ts
vendored
@@ -32,6 +32,7 @@ export const runtimeFlagsSchema = z.object({
|
||||
enableInfoModal: z.boolean(),
|
||||
enableOrganize: z.boolean(),
|
||||
enableThemeEditor: z.boolean(),
|
||||
enableEditorSettings: z.boolean(),
|
||||
});
|
||||
|
||||
export type RuntimeConfig = z.infer<typeof runtimeFlagsSchema>;
|
||||
|
||||
@@ -22,6 +22,7 @@ export type AppSetting = {
|
||||
fullWidthLayout: boolean;
|
||||
windowFrameStyle: 'frameless' | 'NativeTitleBar';
|
||||
fontStyle: FontFamily;
|
||||
customFontFamily: string;
|
||||
dateFormat: DateFormats;
|
||||
startWeekOnMonday: boolean;
|
||||
enableBlurBackground: boolean;
|
||||
@@ -45,12 +46,13 @@ export const dateFormatOptions: DateFormats[] = [
|
||||
'dd MMMM YYYY',
|
||||
];
|
||||
|
||||
export type FontFamily = 'Sans' | 'Serif' | 'Mono';
|
||||
export type FontFamily = 'Sans' | 'Serif' | 'Mono' | 'Custom';
|
||||
|
||||
export const fontStyleOptions = [
|
||||
{ key: 'Sans', value: 'var(--affine-font-sans-family)' },
|
||||
{ key: 'Serif', value: 'var(--affine-font-serif-family)' },
|
||||
{ key: 'Mono', value: 'var(--affine-font-mono-family)' },
|
||||
{ key: 'Custom', value: 'var(--affine-font-sans-family)' },
|
||||
] satisfies {
|
||||
key: FontFamily;
|
||||
value: string;
|
||||
@@ -61,6 +63,7 @@ const appSettingBaseAtom = atomWithStorage<AppSetting>('affine-settings', {
|
||||
fullWidthLayout: false,
|
||||
windowFrameStyle: 'frameless',
|
||||
fontStyle: 'Sans',
|
||||
customFontFamily: '',
|
||||
dateFormat: dateFormatOptions[0],
|
||||
startWeekOnMonday: false,
|
||||
enableBlurBackground: true,
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||
"@radix-ui/react-slider": "^1.2.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@radix-ui/react-toolbar": "^1.0.4",
|
||||
|
||||
@@ -22,6 +22,7 @@ export * from './ui/popover';
|
||||
export * from './ui/radio';
|
||||
export * from './ui/scrollbar';
|
||||
export * from './ui/skeleton';
|
||||
export * from './ui/slider';
|
||||
export * from './ui/switch';
|
||||
export * from './ui/table';
|
||||
export * from './ui/tabs';
|
||||
|
||||
57
packages/frontend/component/src/ui/slider/index.css.ts
Normal file
57
packages/frontend/component/src/ui/slider/index.css.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const trackStyle = style({
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '12px 0',
|
||||
cursor: 'pointer',
|
||||
});
|
||||
export const fakeTrackStyle = style({
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
backgroundColor: cssVarV2('layer/insideBorder/border'),
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
});
|
||||
|
||||
export const filledTrackStyle = style({
|
||||
height: '100%',
|
||||
backgroundColor: cssVarV2('icon/primary'),
|
||||
borderRadius: '1px',
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
left: '0',
|
||||
});
|
||||
|
||||
export const thumbStyle = style({
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
backgroundColor: cssVarV2('icon/primary'),
|
||||
borderRadius: '50%',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
cursor: 'pointer',
|
||||
});
|
||||
|
||||
export const nodeStyle = style({
|
||||
width: '4px',
|
||||
height: '4px',
|
||||
border: '2px solid transparent',
|
||||
backgroundColor: cssVarV2('layer/insideBorder/border'),
|
||||
borderRadius: '50%',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
cursor: 'pointer',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
selectors: {
|
||||
'&[data-active="true"]': {
|
||||
backgroundColor: cssVarV2('icon/primary'),
|
||||
},
|
||||
},
|
||||
});
|
||||
1
packages/frontend/component/src/ui/slider/index.ts
Normal file
1
packages/frontend/component/src/ui/slider/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './slider';
|
||||
24
packages/frontend/component/src/ui/slider/slider.stories.tsx
Normal file
24
packages/frontend/component/src/ui/slider/slider.stories.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import type { SliderProps } from './index';
|
||||
import { Slider } from './index';
|
||||
|
||||
export default {
|
||||
title: 'UI/Slider',
|
||||
component: Slider,
|
||||
} satisfies Meta<typeof Slider>;
|
||||
|
||||
const Template: StoryFn<SliderProps> = args => {
|
||||
const [value, setValue] = useState<number[]>([0]);
|
||||
return <Slider value={value} onValueChange={setValue} {...args} />;
|
||||
};
|
||||
|
||||
export const Default: StoryFn<SliderProps> = Template.bind(undefined);
|
||||
Default.args = {
|
||||
min: 0,
|
||||
max: 10,
|
||||
width: 500,
|
||||
step: 1,
|
||||
nodes: [0, 5, 10],
|
||||
};
|
||||
75
packages/frontend/component/src/ui/slider/slider.tsx
Normal file
75
packages/frontend/component/src/ui/slider/slider.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import * as Sliders from '@radix-ui/react-slider';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import * as styles from './index.css';
|
||||
|
||||
export interface SliderProps extends Sliders.SliderProps {
|
||||
width?: number;
|
||||
containerStyle?: React.CSSProperties;
|
||||
rootStyle?: React.CSSProperties;
|
||||
trackStyle?: React.CSSProperties;
|
||||
rangeStyle?: React.CSSProperties;
|
||||
thumbStyle?: React.CSSProperties;
|
||||
noteStyle?: React.CSSProperties;
|
||||
nodes?: number[]; // The values where the nodes should be placed
|
||||
}
|
||||
|
||||
export const Slider = ({
|
||||
value,
|
||||
min = 0,
|
||||
max = 10,
|
||||
step,
|
||||
width = 250,
|
||||
nodes,
|
||||
containerStyle,
|
||||
rootStyle,
|
||||
trackStyle,
|
||||
rangeStyle,
|
||||
thumbStyle,
|
||||
noteStyle,
|
||||
...props
|
||||
}: SliderProps) => {
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<div style={{ ...containerStyle, width: width ? `${width}px` : undefined }}>
|
||||
<Sliders.Root
|
||||
value={value}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
style={rootStyle}
|
||||
{...props}
|
||||
>
|
||||
<Sliders.Track className={styles.trackStyle} ref={sliderRef}>
|
||||
<div className={styles.fakeTrackStyle} style={trackStyle}>
|
||||
<Sliders.Range
|
||||
className={styles.filledTrackStyle}
|
||||
style={rangeStyle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!!nodes &&
|
||||
nodes.map((nodeValue, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={styles.nodeStyle}
|
||||
data-active={value && value[0] >= nodeValue}
|
||||
style={{
|
||||
left: `${((nodeValue - (min !== undefined ? min : 0)) / (max !== undefined ? max - (min !== undefined ? min : 0) : 1)) * 100}%`,
|
||||
transform:
|
||||
index === 0
|
||||
? 'translateY(-50%)'
|
||||
: index === nodes.length - 1
|
||||
? 'translateY(-50%) translateX(-100%)'
|
||||
: undefined,
|
||||
...noteStyle,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<Sliders.Thumb className={styles.thumbStyle} style={thumbStyle} />
|
||||
</Sliders.Track>
|
||||
</Sliders.Root>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@affine/component/setting-components';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { AppSetting } from '@toeverything/infra';
|
||||
import { fontStyleOptions, windowFrameStyleOptions } from '@toeverything/infra';
|
||||
import { windowFrameStyleOptions } from '@toeverything/infra';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
@@ -58,45 +58,6 @@ export const ThemeSettings = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const FontFamilySettings = () => {
|
||||
const t = useI18n();
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
|
||||
const radioItems = useMemo(() => {
|
||||
return fontStyleOptions.map(({ key, value }) => {
|
||||
const label =
|
||||
key === 'Mono'
|
||||
? t[`com.affine.appearanceSettings.fontStyle.mono`]()
|
||||
: key === 'Sans'
|
||||
? t['com.affine.appearanceSettings.fontStyle.sans']()
|
||||
: key === 'Serif'
|
||||
? t['com.affine.appearanceSettings.fontStyle.serif']()
|
||||
: '';
|
||||
return {
|
||||
value: key,
|
||||
label,
|
||||
testId: 'system-font-style-trigger',
|
||||
style: { fontFamily: value },
|
||||
} satisfies RadioItem;
|
||||
});
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<RadioGroup
|
||||
items={radioItems}
|
||||
value={appSettings.fontStyle}
|
||||
width={250}
|
||||
className={settingWrapper}
|
||||
onChange={useCallback(
|
||||
(value: AppSetting['fontStyle']) => {
|
||||
updateSettings('fontStyle', value);
|
||||
},
|
||||
[updateSettings]
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const AppearanceSettings = () => {
|
||||
const t = useI18n();
|
||||
|
||||
@@ -116,12 +77,6 @@ export const AppearanceSettings = () => {
|
||||
>
|
||||
<ThemeSettings />
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.appearanceSettings.font.title']()}
|
||||
desc={t['com.affine.appearanceSettings.font.description']()}
|
||||
>
|
||||
<FontFamilySettings />
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.appearanceSettings.language.title']()}
|
||||
desc={t['com.affine.appearanceSettings.language.description']()}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { SettingWrapper } from '@affine/component/setting-components';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
|
||||
import { ConnecterSettings } from './connecter';
|
||||
import { MindMapSettings } from './mind-map';
|
||||
import { NoteSettings } from './note';
|
||||
import { PenSettings } from './pen';
|
||||
import { ShapeSettings } from './shape';
|
||||
import { TextSettings } from './text';
|
||||
|
||||
export const Edgeless = () => {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<SettingWrapper title={t['com.affine.settings.editorSettings.edgeless']()}>
|
||||
<NoteSettings />
|
||||
<TextSettings />
|
||||
<ShapeSettings />
|
||||
<ConnecterSettings />
|
||||
<PenSettings />
|
||||
<MindMapSettings />
|
||||
</SettingWrapper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './edgeless';
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuTrigger,
|
||||
RadioGroup,
|
||||
type RadioItem,
|
||||
} 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 MindMapSettings = () => {
|
||||
const t = useI18n();
|
||||
const [layoutValue, setLayoutValue] = useState<'left' | 'radial' | 'right'>(
|
||||
'right'
|
||||
);
|
||||
const layoutValueItems = useMemo<RadioItem[]>(
|
||||
() => [
|
||||
{
|
||||
value: 'left',
|
||||
label:
|
||||
t[
|
||||
'com.affine.settings.editorSettings.edgeless.mind-map.layout.left'
|
||||
](),
|
||||
},
|
||||
{
|
||||
value: 'radial',
|
||||
label:
|
||||
t[
|
||||
'com.affine.settings.editorSettings.edgeless.mind-map.layout.radial'
|
||||
](),
|
||||
},
|
||||
{
|
||||
value: 'right',
|
||||
label:
|
||||
t[
|
||||
'com.affine.settings.editorSettings.edgeless.mind-map.layout.right'
|
||||
](),
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<EdgelessSnapshot
|
||||
title={t['com.affine.settings.editorSettings.edgeless.mind-map']()}
|
||||
option={['mock-option']}
|
||||
type="mock-type"
|
||||
/>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.style']()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Style 1</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Style 1
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.mind-map.layout'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<RadioGroup
|
||||
items={layoutValueItems}
|
||||
value={layoutValue}
|
||||
width={250}
|
||||
className={settingWrapper}
|
||||
onChange={setLayoutValue}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
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 NoteSettings = () => {
|
||||
const t = useI18n();
|
||||
const [borderStyle, setBorderStyle] = useState<'solid' | 'dash' | 'none'>(
|
||||
'solid'
|
||||
);
|
||||
const [borderThickness, setBorderThickness] = useState<number[]>([3]);
|
||||
|
||||
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'](),
|
||||
},
|
||||
{
|
||||
value: 'none',
|
||||
label:
|
||||
t['com.affine.settings.editorSettings.edgeless.note.border.none'](),
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<EdgelessSnapshot
|
||||
title={t['com.affine.settings.editorSettings.edgeless.note']()}
|
||||
option={['mock-option']}
|
||||
type="mock-type"
|
||||
/>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.note.background'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Yellow</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Yellow
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.note.corners']()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Small</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Small
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.note.shadow']()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Box shadow</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Box shadow
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.note.border']()}
|
||||
desc={''}
|
||||
>
|
||||
<RadioGroup
|
||||
items={borderStyleItems}
|
||||
value={borderStyle}
|
||||
width={250}
|
||||
className={settingWrapper}
|
||||
onChange={setBorderStyle}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.note.border-thickness'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<Slider
|
||||
value={borderThickness}
|
||||
onValueChange={setBorderThickness}
|
||||
min={1}
|
||||
max={5}
|
||||
step={1}
|
||||
nodes={[1, 2, 3, 4, 5]}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Menu, MenuItem, MenuTrigger, Slider } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { menuTrigger } from '../style.css';
|
||||
import { EdgelessSnapshot } from './snapshot';
|
||||
|
||||
export const PenSettings = () => {
|
||||
const t = useI18n();
|
||||
const [borderThickness, setBorderThickness] = useState<number[]>([3]);
|
||||
return (
|
||||
<>
|
||||
<EdgelessSnapshot
|
||||
title={t['com.affine.settings.editorSettings.edgeless.pen']()}
|
||||
option={['mock-option']}
|
||||
type="mock-type"
|
||||
/>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.pen.color']()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Yellow</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Yellow
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.pen.thickness']()}
|
||||
desc={''}
|
||||
>
|
||||
<Slider
|
||||
value={borderThickness}
|
||||
onValueChange={setBorderThickness}
|
||||
min={1}
|
||||
max={5}
|
||||
step={1}
|
||||
nodes={[1, 2, 3, 4, 5]}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,276 @@
|
||||
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, shapeIndicator } from '../style.css';
|
||||
import { EdgelessSnapshot } from './snapshot';
|
||||
|
||||
type Shape =
|
||||
| 'square'
|
||||
| 'ellipse'
|
||||
| 'diamond'
|
||||
| 'triangle'
|
||||
| 'rounded-rectangle';
|
||||
|
||||
export const ShapeSettings = () => {
|
||||
const t = useI18n();
|
||||
const [currentShape, setCurrentShape] = useState<Shape>('square');
|
||||
const [shapeStyle, setShapeStyle] = useState<'general' | 'scribbled'>(
|
||||
'general'
|
||||
);
|
||||
const [borderStyle, setBorderStyle] = useState<'solid' | 'dash' | 'none'>(
|
||||
'solid'
|
||||
);
|
||||
const [borderThickness, setBorderThickness] = useState<number[]>([3]);
|
||||
const [textAlignment, setTextAlignment] = useState<
|
||||
'left' | 'center' | 'right'
|
||||
>('left');
|
||||
|
||||
const shapeStyleItems = 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 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'](),
|
||||
},
|
||||
{
|
||||
value: 'none',
|
||||
label:
|
||||
t['com.affine.settings.editorSettings.edgeless.note.border.none'](),
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
const alignItems = useMemo<RadioItem[]>(
|
||||
() => [
|
||||
{
|
||||
value: 'left',
|
||||
label:
|
||||
t[
|
||||
'com.affine.settings.editorSettings.edgeless.text.alignment.left'
|
||||
](),
|
||||
},
|
||||
{
|
||||
value: 'center',
|
||||
label:
|
||||
t[
|
||||
'com.affine.settings.editorSettings.edgeless.text.alignment.center'
|
||||
](),
|
||||
},
|
||||
{
|
||||
value: 'right',
|
||||
label:
|
||||
t[
|
||||
'com.affine.settings.editorSettings.edgeless.text.alignment.right'
|
||||
](),
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
const shapes = useMemo<RadioItem[]>(
|
||||
() => [
|
||||
{
|
||||
value: 'square',
|
||||
label: t['com.affine.settings.editorSettings.edgeless.shape.square'](),
|
||||
},
|
||||
{
|
||||
value: 'ellipse',
|
||||
label: t['com.affine.settings.editorSettings.edgeless.shape.ellipse'](),
|
||||
},
|
||||
{
|
||||
value: 'diamond',
|
||||
label: t['com.affine.settings.editorSettings.edgeless.shape.diamond'](),
|
||||
},
|
||||
{
|
||||
value: 'triangle',
|
||||
label:
|
||||
t['com.affine.settings.editorSettings.edgeless.shape.triangle'](),
|
||||
},
|
||||
{
|
||||
value: 'rounded-rectangle',
|
||||
label:
|
||||
t[
|
||||
'com.affine.settings.editorSettings.edgeless.shape.rounded-rectangle'
|
||||
](),
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EdgelessSnapshot
|
||||
title={t['com.affine.settings.editorSettings.edgeless.shape']()}
|
||||
option={['mock-option']}
|
||||
type="mock-type"
|
||||
/>
|
||||
|
||||
<RadioGroup
|
||||
padding={0}
|
||||
gap={4}
|
||||
itemHeight={28}
|
||||
borderRadius={8}
|
||||
value={currentShape}
|
||||
items={shapes}
|
||||
onChange={setCurrentShape}
|
||||
style={{ background: 'transparent', marginBottom: '16px' }}
|
||||
indicatorClassName={shapeIndicator}
|
||||
/>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.style']()}
|
||||
desc={''}
|
||||
>
|
||||
<RadioGroup
|
||||
items={shapeStyleItems}
|
||||
value={shapeStyle}
|
||||
width={250}
|
||||
className={settingWrapper}
|
||||
onChange={setShapeStyle}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.shape.fill-color'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Yellow</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Yellow
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.shape.border-color'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Yellow</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Yellow
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.shape.border-style'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<RadioGroup
|
||||
items={borderStyleItems}
|
||||
value={borderStyle}
|
||||
width={250}
|
||||
className={settingWrapper}
|
||||
onChange={setBorderStyle}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.shape.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.shape.text-color'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Yellow</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Yellow
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.shape.font']()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Inter</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Inter
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.shape.font-size'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>15px</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
15px
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.shape.font-style'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Regular</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Regular
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.shape.text-alignment'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<RadioGroup
|
||||
items={alignItems}
|
||||
value={textAlignment}
|
||||
width={250}
|
||||
className={settingWrapper}
|
||||
onChange={setTextAlignment}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { snapshot, snapshotContainer, snapshotTitle } from '../style.css';
|
||||
|
||||
export const EdgelessSnapshot = ({
|
||||
title,
|
||||
option,
|
||||
type,
|
||||
}: {
|
||||
title: string;
|
||||
option: string[];
|
||||
type: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className={snapshotContainer}>
|
||||
<div className={snapshotTitle}>{title}</div>
|
||||
<div
|
||||
className={snapshot}
|
||||
data-editor-option={option}
|
||||
data-editor-type={type}
|
||||
>
|
||||
Mock Preview
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuTrigger,
|
||||
RadioGroup,
|
||||
type RadioItem,
|
||||
} 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 TextSettings = () => {
|
||||
const t = useI18n();
|
||||
|
||||
const [textAlignment, setTextAlignment] = useState<
|
||||
'left' | 'center' | 'right'
|
||||
>('left');
|
||||
|
||||
const alignItems = useMemo<RadioItem[]>(
|
||||
() => [
|
||||
{
|
||||
value: 'left',
|
||||
label:
|
||||
t[
|
||||
'com.affine.settings.editorSettings.edgeless.text.alignment.left'
|
||||
](),
|
||||
},
|
||||
{
|
||||
value: 'center',
|
||||
label:
|
||||
t[
|
||||
'com.affine.settings.editorSettings.edgeless.text.alignment.center'
|
||||
](),
|
||||
},
|
||||
{
|
||||
value: 'right',
|
||||
label:
|
||||
t[
|
||||
'com.affine.settings.editorSettings.edgeless.text.alignment.right'
|
||||
](),
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EdgelessSnapshot
|
||||
title={t['com.affine.settings.editorSettings.edgeless.text']()}
|
||||
option={['mock-option']}
|
||||
type="mock-type"
|
||||
/>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.text.color']()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Blue</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Blue
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.text.font']()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Inter</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Inter
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.text.font-size']()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>15px</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
15px
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.edgeless.text.font-weight'
|
||||
]()}
|
||||
desc={''}
|
||||
>
|
||||
<Menu items={<MenuItem>Regular</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Regular
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.edgeless.text.alignment']()}
|
||||
desc={''}
|
||||
>
|
||||
<RadioGroup
|
||||
items={alignItems}
|
||||
value={textAlignment}
|
||||
width={250}
|
||||
className={settingWrapper}
|
||||
onChange={setTextAlignment}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,333 @@
|
||||
import {
|
||||
Loading,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuSeparator,
|
||||
MenuTrigger,
|
||||
RadioGroup,
|
||||
type RadioItem,
|
||||
Scrollable,
|
||||
Switch,
|
||||
} from '@affine/component';
|
||||
import {
|
||||
SettingRow,
|
||||
SettingWrapper,
|
||||
} from '@affine/component/setting-components';
|
||||
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
|
||||
import { SystemFontFamilyService } from '@affine/core/modules/system-font-family/services/system-font-family';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import {
|
||||
type AppSetting,
|
||||
type DocMode,
|
||||
type FontFamily,
|
||||
fontStyleOptions,
|
||||
useLiveData,
|
||||
useService,
|
||||
} from '@toeverything/infra';
|
||||
import {
|
||||
type ChangeEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { menu, menuTrigger, searchInput, settingWrapper } from './style.css';
|
||||
|
||||
const FontFamilySettings = () => {
|
||||
const t = useI18n();
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
const getLabel = useCallback(
|
||||
(fontKey: FontFamily) => {
|
||||
switch (fontKey) {
|
||||
case 'Sans':
|
||||
return t['com.affine.appearanceSettings.fontStyle.sans']();
|
||||
case 'Serif':
|
||||
return t['com.affine.appearanceSettings.fontStyle.serif']();
|
||||
case 'Mono':
|
||||
return t[`com.affine.appearanceSettings.fontStyle.mono`]();
|
||||
case 'Custom':
|
||||
return t['com.affine.settings.editorSettings.edgeless.custom']();
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
},
|
||||
[t]
|
||||
);
|
||||
|
||||
const radioItems = useMemo(() => {
|
||||
return fontStyleOptions
|
||||
.map(({ key, value }) => {
|
||||
if (key === 'Custom' && !environment.isDesktop) {
|
||||
return null;
|
||||
}
|
||||
const label = getLabel(key);
|
||||
let fontFamily = value;
|
||||
if (key === 'Custom' && appSettings.customFontFamily) {
|
||||
fontFamily = `${appSettings.customFontFamily}, ${value}`;
|
||||
}
|
||||
return {
|
||||
value: key,
|
||||
label,
|
||||
testId: 'system-font-style-trigger',
|
||||
style: {
|
||||
fontFamily,
|
||||
},
|
||||
} satisfies RadioItem;
|
||||
})
|
||||
.filter(item => item !== null);
|
||||
}, [appSettings.customFontFamily, getLabel]);
|
||||
|
||||
return (
|
||||
<RadioGroup
|
||||
items={radioItems}
|
||||
value={appSettings.fontStyle}
|
||||
width={250}
|
||||
className={settingWrapper}
|
||||
onChange={useCallback(
|
||||
(value: AppSetting['fontStyle']) => {
|
||||
updateSettings('fontStyle', value);
|
||||
},
|
||||
[updateSettings]
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getFontFamily = (font: string) => `${font}, ${fontStyleOptions[0].value}`;
|
||||
|
||||
const FontMenuItems = ({ onSelect }: { onSelect: (font: string) => void }) => {
|
||||
const systemFontFamily = useService(SystemFontFamilyService).systemFontFamily;
|
||||
useEffect(() => {
|
||||
systemFontFamily.loadFontList();
|
||||
systemFontFamily.clearSearch();
|
||||
}, [systemFontFamily]);
|
||||
|
||||
const isLoading = useLiveData(systemFontFamily.isLoading$);
|
||||
const result = useLiveData(systemFontFamily.result$);
|
||||
const searchText = useLiveData(systemFontFamily.searchText$);
|
||||
|
||||
const onInputChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
systemFontFamily.search(e.target.value);
|
||||
},
|
||||
[systemFontFamily]
|
||||
);
|
||||
const onInputKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation(); // avoid typeahead search built-in in the menu
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
value={searchText ?? ''}
|
||||
onChange={onInputChange}
|
||||
onKeyDown={onInputKeyDown}
|
||||
autoFocus
|
||||
className={searchInput}
|
||||
placeholder="Type here ..."
|
||||
/>
|
||||
<MenuSeparator />
|
||||
{isLoading ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<Scrollable.Root style={{ height: '200px' }}>
|
||||
<Scrollable.Viewport>
|
||||
{result.length > 0 ? (
|
||||
result.map(font => (
|
||||
<FontMenuItem key={font} font={font} onSelect={onSelect} />
|
||||
))
|
||||
) : (
|
||||
<div>not found</div>
|
||||
)}
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar />
|
||||
</Scrollable.Root>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FontMenuItem = ({
|
||||
font,
|
||||
onSelect,
|
||||
}: {
|
||||
font: string;
|
||||
onSelect: (font: string) => void;
|
||||
}) => {
|
||||
const handleFontSelect = useCallback(() => onSelect(font), [font, onSelect]);
|
||||
const fontFamily = getFontFamily(font);
|
||||
return (
|
||||
<MenuItem key={font} onSelect={handleFontSelect} style={{ fontFamily }}>
|
||||
{font}
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomFontFamilySettings = () => {
|
||||
const t = useI18n();
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
const fontFamily = getFontFamily(appSettings.customFontFamily);
|
||||
const onCustomFontFamilyChange = useCallback(
|
||||
(fontFamily: string) => {
|
||||
updateSettings('customFontFamily', fontFamily);
|
||||
},
|
||||
[updateSettings]
|
||||
);
|
||||
if (appSettings.fontStyle !== 'Custom' || !environment.isDesktop) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.general.font-family.custom.title'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.general.font-family.custom.description'
|
||||
]()}
|
||||
>
|
||||
<Menu
|
||||
items={<FontMenuItems onSelect={onCustomFontFamilyChange} />}
|
||||
contentOptions={{
|
||||
align: 'end',
|
||||
}}
|
||||
>
|
||||
<MenuTrigger className={menuTrigger} style={{ fontFamily }}>
|
||||
{appSettings.customFontFamily || 'Select a font'}
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
);
|
||||
};
|
||||
const NewDocDefaultModeSettings = () => {
|
||||
const t = useI18n();
|
||||
const [value, setValue] = useState<DocMode>('page');
|
||||
const radioItems = useMemo<RadioItem[]>(
|
||||
() => [
|
||||
{
|
||||
value: 'page',
|
||||
label: t['Page'](),
|
||||
testId: 'page-mode-trigger',
|
||||
},
|
||||
{
|
||||
value: 'edgeless',
|
||||
label: t['Edgeless'](),
|
||||
testId: 'edgeless-mode-trigger',
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
return (
|
||||
<RadioGroup
|
||||
items={radioItems}
|
||||
value={value}
|
||||
width={250}
|
||||
className={settingWrapper}
|
||||
onChange={setValue}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const General = () => {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<SettingWrapper title={t['com.affine.settings.editorSettings.general']()}>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.general.ai.title']()}
|
||||
desc={t['com.affine.settings.editorSettings.general.ai.description']()}
|
||||
>
|
||||
<Switch />
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.appearanceSettings.font.title']()}
|
||||
desc={t['com.affine.appearanceSettings.font.description']()}
|
||||
>
|
||||
<FontFamilySettings />
|
||||
</SettingRow>
|
||||
<CustomFontFamilySettings />
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.general.font-family.title'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.general.font-family.description'
|
||||
]()}
|
||||
>
|
||||
<Menu items={<MenuItem>inter</MenuItem>}>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
inter
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.general.font-size.title']()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.general.font-size.description'
|
||||
]()}
|
||||
>
|
||||
<Menu
|
||||
contentOptions={{
|
||||
className: menu,
|
||||
}}
|
||||
items={<MenuItem>15</MenuItem>}
|
||||
>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
15
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.general.default-new-doc.title'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.general.default-new-doc.description'
|
||||
]()}
|
||||
>
|
||||
<NewDocDefaultModeSettings />
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.general.default-code-block.language.title'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.general.default-code-block.language.description'
|
||||
]()}
|
||||
>
|
||||
<Menu
|
||||
contentOptions={{
|
||||
className: menu,
|
||||
}}
|
||||
items={<MenuItem>Plain Text</MenuItem>}
|
||||
>
|
||||
<MenuTrigger className={menuTrigger} disabled>
|
||||
Plain Text
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.general.default-code-block.wrap.title'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.general.default-code-block.wrap.description'
|
||||
]()}
|
||||
>
|
||||
<Switch />
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.general.spell-check.title'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.general.spell-check.description'
|
||||
]()}
|
||||
>
|
||||
<Switch />
|
||||
</SettingRow>
|
||||
</SettingWrapper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { SettingHeader } from '@affine/component/setting-components';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
|
||||
import { Edgeless } from './edgeless';
|
||||
import { General } from './general';
|
||||
import { Page } from './page';
|
||||
import { Preferences } from './preferences';
|
||||
|
||||
export const EditorSettings = () => {
|
||||
const t = useI18n();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingHeader
|
||||
title={t['com.affine.settings.editorSettings.title']()}
|
||||
subtitle={t['com.affine.settings.editorSettings.subtitle']()}
|
||||
/>
|
||||
<General />
|
||||
<Page />
|
||||
<Edgeless />
|
||||
<Preferences />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Switch } from '@affine/component';
|
||||
import {
|
||||
SettingRow,
|
||||
SettingWrapper,
|
||||
} from '@affine/component/setting-components';
|
||||
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
|
||||
export const Page = () => {
|
||||
const t = useI18n();
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
return (
|
||||
<SettingWrapper title={t['com.affine.settings.editorSettings.page']()}>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.editorSettings.page.full-width.title']()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.page.full-width.description'
|
||||
]()}
|
||||
>
|
||||
<Switch
|
||||
data-testid="full-width-layout-trigger"
|
||||
checked={appSettings.fullWidthLayout}
|
||||
onChange={checked => updateSettings('fullWidthLayout', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.page.display-doc-info.title'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.page.display-doc-info.description'
|
||||
]()}
|
||||
>
|
||||
<Switch />
|
||||
</SettingRow>
|
||||
</SettingWrapper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Button } from '@affine/component';
|
||||
import {
|
||||
SettingRow,
|
||||
SettingWrapper,
|
||||
} from '@affine/component/setting-components';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
|
||||
export const Preferences = () => {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<SettingWrapper
|
||||
title={t['com.affine.settings.editorSettings.preferences']()}
|
||||
>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.preferences.export.title'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.preferences.export.description'
|
||||
]()}
|
||||
>
|
||||
<Button>Export</Button>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.editorSettings.preferences.import.title'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.settings.editorSettings.preferences.import.description'
|
||||
]()}
|
||||
>
|
||||
<Button>Import</Button>
|
||||
</SettingRow>
|
||||
</SettingWrapper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
export const settingWrapper = style({
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
minWidth: '150px',
|
||||
maxWidth: '250px',
|
||||
});
|
||||
|
||||
export const menu = style({
|
||||
background: 'white',
|
||||
width: '250px',
|
||||
maxHeight: '30vh',
|
||||
overflowY: 'auto',
|
||||
});
|
||||
|
||||
export const menuTrigger = style({
|
||||
textTransform: 'capitalize',
|
||||
fontWeight: 600,
|
||||
width: '250px',
|
||||
});
|
||||
|
||||
export const snapshotContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginBottom: '24px',
|
||||
});
|
||||
|
||||
export const snapshotTitle = style({
|
||||
marginBottom: '8px',
|
||||
fontSize: cssVar('fontSm'),
|
||||
fontWeight: 500,
|
||||
color: cssVarV2('text/secondary'),
|
||||
});
|
||||
|
||||
export const snapshot = style({
|
||||
width: '100%',
|
||||
height: '180px',
|
||||
border: `1px solid ${cssVarV2('layer/insideBorder/border')}`,
|
||||
borderRadius: '4px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const shapeIndicator = style({
|
||||
boxShadow: 'none',
|
||||
backgroundColor: cssVarV2('layer/background/tertiary'),
|
||||
});
|
||||
|
||||
export const searchInput = style({
|
||||
flexGrow: 1,
|
||||
padding: '10px 0',
|
||||
margin: '-10px 0',
|
||||
border: 'none',
|
||||
outline: 'none',
|
||||
fontSize: cssVar('fontSm'),
|
||||
fontFamily: 'inherit',
|
||||
color: 'inherit',
|
||||
backgroundColor: 'transparent',
|
||||
'::placeholder': {
|
||||
color: cssVarV2('text/placeholder'),
|
||||
},
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { UserFeatureService } from '@affine/core/modules/cloud/services/user-fea
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import {
|
||||
AppearanceIcon,
|
||||
BlocksuiteIcon,
|
||||
ExperimentIcon,
|
||||
InformationIcon,
|
||||
KeyboardIcon,
|
||||
@@ -15,6 +16,7 @@ import type { GeneralSettingKey } from '../types';
|
||||
import { AboutAffine } from './about';
|
||||
import { AppearanceSettings } from './appearance';
|
||||
import { BillingSettings } from './billing';
|
||||
import { EditorSettings } from './editor';
|
||||
import { ExperimentalFeatures } from './experimental-features';
|
||||
import { PaymentIcon, UpgradeIcon } from './icons';
|
||||
import { AFFiNEPricingPlans } from './plans';
|
||||
@@ -65,6 +67,15 @@ export const useGeneralSettingList = (): GeneralSettingList => {
|
||||
testId: 'about-panel-trigger',
|
||||
},
|
||||
];
|
||||
if (runtimeConfig.enableEditorSettings) {
|
||||
// add editor settings to second position
|
||||
settings.splice(1, 0, {
|
||||
key: 'editor',
|
||||
title: t['com.affine.settings.editorSettings.title'](),
|
||||
icon: BlocksuiteIcon,
|
||||
testId: 'editor-panel-trigger',
|
||||
});
|
||||
}
|
||||
|
||||
if (hasPaymentFeature) {
|
||||
settings.splice(3, 0, {
|
||||
@@ -103,6 +114,8 @@ export const GeneralSetting = ({ generalKey }: GeneralSettingProps) => {
|
||||
switch (generalKey) {
|
||||
case 'shortcuts':
|
||||
return <Shortcuts />;
|
||||
case 'editor':
|
||||
return <EditorSettings />;
|
||||
case 'appearance':
|
||||
return <AppearanceSettings />;
|
||||
case 'about':
|
||||
|
||||
@@ -5,6 +5,7 @@ export const GeneralSettingKeys = [
|
||||
'plans',
|
||||
'billing',
|
||||
'experimental-features',
|
||||
'editor',
|
||||
] as const;
|
||||
|
||||
export const WorkspaceSubTabs = ['preference', 'properties'] as const;
|
||||
|
||||
@@ -57,8 +57,13 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
|
||||
option => option.key === appSettings.fontStyle
|
||||
);
|
||||
assertExists(fontStyle);
|
||||
return fontStyle.value;
|
||||
}, [appSettings.fontStyle]);
|
||||
|
||||
const customFontFamily = appSettings.customFontFamily;
|
||||
|
||||
return customFontFamily && fontStyle.key === 'Custom'
|
||||
? `${customFontFamily}, ${fontStyle.value}`
|
||||
: fontStyle.value;
|
||||
}, [appSettings.customFontFamily, appSettings.fontStyle]);
|
||||
|
||||
const blockId = useRouterHash();
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { configurePermissionsModule } from './permissions';
|
||||
import { configureWorkspacePropertiesModule } from './properties';
|
||||
import { configureQuickSearchModule } from './quicksearch';
|
||||
import { configureShareDocsModule } from './share-doc';
|
||||
import { configureSystemFontFamilyModule } from './system-font-family';
|
||||
import { configureTagModule } from './tag';
|
||||
import { configureTelemetryModule } from './telemetry';
|
||||
import { configureThemeEditorModule } from './theme-editor';
|
||||
@@ -41,4 +42,5 @@ export function configureCommonModules(framework: Framework) {
|
||||
configureExplorerModule(framework);
|
||||
configureThemeEditorModule(framework);
|
||||
configureEditorModule(framework);
|
||||
configureSystemFontFamilyModule(framework);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { apis } from '@affine/electron-api';
|
||||
import {
|
||||
effect,
|
||||
Entity,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
mapInto,
|
||||
onComplete,
|
||||
onStart,
|
||||
} from '@toeverything/infra';
|
||||
import { exhaustMap } from 'rxjs';
|
||||
|
||||
export class SystemFontFamily extends Entity {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
readonly searchText$ = new LiveData<string | null>(null);
|
||||
readonly isLoading$ = new LiveData<boolean>(false);
|
||||
readonly fontList$ = new LiveData<string[]>([]);
|
||||
readonly result$ = LiveData.computed(get => {
|
||||
const fontList = get(this.fontList$);
|
||||
const searchText = get(this.searchText$);
|
||||
if (!searchText) {
|
||||
return fontList;
|
||||
}
|
||||
|
||||
const filteredFonts = fontList.filter(font =>
|
||||
font.toLowerCase().includes(searchText.toLowerCase())
|
||||
);
|
||||
return filteredFonts;
|
||||
}).throttleTime(500);
|
||||
|
||||
loadFontList = effect(
|
||||
exhaustMap(() => {
|
||||
return fromPromise(async () => {
|
||||
if (!apis?.fontList) {
|
||||
return [];
|
||||
}
|
||||
return apis.fontList.getSystemFonts();
|
||||
}).pipe(
|
||||
mapInto(this.fontList$),
|
||||
// TODO: catchErrorInto(this.error$),
|
||||
onStart(() => {
|
||||
this.isLoading$.next(true);
|
||||
}),
|
||||
onComplete(() => {
|
||||
this.isLoading$.next(false);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
search(searchText: string) {
|
||||
this.searchText$.next(searchText);
|
||||
}
|
||||
|
||||
clearSearch() {
|
||||
this.searchText$.next(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { SystemFontFamily } from './entities/system-font-family';
|
||||
import { SystemFontFamilyService } from './services/system-font-family';
|
||||
|
||||
export function configureSystemFontFamilyModule(framework: Framework) {
|
||||
framework.service(SystemFontFamilyService).entity(SystemFontFamily);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { SystemFontFamily } from '../entities/system-font-family';
|
||||
|
||||
export class SystemFontFamilyService extends Service {
|
||||
public readonly systemFontFamily =
|
||||
this.framework.createEntity(SystemFontFamily);
|
||||
}
|
||||
@@ -80,6 +80,7 @@
|
||||
"dependencies": {
|
||||
"async-call-rpc": "^6.4.2",
|
||||
"electron-updater": "^6.2.1",
|
||||
"font-list": "^1.5.1",
|
||||
"link-preview-js": "^3.0.5",
|
||||
"yjs": "patch:yjs@npm%3A13.6.18#~/.yarn/patches/yjs-npm-13.6.18-ad0d5f7c43.patch"
|
||||
},
|
||||
|
||||
15
packages/frontend/electron/src/main/font-list/handlers.ts
Normal file
15
packages/frontend/electron/src/main/font-list/handlers.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { getFonts } from 'font-list';
|
||||
|
||||
import type { NamespaceHandlers } from '../type';
|
||||
|
||||
export const fontListHandlers = {
|
||||
getSystemFonts: async () => {
|
||||
try {
|
||||
const fonts = await getFonts();
|
||||
return fonts;
|
||||
} catch (error) {
|
||||
console.error('Failed to get system fonts:', error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
} satisfies NamespaceHandlers;
|
||||
1
packages/frontend/electron/src/main/font-list/index.ts
Normal file
1
packages/frontend/electron/src/main/font-list/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './handlers';
|
||||
@@ -5,6 +5,7 @@ import { clipboardHandlers } from './clipboard';
|
||||
import { configStorageHandlers } from './config-storage';
|
||||
import { exportHandlers } from './export';
|
||||
import { findInPageHandlers } from './find-in-page';
|
||||
import { fontListHandlers } from './font-list';
|
||||
import { getLogFilePath, logger, revealLogFile } from './logger';
|
||||
import { sharedStorageHandlers } from './shared-storage';
|
||||
import { uiHandlers } from './ui/handlers';
|
||||
@@ -29,6 +30,7 @@ export const allHandlers = {
|
||||
configStorage: configStorageHandlers,
|
||||
findInPage: findInPageHandlers,
|
||||
sharedStorage: sharedStorageHandlers,
|
||||
fontList: fontListHandlers,
|
||||
};
|
||||
|
||||
export const registerHandlers = () => {
|
||||
|
||||
@@ -1216,6 +1216,94 @@
|
||||
"com.affine.settings.appearance.window-frame-description": "Customise appearance of Windows Client.",
|
||||
"com.affine.settings.auto-check-description": "If enabled, it will automatically check for new versions at regular intervals.",
|
||||
"com.affine.settings.auto-download-description": "If enabled, new versions will be automatically downloaded to the current device.",
|
||||
"com.affine.settings.editorSettings": "Editor",
|
||||
"com.affine.settings.editorSettings.title": "Editor Settings",
|
||||
"com.affine.settings.editorSettings.subtitle": "Configure your own editor.",
|
||||
"com.affine.settings.editorSettings.general": "General",
|
||||
"com.affine.settings.editorSettings.general.ai.title": "AFFiNE AI",
|
||||
"com.affine.settings.editorSettings.general.ai.description": "Enable the powerful AI assistant, AFFiNE AI.",
|
||||
"com.affine.settings.editorSettings.general.font-style.title": "Font style",
|
||||
"com.affine.settings.editorSettings.general.font-style.description": "Choose your editor’s font style.",
|
||||
"com.affine.settings.editorSettings.general.font-family.title": "Font family",
|
||||
"com.affine.settings.editorSettings.general.font-family.description": "Choose your editor’s font family.",
|
||||
"com.affine.settings.editorSettings.general.font-family.custom.title": "Custom font family",
|
||||
"com.affine.settings.editorSettings.general.font-family.custom.description": "Customize your text experience.",
|
||||
"com.affine.settings.editorSettings.general.font-size.title": "Font size",
|
||||
"com.affine.settings.editorSettings.general.font-size.description": "Choose your editor’s base font size.",
|
||||
"com.affine.settings.editorSettings.general.default-new-doc.title": "New doc default mode",
|
||||
"com.affine.settings.editorSettings.general.default-new-doc.description": "Default mode for new doc.",
|
||||
"com.affine.settings.editorSettings.general.default-code-block.language.title": "Code blocks default language",
|
||||
"com.affine.settings.editorSettings.general.default-code-block.language.description": "Set a default programming language.",
|
||||
"com.affine.settings.editorSettings.general.default-code-block.wrap.title": "Wrap code in code blocks",
|
||||
"com.affine.settings.editorSettings.general.default-code-block.wrap.description": "Encapsulate code snippets for better readability.",
|
||||
"com.affine.settings.editorSettings.general.spell-check.title": "Spell check",
|
||||
"com.affine.settings.editorSettings.general.spell-check.description": "Automatically detect and correct spelling errors.",
|
||||
"com.affine.settings.editorSettings.page": "Page",
|
||||
"com.affine.settings.editorSettings.page.full-width.title": "Full width layout",
|
||||
"com.affine.settings.editorSettings.page.full-width.description": "Maximise display of content within a page.",
|
||||
"com.affine.settings.editorSettings.page.display-doc-info.title": "Display doc info",
|
||||
"com.affine.settings.editorSettings.page.display-doc-info.description": "Display document information on the doc.",
|
||||
"com.affine.settings.editorSettings.edgeless": "Edgeless",
|
||||
"com.affine.settings.editorSettings.edgeless.style": "Style",
|
||||
"com.affine.settings.editorSettings.edgeless.style.general": "General",
|
||||
"com.affine.settings.editorSettings.edgeless.style.scribbled": "Scribbled",
|
||||
"com.affine.settings.editorSettings.edgeless.custom": "Custom",
|
||||
"com.affine.settings.editorSettings.edgeless.note": "Note",
|
||||
"com.affine.settings.editorSettings.edgeless.note.background": "Background",
|
||||
"com.affine.settings.editorSettings.edgeless.note.corners": "Corners",
|
||||
"com.affine.settings.editorSettings.edgeless.note.shadow": "Shadow style",
|
||||
"com.affine.settings.editorSettings.edgeless.note.border": "Border style",
|
||||
"com.affine.settings.editorSettings.edgeless.note.border.solid": "Solid",
|
||||
"com.affine.settings.editorSettings.edgeless.note.border.dash": "Dash",
|
||||
"com.affine.settings.editorSettings.edgeless.note.border.none": "None",
|
||||
"com.affine.settings.editorSettings.edgeless.note.border-thickness": "Border thickness",
|
||||
"com.affine.settings.editorSettings.edgeless.text": "Text",
|
||||
"com.affine.settings.editorSettings.edgeless.text.color": "Text color",
|
||||
"com.affine.settings.editorSettings.edgeless.text.font": "Font",
|
||||
"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.alignment": "Alignment",
|
||||
"com.affine.settings.editorSettings.edgeless.text.alignment.left": "Left",
|
||||
"com.affine.settings.editorSettings.edgeless.text.alignment.center": "Center",
|
||||
"com.affine.settings.editorSettings.edgeless.text.alignment.right": "Right",
|
||||
"com.affine.settings.editorSettings.edgeless.shape": "Shape",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.square": "Square",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.ellipse": "Ellipse",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.diamond": "Diamond",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.triangle": "Triangle",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.rounded-rectangle": "Rounded Rectangle",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.fill-color": "Fill color",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.border-color": "Border color",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.border-style": "Border style",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.border-thickness": "Border thickness",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.text-color": "Text color",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.font": "Font",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.font-size": "Font size",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.font-style": "Font style",
|
||||
"com.affine.settings.editorSettings.edgeless.shape.text-alignment": "Text alignment",
|
||||
"com.affine.settings.editorSettings.edgeless.connecter": "Connecter",
|
||||
"com.affine.settings.editorSettings.edgeless.connecter.color": "Color",
|
||||
"com.affine.settings.editorSettings.edgeless.connecter.connector-shape": "Connector Shape",
|
||||
"com.affine.settings.editorSettings.edgeless.connecter.connector-shape.elbowed": "Elbowed",
|
||||
"com.affine.settings.editorSettings.edgeless.connecter.connector-shape.curve": "Curve",
|
||||
"com.affine.settings.editorSettings.edgeless.connecter.connector-shape.straight": "Straight",
|
||||
"com.affine.settings.editorSettings.edgeless.connecter.border-style": "Border style",
|
||||
"com.affine.settings.editorSettings.edgeless.connecter.border-thickness": "Border thickness",
|
||||
"com.affine.settings.editorSettings.edgeless.connecter.start-endpoint": "Start endpoint",
|
||||
"com.affine.settings.editorSettings.edgeless.connecter.end-endpoint": "End endpoint",
|
||||
"com.affine.settings.editorSettings.edgeless.pen": "Pen",
|
||||
"com.affine.settings.editorSettings.edgeless.pen.color": "Color",
|
||||
"com.affine.settings.editorSettings.edgeless.pen.thickness": "Thickness",
|
||||
"com.affine.settings.editorSettings.edgeless.mind-map": "Mind Map",
|
||||
"com.affine.settings.editorSettings.edgeless.mind-map.layout": "Layout",
|
||||
"com.affine.settings.editorSettings.edgeless.mind-map.layout.left": "Left",
|
||||
"com.affine.settings.editorSettings.edgeless.mind-map.layout.radial": "Radial",
|
||||
"com.affine.settings.editorSettings.edgeless.mind-map.layout.right": "Right",
|
||||
"com.affine.settings.editorSettings.preferences": "Preferences",
|
||||
"com.affine.settings.editorSettings.preferences.export.title": "Export Settings",
|
||||
"com.affine.settings.editorSettings.preferences.export.description": "You can export the entire preferences data for backup, and the exported data can be re-imported.",
|
||||
"com.affine.settings.editorSettings.preferences.import.title": "Import Settings",
|
||||
"com.affine.settings.editorSettings.preferences.import.description": "You can import previously exported preferences data for restoration.",
|
||||
"com.affine.settings.email": "Email",
|
||||
"com.affine.settings.email.action": "Change email",
|
||||
"com.affine.settings.email.action.change": "Change email",
|
||||
|
||||
@@ -28,6 +28,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
enableNewSettingUnstableApi: false,
|
||||
enableEnhanceShareMode: false,
|
||||
enableThemeEditor: false,
|
||||
enableEditorSettings: false,
|
||||
};
|
||||
},
|
||||
get beta() {
|
||||
@@ -56,6 +57,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
enableInfoModal: true,
|
||||
enableOrganize: true,
|
||||
enableThemeEditor: true,
|
||||
enableEditorSettings: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -310,6 +310,7 @@ __metadata:
|
||||
"@radix-ui/react-popover": "npm:^1.0.7"
|
||||
"@radix-ui/react-radio-group": "npm:^1.1.3"
|
||||
"@radix-ui/react-scroll-area": "npm:^1.0.5"
|
||||
"@radix-ui/react-slider": "npm:^1.2.0"
|
||||
"@radix-ui/react-tabs": "npm:^1.1.0"
|
||||
"@radix-ui/react-toast": "npm:^1.1.5"
|
||||
"@radix-ui/react-toolbar": "npm:^1.0.4"
|
||||
@@ -557,6 +558,7 @@ __metadata:
|
||||
electron-updater: "npm:^6.2.1"
|
||||
electron-window-state: "npm:^5.0.3"
|
||||
esbuild: "npm:^0.23.0"
|
||||
font-list: "npm:^1.5.1"
|
||||
fs-extra: "npm:^11.2.0"
|
||||
glob: "npm:^11.0.0"
|
||||
jotai: "npm:^2.8.0"
|
||||
@@ -21796,6 +21798,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"font-list@npm:^1.5.1":
|
||||
version: 1.5.1
|
||||
resolution: "font-list@npm:1.5.1"
|
||||
checksum: 10/e1500017b63e0a554348143f69d64d4f61dba8bd002456e161840e79454b67feea4bbd33b5fb1648f6f8d0415307616da14751f9289ceaabcbcd62414e0f3ff8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"foreground-child@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "foreground-child@npm:2.0.0"
|
||||
|
||||
Reference in New Issue
Block a user