akumatus
2024-09-05 15:24:36 +00:00
parent f4db4058f8
commit 9cbe416c2c
14 changed files with 939 additions and 143 deletions

View File

@@ -15,6 +15,7 @@ import {
PointStyle,
StrokeStyle,
} from '@blocksuite/blocks';
import type { Doc } from '@blocksuite/store';
import { useFramework, useLiveData } from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
@@ -23,6 +24,7 @@ import { menuTrigger, settingWrapper } from '../style.css';
import { useColor } from '../utils';
import { Point } from './point';
import { EdgelessSnapshot } from './snapshot';
import { getSurfaceBlock } from './utils';
enum ConnecterStyle {
General = 'general',
@@ -189,13 +191,18 @@ export const ConnectorSettings = () => {
});
}, [editorSetting, settings]);
const getElements = useCallback((doc: Doc) => {
const surface = getSurfaceBlock(doc);
return surface?.getElementsByType('connector') || [];
}, []);
return (
<>
<EdgelessSnapshot
title={t['com.affine.settings.editorSettings.edgeless.connecter']()}
docName="connector"
keyName="connector"
flavour="connector"
getElements={getElements}
/>
<SettingRow
name={t[

View File

@@ -0,0 +1,470 @@
{
"type": "page",
"meta": {
"id": "IWDwzafoLh",
"title": "BlockSuite Playground",
"createDate": 1725541560333,
"tags": []
},
"blocks": {
"type": "block",
"id": "DBwzSu70O4",
"flavour": "affine:page",
"version": 2,
"props": {
"title": {
"$blocksuite:internal:text$": true,
"delta": [
{
"insert": "BlockSuite Playground"
}
]
}
},
"children": [
{
"type": "block",
"id": "VsLifn6v6J",
"flavour": "affine:surface",
"version": 5,
"props": {
"elements": {
"03ESqmbu0V": {
"index": "aF",
"seed": 456881005,
"color": "--affine-palette-line-black",
"fillColor": "--affine-palette-shape-yellow",
"filled": true,
"fontFamily": "blocksuite:surface:Inter",
"fontSize": 12,
"maxWidth": false,
"padding": [10, 20],
"radius": 0.1,
"rotate": 0,
"roughness": 1.4,
"shadow": null,
"shapeStyle": "General",
"shapeType": "rect",
"strokeColor": "--affine-palette-line-yellow",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": {
"affine:surface:text": true,
"delta": [
{
"insert": "Start"
}
]
},
"textResizing": 1,
"xywh": "[252.60701568907035,-31.240866187007754,113.98566757331668,46.66666793823242]",
"type": "shape",
"id": "03ESqmbu0V"
},
"YQDvi-2y7O": {
"index": "aG",
"seed": 1257533881,
"color": "--affine-palette-line-black",
"fillColor": "--affine-palette-shape-yellow",
"filled": true,
"fontFamily": "blocksuite:surface:Inter",
"fontSize": 12,
"maxWidth": false,
"padding": [10, 20],
"radius": 0,
"rotate": 0,
"roughness": 1.4,
"shadow": null,
"shapeStyle": "General",
"shapeType": "rect",
"strokeColor": "--affine-palette-line-yellow",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": {
"affine:surface:text": true,
"delta": [
{
"insert": "Action"
}
]
},
"textResizing": 1,
"xywh": "[251.34653068910762,63.28169306573089,116.50663757324219,46.66666793823242]",
"type": "shape",
"id": "YQDvi-2y7O"
},
"d16Jq63Ef-": {
"index": "aH",
"seed": 1257533881,
"color": "--affine-palette-line-black",
"fillColor": "--affine-palette-shape-yellow",
"filled": true,
"fontFamily": "blocksuite:surface:Inter",
"fontSize": 12,
"maxWidth": false,
"padding": [10, 20],
"radius": 0,
"rotate": 0,
"roughness": 1.4,
"shadow": null,
"shapeStyle": "General",
"shapeType": "rect",
"strokeColor": "--affine-palette-line-yellow",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": {
"affine:surface:text": true,
"delta": [
{
"insert": "Action"
}
]
},
"textResizing": 1,
"xywh": "[480.71602985081984,63.28169306573089,116.50663757324219,46.66666793823242]",
"type": "shape",
"id": "d16Jq63Ef-"
},
"pFUd4r8HSb": {
"index": "aI",
"seed": 666256980,
"color": "--affine-palette-line-black",
"fillColor": "--affine-palette-shape-yellow",
"filled": true,
"fontFamily": "blocksuite:surface:Inter",
"fontSize": 12,
"maxWidth": false,
"padding": [10, 20],
"radius": 0,
"rotate": 0,
"roughness": 1.4,
"shadow": null,
"shapeStyle": "General",
"shapeType": "diamond",
"strokeColor": "--affine-palette-line-yellow",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": {
"affine:surface:text": true,
"delta": [
{
"insert": "Yes/No"
}
]
},
"textResizing": 1,
"xywh": "[259.6798856390588,158.60141743948986,99.83992767333984,100]",
"type": "shape",
"id": "pFUd4r8HSb"
},
"WC0DtV40eR": {
"index": "aJ",
"seed": 1257533881,
"color": "--affine-palette-line-black",
"fillColor": "--affine-palette-shape-yellow",
"filled": true,
"fontFamily": "blocksuite:surface:Inter",
"fontSize": 12,
"maxWidth": false,
"padding": [10, 20],
"radius": 0,
"rotate": 0,
"roughness": 1.4,
"shadow": null,
"shapeStyle": "General",
"shapeType": "rect",
"strokeColor": "--affine-palette-line-yellow",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": {
"affine:surface:text": true,
"delta": [
{
"insert": "Action"
}
]
},
"textResizing": 1,
"xywh": "[251.3465118415371,306.9211418132488,116.50663757324219,47]",
"type": "shape",
"id": "WC0DtV40eR"
},
"L8IRBW8JgC": {
"index": "aK",
"seed": 456881005,
"color": "--affine-palette-line-black",
"fillColor": "--affine-palette-shape-yellow",
"filled": true,
"fontFamily": "blocksuite:surface:Inter",
"fontSize": 12,
"maxWidth": false,
"padding": [10, 20],
"radius": 0.1,
"rotate": 0,
"roughness": 1.4,
"shadow": null,
"shapeStyle": "General",
"shapeType": "rect",
"strokeColor": "--affine-palette-line-yellow",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": {
"affine:surface:text": true,
"delta": [
{
"insert": "End"
}
]
},
"textResizing": 1,
"xywh": "[251.21161577561134,402.2408661870077,116.77642970509373,47]",
"type": "shape",
"id": "L8IRBW8JgC"
},
"jnVx3KzCLh": {
"index": "aR",
"seed": 914130882,
"color": "--affine-palette-line-black",
"fillColor": "--affine-palette-shape-yellow",
"filled": true,
"fontFamily": "blocksuite:surface:Inter",
"fontSize": 16,
"maxWidth": false,
"padding": [10, 20],
"radius": 0,
"rotate": 0,
"roughness": 1.4,
"shadow": null,
"shapeStyle": "General",
"shapeType": "triangle",
"strokeColor": "--affine-palette-line-yellow",
"strokeStyle": "solid",
"strokeWidth": 2,
"textResizing": 1,
"xywh": "[16.110706599375533,93.96971522611781,99.95313668402997,80.60737422459296]",
"type": "shape",
"id": "jnVx3KzCLh"
},
"hXY08VN1b_": {
"index": "aS",
"seed": 1037406953,
"color": "--affine-palette-line-black",
"fillColor": "--affine-palette-shape-yellow",
"filled": true,
"fontFamily": "blocksuite:surface:Inter",
"fontSize": 16,
"maxWidth": false,
"padding": [10, 20],
"radius": 0,
"rotate": 0,
"roughness": 1.4,
"shadow": null,
"shapeStyle": "General",
"shapeType": "ellipse",
"strokeColor": "--affine-palette-line-yellow",
"strokeStyle": "solid",
"strokeWidth": 2,
"textResizing": 1,
"xywh": "[16.110706599375547,226.48127342070092,97.3493072661882,97.90333942510793]",
"type": "shape",
"id": "hXY08VN1b_"
},
"-aHPKrKHll": {
"index": "aL",
"seed": 1201328314,
"frontEndpointStyle": "None",
"mode": 1,
"rearEndpointStyle": "Arrow",
"rough": false,
"roughness": 1.4,
"source": {
"id": "03ESqmbu0V",
"position": [0.5, 1]
},
"stroke": "--affine-palette-line-grey",
"strokeStyle": "solid",
"strokeWidth": 2,
"target": {
"id": "YQDvi-2y7O",
"position": [0.5, 0]
},
"type": "connector",
"id": "-aHPKrKHll"
},
"9nFcoL2dRX": {
"index": "aM",
"seed": 1062067961,
"frontEndpointStyle": "None",
"mode": 1,
"rearEndpointStyle": "Arrow",
"rough": false,
"roughness": 1.4,
"source": {
"id": "YQDvi-2y7O",
"position": [1, 0.5]
},
"stroke": "--affine-palette-line-grey",
"strokeStyle": "solid",
"strokeWidth": 2,
"target": {
"id": "d16Jq63Ef-",
"position": [0, 0.49997133993897247]
},
"type": "connector",
"id": "9nFcoL2dRX"
},
"j4pebF9wC1": {
"index": "aN",
"seed": 970063880,
"frontEndpointStyle": "Arrow",
"mode": 1,
"rearEndpointStyle": "None",
"rough": false,
"roughness": 1.4,
"source": {
"id": "d16Jq63Ef-",
"position": [0.5, 1]
},
"stroke": "--affine-palette-line-grey",
"strokeStyle": "solid",
"strokeWidth": 2,
"target": {
"id": "pFUd4r8HSb",
"position": [1, 0.5]
},
"type": "connector",
"id": "j4pebF9wC1"
},
"WUp1wz3qjl": {
"index": "aO",
"seed": 628182641,
"frontEndpointStyle": "None",
"mode": 1,
"rearEndpointStyle": "Arrow",
"rough": false,
"roughness": 1.4,
"source": {
"id": "YQDvi-2y7O",
"position": [0.5, 1]
},
"stroke": "--affine-palette-line-grey",
"strokeStyle": "solid",
"strokeWidth": 2,
"target": {
"id": "pFUd4r8HSb",
"position": [0.5, 0]
},
"type": "connector",
"id": "WUp1wz3qjl"
},
"LW-DOsZkzx": {
"index": "aP",
"seed": 1308365768,
"frontEndpointStyle": "None",
"mode": 1,
"rearEndpointStyle": "Arrow",
"rough": false,
"roughness": 1.4,
"source": {
"id": "pFUd4r8HSb",
"position": [0.5, 1]
},
"stroke": "--affine-palette-line-grey",
"strokeStyle": "solid",
"strokeWidth": 2,
"target": {
"id": "WC0DtV40eR",
"position": [0.5, 0]
},
"type": "connector",
"id": "LW-DOsZkzx"
},
"iFv5piS4UF": {
"index": "aQ",
"seed": 782147341,
"frontEndpointStyle": "None",
"mode": 1,
"rearEndpointStyle": "Arrow",
"rough": false,
"roughness": 1.4,
"source": {
"id": "WC0DtV40eR",
"position": [0.5, 1]
},
"stroke": "--affine-palette-line-grey",
"strokeStyle": "solid",
"strokeWidth": 2,
"target": {
"id": "L8IRBW8JgC",
"position": [0.5, 0]
},
"type": "connector",
"id": "iFv5piS4UF"
}
}
},
"children": [
{
"type": "block",
"id": "LHrmmlDo4Q",
"flavour": "affine:edgeless-text",
"version": 1,
"props": {
"xywh": "[30.80080405395168,20.987504771361614,71.2421875,56]",
"index": "aE",
"color": "--affine-palette-line-black",
"fontFamily": "blocksuite:surface:Inter",
"fontStyle": "normal",
"fontWeight": "400",
"textAlign": "left",
"scale": 1,
"rotate": 0,
"hasMaxWidth": false
},
"children": [
{
"type": "block",
"id": "qc2TtDkPRq",
"flavour": "affine:paragraph",
"version": 1,
"props": {
"type": "text",
"text": {
"$blocksuite:internal:text$": true,
"delta": [
{
"insert": "Others"
}
]
}
},
"children": []
}
]
},
{
"type": "block",
"id": "cmuL9JhfVH",
"flavour": "affine:frame",
"version": 1,
"props": {
"title": {
"$blocksuite:internal:text$": true,
"delta": [
{
"insert": "Frame 1"
}
]
},
"background": "--affine-palette-transparent",
"xywh": "[-131.27862548828125,-112.8125,880.65234375,645.11328125]",
"index": "aEV",
"childElementIds": {}
},
"children": []
}
]
}
]
}
}

View File

@@ -20,6 +20,7 @@ export type DocName =
| 'note'
| 'pen'
| 'shape'
| 'flow'
| 'text'
| 'connector'
| 'mindmap';
@@ -31,6 +32,7 @@ export async function getDocByName(name: DocName) {
note: (await import('./note.json')).default,
pen: (await import('./pen.json')).default,
shape: (await import('./shape.json')).default,
flow: (await import('./flow.json')).default,
text: (await import('./text.json')).default,
connector: (await import('./connector.json')).default,
mindmap: (await import('./mindmap.json')).default,

View File

@@ -6,11 +6,13 @@ import {
} from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { useI18n } from '@affine/i18n';
import { useMemo, useState } from 'react';
import type { Doc } from '@blocksuite/store';
import { useCallback, useMemo, useState } from 'react';
import { DropdownMenu } from '../menu';
import { menuTrigger, settingWrapper } from '../style.css';
import { EdgelessSnapshot } from './snapshot';
import { getSurfaceBlock } from './utils';
export const MindMapSettings = () => {
const t = useI18n();
@@ -43,13 +45,19 @@ export const MindMapSettings = () => {
],
[t]
);
const getElements = useCallback((doc: Doc) => {
const surface = getSurfaceBlock(doc);
return surface?.getElementsByType('mindmap') || [];
}, []);
return (
<>
<EdgelessSnapshot
title={t['com.affine.settings.editorSettings.edgeless.mind-map']()}
docName="mindmap"
keyName={'mindmap' as any}
flavour="mindmap"
getElements={getElements}
height={320}
/>
<SettingRow

View File

@@ -16,6 +16,7 @@ import {
NoteShadowMap,
StrokeStyle,
} from '@blocksuite/blocks';
import type { Doc } from '@blocksuite/store';
import { useFramework, useLiveData } from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
@@ -166,13 +167,17 @@ export const NoteSettings = () => {
return getColorFromMap(background, NoteBackgroundColorMap);
}, [getColorFromMap, settings]);
const getElements = useCallback((doc: Doc) => {
return doc.getBlocksByFlavour('affine:note') || [];
}, []);
return (
<>
<EdgelessSnapshot
title={t['com.affine.settings.editorSettings.edgeless.note']()}
docName="note"
keyName="affine:note"
flavour="affine:note"
getElements={getElements}
/>
<SettingRow
name={t[

View File

@@ -3,6 +3,7 @@ import { SettingRow } from '@affine/component/setting-components';
import { EditorSettingService } from '@affine/core/modules/editor-settting';
import { useI18n } from '@affine/i18n';
import { LineColor, LineColorMap } from '@blocksuite/blocks';
import type { Doc } from '@blocksuite/store';
import { useFramework, useLiveData } from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
@@ -11,6 +12,7 @@ import { menuTrigger } from '../style.css';
import { useColor } from '../utils';
import { Point } from './point';
import { EdgelessSnapshot } from './snapshot';
import { getSurfaceBlock } from './utils';
export const PenSettings = () => {
const t = useI18n();
@@ -52,13 +54,19 @@ export const PenSettings = () => {
},
[editorSetting]
);
const getElements = useCallback((doc: Doc) => {
const surface = getSurfaceBlock(doc);
return surface?.getElementsByType('brush') || [];
}, []);
return (
<>
<EdgelessSnapshot
title={t['com.affine.settings.editorSettings.edgeless.pen']()}
docName="pen"
keyName="brush"
flavour="brush"
getElements={getElements}
/>
<SettingRow
name={t['com.affine.settings.editorSettings.edgeless.pen.color']()}

View File

@@ -6,42 +6,68 @@ import {
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 { useMemo, useState } from 'react';
import type { EditorHost } from '@blocksuite/block-std';
import type {
EdgelessRootService,
ShapeElementModel,
ShapeName,
} from '@blocksuite/blocks';
import {
createEnumMap,
FontFamily,
FontFamilyMap,
FontStyle,
FontWeight,
FontWeightMap,
getShapeName,
LineColor,
LineColorMap,
ShapeFillColor,
ShapeStyle,
ShapeType,
StrokeStyle,
TextAlign,
} from '@blocksuite/blocks';
import type { Doc } from '@blocksuite/store';
import { useFramework, useLiveData } from '@toeverything/infra';
import { useCallback, useMemo, useState } from 'react';
import { DropdownMenu } from '../menu';
import { menuTrigger, settingWrapper, shapeIndicator } from '../style.css';
import { useColor } from '../utils';
import type { DocName } from './docs';
import { Point } from './point';
import { EdgelessSnapshot } from './snapshot';
import { getSurfaceBlock } from './utils';
type Shape =
| 'square'
| 'ellipse'
| 'diamond'
| 'triangle'
| 'rounded-rectangle';
enum ShapeTextFontSize {
'12px' = '12',
'20px' = '20',
'28px' = '28',
'36px' = '36',
}
const ShapeFillColorMap = createEnumMap(ShapeFillColor);
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 framework = useFramework();
const { editorSetting } = framework.get(EditorSettingService);
const settings = useLiveData(editorSetting.settings$);
const getColorFromMap = useColor();
const [currentShape, setCurrentShape] = useState<ShapeName>(ShapeType.Rect);
const shapeStyleItems = useMemo<RadioItem[]>(
() => [
{
value: 'general',
value: ShapeStyle.General,
label: t['com.affine.settings.editorSettings.edgeless.style.general'](),
},
{
value: 'scribbled',
value: ShapeStyle.Scribbled,
label:
t['com.affine.settings.editorSettings.edgeless.style.scribbled'](),
},
@@ -49,20 +75,30 @@ export const ShapeSettings = () => {
[t]
);
const { shapeStyle } = settings[`shape:${currentShape}`];
const setShapeStyle = useCallback(
(value: ShapeStyle) => {
editorSetting.set(`shape:${currentShape}`, {
shapeStyle: value,
});
},
[editorSetting, currentShape]
);
const borderStyleItems = useMemo<RadioItem[]>(
() => [
{
value: 'solid',
value: StrokeStyle.Solid,
label:
t['com.affine.settings.editorSettings.edgeless.note.border.solid'](),
},
{
value: 'dash',
value: StrokeStyle.Dash,
label:
t['com.affine.settings.editorSettings.edgeless.note.border.dash'](),
},
{
value: 'none',
value: StrokeStyle.None,
label:
t['com.affine.settings.editorSettings.edgeless.note.border.none'](),
},
@@ -70,24 +106,34 @@ export const ShapeSettings = () => {
[t]
);
const borderStyle = settings[`shape:${currentShape}`].strokeStyle;
const setBorderStyle = useCallback(
(value: StrokeStyle) => {
editorSetting.set(`shape:${currentShape}`, {
strokeStyle: value,
});
},
[editorSetting, currentShape]
);
const alignItems = useMemo<RadioItem[]>(
() => [
{
value: 'left',
value: TextAlign.Left,
label:
t[
'com.affine.settings.editorSettings.edgeless.text.alignment.left'
](),
},
{
value: 'center',
value: TextAlign.Center,
label:
t[
'com.affine.settings.editorSettings.edgeless.text.alignment.center'
](),
},
{
value: 'right',
value: TextAlign.Right,
label:
t[
'com.affine.settings.editorSettings.edgeless.text.alignment.right'
@@ -97,27 +143,37 @@ export const ShapeSettings = () => {
[t]
);
const textAlignment = settings[`shape:${currentShape}`].textAlign;
const setTextAlignment = useCallback(
(value: TextAlign) => {
editorSetting.set(`shape:${currentShape}`, {
textAlign: value,
});
},
[editorSetting, currentShape]
);
const shapes = useMemo<RadioItem[]>(
() => [
{
value: 'square',
value: ShapeType.Rect,
label: t['com.affine.settings.editorSettings.edgeless.shape.square'](),
},
{
value: 'ellipse',
value: ShapeType.Ellipse,
label: t['com.affine.settings.editorSettings.edgeless.shape.ellipse'](),
},
{
value: 'diamond',
value: ShapeType.Diamond,
label: t['com.affine.settings.editorSettings.edgeless.shape.diamond'](),
},
{
value: 'triangle',
value: ShapeType.Triangle,
label:
t['com.affine.settings.editorSettings.edgeless.shape.triangle'](),
},
{
value: 'rounded-rectangle',
value: 'roundedRect',
label:
t[
'com.affine.settings.editorSettings.edgeless.shape.rounded-rectangle'
@@ -127,14 +183,212 @@ export const ShapeSettings = () => {
[t]
);
const docs = useMemo<RadioItem[]>(
() => [
{
value: 'shape',
label: t['com.affine.settings.editorSettings.edgeless.shape.list'](),
},
{
value: 'flow',
label: t['com.affine.settings.editorSettings.edgeless.shape.flow'](),
},
],
[t]
);
const [currentDoc, setCurrentDoc] = useState<DocName>('shape');
const fillColorItems = useMemo(() => {
const { fillColor } = settings[`shape:${currentShape}`];
return Object.entries(ShapeFillColor).map(([name, value]) => {
const handler = () => {
editorSetting.set(`shape:${currentShape}`, { fillColor: value });
};
const isSelected = fillColor === value;
return (
<MenuItem key={name} onSelect={handler} selected={isSelected}>
{name}
</MenuItem>
);
});
}, [editorSetting, settings, currentShape]);
const borderColorItems = useMemo(() => {
const { strokeColor } = settings[`shape:${currentShape}`];
return Object.entries(LineColor).map(([name, value]) => {
const handler = () => {
editorSetting.set(`shape:${currentShape}`, { strokeColor: value });
};
const isSelected = strokeColor === value;
return (
<MenuItem key={name} onSelect={handler} selected={isSelected}>
{name}
</MenuItem>
);
});
}, [editorSetting, settings, currentShape]);
const borderThickness = settings[`shape:${currentShape}`].strokeWidth;
const setBorderThickness = useCallback(
(value: number[]) => {
editorSetting.set(`shape:${currentShape}`, {
strokeWidth: value[0],
});
},
[editorSetting, currentShape]
);
const fontFamilyItems = useMemo(() => {
const { fontFamily } = settings[`shape:${currentShape}`];
return Object.entries(FontFamily).map(([name, value]) => {
const handler = () => {
editorSetting.set(`shape:${currentShape}`, { fontFamily: value });
};
const isSelected = fontFamily === value;
return (
<MenuItem key={name} onSelect={handler} selected={isSelected}>
{name}
</MenuItem>
);
});
}, [editorSetting, settings, currentShape]);
const fontStyleItems = useMemo(() => {
const { fontStyle } = settings[`shape:${currentShape}`];
return Object.entries(FontStyle).map(([name, value]) => {
const handler = () => {
editorSetting.set(`shape:${currentShape}`, { fontStyle: value });
};
const isSelected = fontStyle === value;
return (
<MenuItem key={name} onSelect={handler} selected={isSelected}>
{name}
</MenuItem>
);
});
}, [editorSetting, settings, currentShape]);
const fontWeightItems = useMemo(() => {
const { fontWeight } = settings[`shape:${currentShape}`];
return Object.entries(FontWeight).map(([name, value]) => {
const handler = () => {
editorSetting.set(`shape:${currentShape}`, { fontWeight: value });
};
const isSelected = fontWeight === value;
return (
<MenuItem key={name} onSelect={handler} selected={isSelected}>
{name}
</MenuItem>
);
});
}, [editorSetting, settings, currentShape]);
const fontSizeItems = useMemo(() => {
const { fontSize } = settings[`shape:${currentShape}`];
return Object.entries(ShapeTextFontSize).map(([name, value]) => {
const handler = () => {
editorSetting.set(`shape:${currentShape}`, { fontSize: Number(value) });
};
const isSelected = fontSize === Number(value);
return (
<MenuItem key={name} onSelect={handler} selected={isSelected}>
{name}
</MenuItem>
);
});
}, [editorSetting, settings, currentShape]);
const textColorItems = useMemo(() => {
const { color } = settings[`shape:${currentShape}`];
return Object.entries(LineColor).map(([name, value]) => {
const handler = () => {
editorSetting.set(`shape:${currentShape}`, { color: value });
};
const isSelected = color === value;
return (
<MenuItem key={name} onSelect={handler} selected={isSelected}>
{name}
</MenuItem>
);
});
}, [editorSetting, settings, currentShape]);
const getElements = useCallback(
(doc: Doc) => {
const surface = getSurfaceBlock(doc);
if (!surface) return [];
return surface.getElementsByType('shape').filter(node => {
const shape = node as ShapeElementModel;
const { shapeType, radius } = shape;
const shapeName = getShapeName(shapeType, radius);
return shapeName === currentShape;
});
},
[currentShape]
);
const firstUpdate = useCallback(
(doc: Doc, editorHost: EditorHost) => {
const edgelessService = editorHost.std.getService(
'affine:page'
) as EdgelessRootService;
const surface = getSurfaceBlock(doc);
if (!surface) return;
surface.getElementsByType('shape').forEach(node => {
const shape = node as ShapeElementModel;
const { shapeType, radius } = shape;
const shapeName = getShapeName(shapeType, radius);
const props = editorSetting.get(`shape:${shapeName}`);
edgelessService.updateElement(shape.id, props);
});
},
[editorSetting]
);
const fillColor = useMemo(() => {
const color = settings[`shape:${currentShape}`].fillColor;
return getColorFromMap(color, ShapeFillColorMap);
}, [currentShape, getColorFromMap, settings]);
const borderColor = useMemo(() => {
const color = settings[`shape:${currentShape}`].strokeColor;
return getColorFromMap(color, LineColorMap);
}, [currentShape, getColorFromMap, settings]);
const textColor = useMemo(() => {
const color = settings[`shape:${currentShape}`].color;
return getColorFromMap(color, LineColorMap);
}, [currentShape, getColorFromMap, settings]);
const height = currentDoc === 'flow' ? 456 : 180;
return (
<>
<EdgelessSnapshot
key={currentDoc}
title={t['com.affine.settings.editorSettings.edgeless.shape']()}
docName="shape"
keyName="shape"
flavour="shape"
/>
docName={currentDoc}
keyName={`shape:${currentShape}`}
height={height}
getElements={getElements}
firstUpdate={firstUpdate}
>
<RadioGroup
padding={0}
gap={4}
itemHeight={28}
borderRadius={8}
value={currentDoc}
items={docs}
onChange={setCurrentDoc}
style={{
background: 'transparent',
position: 'absolute',
right: 0,
bottom: 0,
}}
indicatorClassName={shapeIndicator}
/>
</EdgelessSnapshot>
<RadioGroup
padding={0}
@@ -165,14 +419,19 @@ export const ShapeSettings = () => {
]()}
desc={''}
>
<DropdownMenu
items={<MenuItem>Yellow</MenuItem>}
trigger={
<MenuTrigger className={menuTrigger} disabled>
Yellow
</MenuTrigger>
}
/>
{fillColor ? (
<DropdownMenu
items={fillColorItems}
trigger={
<MenuTrigger
className={menuTrigger}
prefix={<Point color={fillColor.value} />}
>
{fillColor.key}
</MenuTrigger>
}
/>
) : null}
</SettingRow>
<SettingRow
name={t[
@@ -180,14 +439,19 @@ export const ShapeSettings = () => {
]()}
desc={''}
>
<DropdownMenu
items={<MenuItem>Yellow</MenuItem>}
trigger={
<MenuTrigger className={menuTrigger} disabled>
Yellow
</MenuTrigger>
}
/>
{borderColor ? (
<DropdownMenu
items={borderColorItems}
trigger={
<MenuTrigger
className={menuTrigger}
prefix={<Point color={borderColor.value} />}
>
{borderColor.key}
</MenuTrigger>
}
/>
) : null}
</SettingRow>
<SettingRow
name={t[
@@ -210,12 +474,12 @@ export const ShapeSettings = () => {
desc={''}
>
<Slider
value={borderThickness}
value={[borderThickness]}
onValueChange={setBorderThickness}
min={1}
max={5}
step={1}
nodes={[1, 2, 3, 4, 5]}
min={2}
max={12}
step={2}
nodes={[2, 4, 6, 8, 10, 12]}
/>
</SettingRow>
<SettingRow
@@ -224,25 +488,31 @@ export const ShapeSettings = () => {
]()}
desc={''}
>
<DropdownMenu
items={<MenuItem>Yellow</MenuItem>}
trigger={
<MenuTrigger className={menuTrigger} disabled>
Yellow
</MenuTrigger>
}
/>
{textColor ? (
<DropdownMenu
items={textColorItems}
trigger={
<MenuTrigger
className={menuTrigger}
prefix={<Point color={textColor.value} />}
>
{textColor.key}
</MenuTrigger>
}
/>
) : null}
</SettingRow>
<SettingRow
name={t['com.affine.settings.editorSettings.edgeless.shape.font']()}
name={t[
'com.affine.settings.editorSettings.edgeless.text.font-family'
]()}
desc={''}
>
{' '}
<DropdownMenu
items={<MenuItem>Inter</MenuItem>}
items={fontFamilyItems}
trigger={
<MenuTrigger className={menuTrigger} disabled>
Inter
<MenuTrigger className={menuTrigger}>
{FontFamilyMap[settings[`shape:${currentShape}`].fontFamily]}
</MenuTrigger>
}
/>
@@ -254,25 +524,40 @@ export const ShapeSettings = () => {
desc={''}
>
<DropdownMenu
items={<MenuItem>15px</MenuItem>}
items={fontSizeItems}
trigger={
<MenuTrigger className={menuTrigger} disabled>
15px
<MenuTrigger className={menuTrigger}>
{settings[`shape:${currentShape}`].fontSize + 'px'}
</MenuTrigger>
}
/>
</SettingRow>
<SettingRow
name={t[
'com.affine.settings.editorSettings.edgeless.shape.font-style'
'com.affine.settings.editorSettings.edgeless.text.font-style'
]()}
desc={''}
>
<DropdownMenu
items={<MenuItem>Regular</MenuItem>}
items={fontStyleItems}
trigger={
<MenuTrigger className={menuTrigger} disabled>
Regular
<MenuTrigger className={menuTrigger}>
{String(settings[`shape:${currentShape}`].fontStyle)}
</MenuTrigger>
}
/>
</SettingRow>
<SettingRow
name={t[
'com.affine.settings.editorSettings.edgeless.text.font-weight'
]()}
desc={''}
>
<DropdownMenu
items={fontWeightItems}
trigger={
<MenuTrigger className={menuTrigger}>
{FontWeightMap[settings[`shape:${currentShape}`].fontWeight]}
</MenuTrigger>
}
/>

View File

@@ -2,11 +2,11 @@ import type { EditorSettingSchema } from '@affine/core/modules/editor-settting';
import { EditorSettingService } from '@affine/core/modules/editor-settting';
import type { EditorHost } from '@blocksuite/block-std';
import { BlockStdScope } from '@blocksuite/block-std';
import type { SurfaceBlockModel } from '@blocksuite/block-std/gfx';
import type { EdgelessRootService, FrameBlockModel } from '@blocksuite/blocks';
import type { GfxPrimitiveElementModel } from '@blocksuite/block-std/gfx';
import type { EdgelessRootService } from '@blocksuite/blocks';
import { SpecProvider } from '@blocksuite/blocks';
import { Bound } from '@blocksuite/global/utils';
import type { Doc } from '@blocksuite/store';
import type { Block, Doc } from '@blocksuite/store';
import { useFramework } from '@toeverything/infra';
import { isEqual } from 'lodash-es';
import { useCallback, useEffect, useRef } from 'react';
@@ -14,52 +14,49 @@ import { map, pairwise } from 'rxjs';
import { snapshotContainer, snapshotTitle } from '../style.css';
import { type DocName, getDocByName } from './docs';
import { getFrameBlock } from './utils';
interface Props {
title: string;
docName: DocName;
flavour: BlockSuite.EdgelessModelKeys;
keyName: keyof EditorSettingSchema;
height?: number;
}
export function getSurfaceBlock(doc: Doc) {
const blocks = doc.getBlocksByFlavour('affine:surface');
return blocks.length !== 0 ? (blocks[0].model as SurfaceBlockModel) : null;
}
function getFrameBlock(doc: Doc) {
const blocks = doc.getBlocksByFlavour('affine:frame');
return blocks.length !== 0 ? (blocks[0].model as FrameBlockModel) : null;
getElements: (doc: Doc) => Array<Block | GfxPrimitiveElementModel>;
firstUpdate?: (doc: Doc, editorHost: EditorHost) => void;
children?: React.ReactElement;
}
const boundMap = new Map<DocName, Bound>();
export const EdgelessSnapshot = (props: Props) => {
const { title, docName, flavour, keyName, height = 180 } = props;
const {
title,
docName,
keyName,
height = 180,
getElements,
firstUpdate,
children,
} = props;
const wrapperRef = useRef<HTMLDivElement | null>(null);
const docRef = useRef<Doc | null>(null);
const editorHostRef = useRef<EditorHost | null>(null);
const framework = useFramework();
const { editorSetting } = framework.get(EditorSettingService);
const updateElement = useCallback(
(props: Record<string, unknown>) => {
const editorHost = editorHostRef.current;
const doc = docRef.current;
if (!editorHost || !doc) return;
const edgelessService = editorHost.std.getService(
'affine:page'
) as EdgelessRootService;
const blocks = doc.getBlocksByFlavour(flavour);
const surface = getSurfaceBlock(doc);
const elements = surface?.getElementsByType(flavour) || [];
[...blocks, ...elements].forEach(ele => {
edgelessService.updateElement(ele.id, props);
});
},
[flavour]
);
const updateElements = useCallback(() => {
const editorHost = editorHostRef.current;
const doc = docRef.current;
if (!editorHost || !doc) return;
const edgelessService = editorHost.std.getService(
'affine:page'
) as EdgelessRootService;
const elements = getElements(doc);
const props = editorSetting.get(keyName) as any;
elements.forEach(element => {
edgelessService.updateElement(element.id, props);
});
}, [editorSetting, getElements, keyName]);
const renderEditor = useCallback(async () => {
if (!wrapperRef.current) return;
@@ -72,8 +69,12 @@ export const EdgelessSnapshot = (props: Props) => {
}).render();
docRef.current = doc;
editorHostRef.current = editorHost;
const settings = editorSetting.get(keyName);
updateElement(settings as any);
if (firstUpdate) {
firstUpdate(doc, editorHost);
} else {
updateElements();
}
// refresh viewport
const edgelessService = editorHost.std.getService(
@@ -82,20 +83,18 @@ export const EdgelessSnapshot = (props: Props) => {
edgelessService.specSlots.viewConnected.once(({ component }) => {
const edgelessBlock = component as any;
edgelessBlock.editorViewportSelector = 'ref-viewport';
edgelessBlock.service.viewport.sizeUpdated.once(() => {
const frame = getFrameBlock(doc);
if (frame) {
boundMap.set(docName, Bound.deserialize(frame.xywh));
doc.deleteBlock(frame);
}
const bound = boundMap.get(docName);
bound && edgelessService.viewport.setViewportByBound(bound);
});
const frame = getFrameBlock(doc);
if (frame) {
boundMap.set(docName, Bound.deserialize(frame.xywh));
doc.deleteBlock(frame);
}
const bound = boundMap.get(docName);
bound && edgelessService.viewport.setViewportByBound(bound);
});
// append to dom node
wrapperRef.current.append(editorHost);
}, [docName, editorSetting, keyName, updateElement]);
}, [docName, firstUpdate, updateElements]);
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -117,11 +116,11 @@ export const EdgelessSnapshot = (props: Props) => {
)
.subscribe(([prev, current]) => {
if (!isEqual(prev, current)) {
updateElement(current);
updateElements();
}
});
return () => sub.unsubscribe();
}, [editorSetting.provider, flavour, keyName, updateElement]);
}, [editorSetting.provider, keyName, updateElements]);
return (
<div className={snapshotContainer}>
@@ -136,6 +135,7 @@ export const EdgelessSnapshot = (props: Props) => {
height,
}}
></div>
{children}
</div>
);
};

View File

@@ -17,6 +17,7 @@ import {
LineColorMap,
TextAlign,
} from '@blocksuite/blocks';
import type { Doc } from '@blocksuite/store';
import { useFramework, useLiveData } from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
@@ -139,13 +140,18 @@ export const TextSettings = () => {
const { color } = settings['affine:edgeless-text'];
return getColorFromMap(color, LineColorMap);
}, [getColorFromMap, settings]);
const getElements = useCallback((doc: Doc) => {
return doc.getBlocksByFlavour('affine:edgeless-text') || [];
}, []);
return (
<>
<EdgelessSnapshot
title={t['com.affine.settings.editorSettings.edgeless.text']()}
docName="text"
keyName="affine:edgeless-text"
flavour="affine:edgeless-text"
getElements={getElements}
/>
<SettingRow
name={t['com.affine.settings.editorSettings.edgeless.text.color']()}

View File

@@ -0,0 +1,13 @@
import type { SurfaceBlockModel } from '@blocksuite/block-std/gfx';
import type { FrameBlockModel } from '@blocksuite/blocks';
import type { Doc } from '@blocksuite/store';
export function getSurfaceBlock(doc: Doc) {
const blocks = doc.getBlocksByFlavour('affine:surface');
return blocks.length !== 0 ? (blocks[0].model as SurfaceBlockModel) : null;
}
export function getFrameBlock(doc: Doc) {
const blocks = doc.getBlocksByFlavour('affine:frame');
return blocks.length !== 0 ? (blocks[0].model as FrameBlockModel) : null;
}

View File

@@ -23,6 +23,7 @@ export const menuTrigger = style({
});
export const snapshotContainer = style({
position: 'relative',
display: 'flex',
flexDirection: 'column',
marginBottom: '24px',

View File

@@ -1,20 +1,7 @@
import {
BrushSchema,
ConnectorSchema,
EdgelessTextSchema,
NoteSchema,
ShapeSchema,
} from '@blocksuite/affine-shared/utils';
import { NodePropsSchema } from '@blocksuite/affine-shared/utils';
import { z } from 'zod';
// TODO import from BlockSuite
export const BSEditorSettingSchema = z.object({
connector: ConnectorSchema,
brush: BrushSchema,
shape: ShapeSchema,
'affine:edgeless-text': EdgelessTextSchema,
'affine:note': NoteSchema,
});
export const BSEditorSettingSchema = NodePropsSchema;
export type FontFamily = 'Sans' | 'Serif' | 'Mono' | 'Custom';

View File

@@ -1292,6 +1292,8 @@
"com.affine.settings.editorSettings.edgeless.shape.text-alignment": "Text alignment",
"com.affine.settings.editorSettings.edgeless.shape.text-color": "Text color",
"com.affine.settings.editorSettings.edgeless.shape.triangle": "Triangle",
"com.affine.settings.editorSettings.edgeless.shape.list": "List",
"com.affine.settings.editorSettings.edgeless.shape.flow": "Flow",
"com.affine.settings.editorSettings.edgeless.style": "Style",
"com.affine.settings.editorSettings.edgeless.style.general": "General",
"com.affine.settings.editorSettings.edgeless.style.scribbled": "Scribbled",

View File

@@ -1293,6 +1293,8 @@
"com.affine.settings.editorSettings.edgeless.shape.text-alignment": "文本对齐",
"com.affine.settings.editorSettings.edgeless.shape.text-color": "文本颜色",
"com.affine.settings.editorSettings.edgeless.shape.triangle": "三角形",
"com.affine.settings.editorSettings.edgeless.shape.list": "列表",
"com.affine.settings.editorSettings.edgeless.shape.flow": "流程图",
"com.affine.settings.editorSettings.edgeless.style": "样式",
"com.affine.settings.editorSettings.edgeless.style.general": "常规",
"com.affine.settings.editorSettings.edgeless.style.scribbled": "涂鸦",