mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(core): shape editor settings (#8122)
<div class='graphite__hidden'>
<div>🎥 Video uploaded on Graphite:</div>
<a href="https://app.graphite.dev/media/video/sJGviKxfE3Ap685cl5bj/cdf4f95c-15b4-4eac-be57-26974f9ca02b.mov">
<img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/sJGviKxfE3Ap685cl5bj/cdf4f95c-15b4-4eac-be57-26974f9ca02b.mov">
</a>
</div>
<video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/sJGviKxfE3Ap685cl5bj/cdf4f95c-15b4-4eac-be57-26974f9ca02b.mov">录屏2024-09-05 21.20.31.mov</video>
This commit is contained in:
@@ -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[
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[
|
||||
|
||||
@@ -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']()}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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']()}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export const menuTrigger = style({
|
||||
});
|
||||
|
||||
export const snapshotContainer = style({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginBottom: '24px',
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "涂鸦",
|
||||
|
||||
Reference in New Issue
Block a user