From f12655655e2684c765383362df0e3f6045e794fd Mon Sep 17 00:00:00 2001 From: akumatus Date: Wed, 11 Sep 2024 09:17:11 +0000 Subject: [PATCH] feat: add mindmap and connector settings (#8198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What changed? - Add `connector` label settings. - Add `mindmap` style settings. - Add skeleton loading placeholder.
🎥 Video uploaded on Graphite:
--- .../editor/edgeless/connector.tsx | 254 +++++++++++++++++- .../editor/edgeless/docs/connector.json | 47 +++- .../editor/edgeless/docs/index.ts | 11 +- .../editor/edgeless/docs/mindmap.json | 78 +++--- .../editor/edgeless/mind-map.tsx | 73 ++++- .../general-setting/editor/edgeless/shape.tsx | 10 +- .../editor/edgeless/snapshot.tsx | 16 +- .../general-setting/editor/style.css.ts | 6 + 8 files changed, 420 insertions(+), 75 deletions(-) diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/connector.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/connector.tsx index 0f7942252e..1813d45506 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/connector.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/connector.tsx @@ -10,10 +10,15 @@ import { EditorSettingService } from '@affine/core/modules/editor-settting'; import { useI18n } from '@affine/i18n'; import { ConnectorMode, + FontFamily, + FontFamilyMap, + FontStyle, + FontWeightMap, LineColor, LineColorMap, PointStyle, StrokeStyle, + TextAlign, } from '@blocksuite/blocks'; import type { Doc } from '@blocksuite/store'; import { useFramework, useLiveData } from '@toeverything/infra'; @@ -21,7 +26,7 @@ import { useCallback, useMemo } from 'react'; import { DropdownMenu } from '../menu'; import { menuTrigger, settingWrapper } from '../style.css'; -import { useColor } from '../utils'; +import { sortedFontWeightEntries, useColor } from '../utils'; import { Point } from './point'; import { EdgelessSnapshot } from './snapshot'; import { getSurfaceBlock } from './utils'; @@ -31,6 +36,15 @@ enum ConnecterStyle { Scribbled = 'scribbled', } +enum ConnectorTextFontSize { + '16px' = '16', + '20px' = '20', + '24px' = '24', + '32px' = '32', + '40px' = '40', + '64px' = '64', +} + export const ConnectorSettings = () => { const t = useI18n(); const framework = useFramework(); @@ -191,6 +205,150 @@ export const ConnectorSettings = () => { }); }, [editorSetting, settings]); + const alignItems = useMemo( + () => [ + { + value: TextAlign.Left, + label: + t[ + 'com.affine.settings.editorSettings.edgeless.text.alignment.left' + ](), + }, + { + value: TextAlign.Center, + label: + t[ + 'com.affine.settings.editorSettings.edgeless.text.alignment.center' + ](), + }, + { + value: TextAlign.Right, + label: + t[ + 'com.affine.settings.editorSettings.edgeless.text.alignment.right' + ](), + }, + ], + [t] + ); + + const textAlignment = settings.connector.labelStyle.textAlign; + const setTextAlignment = useCallback( + (value: TextAlign) => { + editorSetting.set('connector', { + labelStyle: { + textAlign: value, + }, + }); + }, + [editorSetting] + ); + + const fontFamilyItems = useMemo(() => { + const { fontFamily } = settings.connector.labelStyle; + return Object.entries(FontFamily).map(([name, value]) => { + const handler = () => { + editorSetting.set('connector', { + labelStyle: { + fontFamily: value, + }, + }); + }; + const isSelected = fontFamily === value; + return ( + + {name} + + ); + }); + }, [editorSetting, settings]); + + const fontStyleItems = useMemo(() => { + const { fontStyle } = settings.connector.labelStyle; + return Object.entries(FontStyle).map(([name, value]) => { + const handler = () => { + editorSetting.set('connector', { + labelStyle: { + fontStyle: value, + }, + }); + }; + const isSelected = fontStyle === value; + return ( + + {name} + + ); + }); + }, [editorSetting, settings]); + + const fontWeightItems = useMemo(() => { + const { fontWeight } = settings.connector.labelStyle; + return sortedFontWeightEntries.map(([name, value]) => { + const handler = () => { + editorSetting.set('connector', { + labelStyle: { + fontWeight: value, + }, + }); + }; + const isSelected = fontWeight === value; + return ( + + {name} + + ); + }); + }, [editorSetting, settings]); + + const fontSizeItems = useMemo(() => { + const { fontSize } = settings.connector.labelStyle; + return Object.entries(ConnectorTextFontSize).map(([name, value]) => { + const handler = () => { + editorSetting.set('connector', { + labelStyle: { + fontSize: Number(value), + }, + }); + }; + const isSelected = fontSize === Number(value); + return ( + + {name} + + ); + }); + }, [editorSetting, settings]); + + const textColorItems = useMemo(() => { + const { color } = settings.connector.labelStyle; + return Object.entries(LineColor).map(([name, value]) => { + const handler = () => { + editorSetting.set('connector', { + labelStyle: { + color: value, + }, + }); + }; + const isSelected = color === value; + return ( + } + > + {name} + + ); + }); + }, [editorSetting, settings]); + + const textColor = useMemo(() => { + const { color } = settings.connector.labelStyle; + return getColorFromMap(color, LineColorMap); + }, [getColorFromMap, settings]); + const getElements = useCallback((doc: Doc) => { const surface = getSurfaceBlock(doc); return surface?.getElementsByType('connector') || []; @@ -309,6 +467,100 @@ export const ConnectorSettings = () => { } /> + + {textColor ? ( + } + > + {textColor.key} + + } + /> + ) : null} + + + + {FontFamilyMap[settings.connector.labelStyle.fontFamily]} + + } + /> + + + + {settings.connector.labelStyle.fontSize + 'px'} + + } + /> + + + + {settings.connector.labelStyle.fontStyle} + + } + /> + + + + {FontWeightMap[settings.connector.labelStyle.fontWeight]} + + } + /> + + + + ); }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/connector.json b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/connector.json index 9ed00fb274..6db2ee20fc 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/connector.json +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/connector.json @@ -1,14 +1,14 @@ { "type": "page", "meta": { - "id": "v4nxnq2Al2", + "id": "gJEFfmo4QJ", "title": "", - "createDate": 1725459565290, + "createDate": 1726038448921, "tags": [] }, "blocks": { "type": "block", - "id": "eDFE5HokPO", + "id": "orzKfiHevj", "flavour": "affine:page", "version": 2, "props": { @@ -20,37 +20,58 @@ "children": [ { "type": "block", - "id": "ZksVPLRI0K", + "id": "-48EmppaxI", "flavour": "affine:surface", "version": 5, "props": { "elements": { - "hdqh5vFnzj": { + "m_PwyyI76y": { "index": "a0", - "seed": 263456687, + "seed": 44330892, "frontEndpointStyle": "None", + "labelOffset": { + "distance": 0.5, + "anchor": "center" + }, + "labelStyle": { + "color": "--affine-palette-line-black", + "fontSize": 16, + "fontFamily": "blocksuite:surface:Inter", + "fontWeight": "400", + "fontStyle": "normal", + "textAlign": "center" + }, + "labelXYWH": [235.6484375, 65.23828125, 200, 20], "mode": 2, "rearEndpointStyle": "Arrow", "rough": false, "roughness": 1.4, "source": { - "position": [196.0625, 145.84765625] + "position": [120.8515625, 146.44921875] }, "stroke": "--affine-palette-line-grey", "strokeStyle": "solid", "strokeWidth": 2, "target": { - "position": [418.5859375, 52.13671875] + "position": [387.4453125, 4.02734375] + }, + "text": { + "affine:surface:text": true, + "delta": [ + { + "insert": "label" + } + ] }, "type": "connector", - "id": "hdqh5vFnzj" + "id": "m_PwyyI76y" } } }, "children": [ { "type": "block", - "id": "TNgzGwq6Ct", + "id": "-6UhNH7qhy", "flavour": "affine:frame", "version": 1, "props": { @@ -63,10 +84,10 @@ ] }, "background": "--affine-palette-transparent", - "xywh": "[-13.38671875,-4.5625,739.3828125,192.51171875]", - "index": "a0", + "xywh": "[-12.04296875,-49.66796875,542.9765625,248.3984375]", + "index": "Zz", "childElementIds": { - "hdqh5vFnzj": true + "m_PwyyI76y": true } }, "children": [] diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/index.ts b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/index.ts index 6ece104c1b..de7d95a7b2 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/index.ts +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/index.ts @@ -69,13 +69,14 @@ export async function getDocByName(name: DocName) { if (docMap.get(name)) { return docMap.get(name); } - const snapshot = (await loaders[name]()) as DocSnapshot; - const promiseDoc = initDocFromSnapshot(snapshot); - docMap.set(name, promiseDoc); - return promiseDoc; + + const promise = initDoc(name); + docMap.set(name, promise); + return promise; } -async function initDocFromSnapshot(snapshot: DocSnapshot) { +async function initDoc(name: DocName) { + const snapshot = (await loaders[name]()) as DocSnapshot; const collection = await getCollection(); const job = new Job({ collection, diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/mindmap.json b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/mindmap.json index 746d209823..3733859bfa 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/mindmap.json +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/docs/mindmap.json @@ -1,61 +1,65 @@ { "type": "page", "meta": { - "id": "0P4XpxtY1T", - "title": "", - "createDate": 1725500529462, + "id": "_JUmoI_F28", + "title": "BlockSuite Playground", + "createDate": 1725610677620, "tags": [] }, "blocks": { "type": "block", - "id": "o_kDMq1Y2z", + "id": "FnaWN8Zm2_", "flavour": "affine:page", "version": 2, "props": { "title": { "$blocksuite:internal:text$": true, - "delta": [] + "delta": [ + { + "insert": "BlockSuite Playground" + } + ] } }, "children": [ { "type": "block", - "id": "CX3YRdnn7u", + "id": "hevWf4ccWc", "flavour": "affine:surface", "version": 5, "props": { "elements": { - "uXwcJ22j4U": { - "index": "a1", - "seed": 521352102, + "MYyOCcLTWN": { + "index": "a0", + "seed": 739933670, "children": { "affine:surface:ymap": true, "json": { - "5Jk9NbvuPN": { + "BvanBL7O38": { "index": "a0" }, - "DqF847301v": { + "QkmFfps45U": { "index": "a0", - "parent": "5Jk9NbvuPN" + "parent": "BvanBL7O38" }, - "3cpFVUO7z_": { + "1IN8YOdsCP": { "index": "a1", - "parent": "5Jk9NbvuPN" + "parent": "BvanBL7O38" }, - "cq1V7jb9Nw": { + "iFQ9oVR0KN": { "index": "a2", - "parent": "5Jk9NbvuPN" + "parent": "BvanBL7O38" } } }, "layoutType": 0, "style": 1, "type": "mindmap", - "id": "uXwcJ22j4U" + "id": "MYyOCcLTWN" }, - "5Jk9NbvuPN": { + "BvanBL7O38": { "index": "a0", - "seed": 2040865434, + "seed": 819595867, "color": "--affine-black", "fillColor": "--affine-white", "filled": true, @@ -87,13 +91,13 @@ ] }, "textResizing": 0, - "xywh": "[219.1787109375,-137.21072387695312,144.7799530029297,52]", + "xywh": "[-214.85955810546875,-113.65847778320312,144.7799530029297,52]", "type": "shape", - "id": "5Jk9NbvuPN" + "id": "BvanBL7O38" }, - "DqF847301v": { + "QkmFfps45U": { "index": "a0", - "seed": 42391752, + "seed": 557026939, "color": "--affine-black", "fillColor": "--affine-white", "filled": true, @@ -125,13 +129,13 @@ ] }, "textResizing": 0, - "xywh": "[563.9586639404297,-210.21072387695312,76.83197021484375,36]", + "xywh": "[129.92039489746094,-186.65847778320312,76.83197021484375,36]", "type": "shape", - "id": "DqF847301v" + "id": "QkmFfps45U" }, - "3cpFVUO7z_": { + "1IN8YOdsCP": { "index": "a0", - "seed": 1821565231, + "seed": 205695803, "color": "--affine-black", "fillColor": "--affine-white", "filled": true, @@ -163,13 +167,13 @@ ] }, "textResizing": 0, - "xywh": "[563.9586639404297,-129.21072387695312,76.83197021484375,36]", + "xywh": "[129.92039489746094,-105.65847778320312,76.83197021484375,36]", "type": "shape", - "id": "3cpFVUO7z_" + "id": "1IN8YOdsCP" }, - "cq1V7jb9Nw": { + "iFQ9oVR0KN": { "index": "a0", - "seed": 1835053830, + "seed": 585656351, "color": "--affine-black", "fillColor": "--affine-white", "filled": true, @@ -201,16 +205,16 @@ ] }, "textResizing": 0, - "xywh": "[563.9586639404297,-48.210723876953125,76.83197021484375,36]", + "xywh": "[129.92039489746094,-24.658477783203125,76.83197021484375,36]", "type": "shape", - "id": "cq1V7jb9Nw" + "id": "iFQ9oVR0KN" } } }, "children": [ { "type": "block", - "id": "yUWjMW5rEZ", + "id": "bVYX1z2q3T", "flavour": "affine:frame", "version": 1, "props": { @@ -223,10 +227,10 @@ ] }, "background": "--affine-palette-transparent", - "xywh": "[138.3125,-266.609375,602.640625,321.26171875]", - "index": "a0", + "xywh": "[-542.4695816040039,-292.0061340332031,798.42578125,414.50390625]", + "index": "Zz", "childElementIds": { - "uXwcJ22j4U": true + "MYyOCcLTWN": true } }, "children": [] diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/mind-map.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/mind-map.tsx index a29fc8befa..08afaabbf5 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/mind-map.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/mind-map.tsx @@ -5,38 +5,70 @@ import { type RadioItem, } from '@affine/component'; import { SettingRow } from '@affine/component/setting-components'; +import { EditorSettingService } from '@affine/core/modules/editor-settting'; import { useI18n } from '@affine/i18n'; +import { LayoutType, MindmapStyle } from '@blocksuite/blocks'; import type { Doc } from '@blocksuite/store'; -import { useCallback, useMemo, useState } from 'react'; +import { useFramework, useLiveData } from '@toeverything/infra'; +import { useCallback, useMemo } from 'react'; import { DropdownMenu } from '../menu'; import { menuTrigger, settingWrapper } from '../style.css'; import { EdgelessSnapshot } from './snapshot'; import { getSurfaceBlock } from './utils'; +const MINDMAP_STYLES = [ + { + value: MindmapStyle.ONE, + name: 'Style 1', + }, + { + value: MindmapStyle.TWO, + name: 'Style 2', + }, + { + value: MindmapStyle.THREE, + name: 'Style 3', + }, + { + value: MindmapStyle.FOUR, + name: 'Style 4', + }, +]; + export const MindMapSettings = () => { const t = useI18n(); - const [layoutValue, setLayoutValue] = useState<'left' | 'radial' | 'right'>( - 'right' + const framework = useFramework(); + const { editorSetting } = framework.get(EditorSettingService); + const settings = useLiveData(editorSetting.settings$); + + const { layoutType } = settings.mindmap; + const setLayoutType = useCallback( + (value: LayoutType) => { + editorSetting.set('mindmap', { + layoutType: value, + }); + }, + [editorSetting] ); - const layoutValueItems = useMemo( + const layoutTypeItems = useMemo( () => [ { - value: 'left', + value: LayoutType.LEFT as any, label: t[ 'com.affine.settings.editorSettings.edgeless.mind-map.layout.left' ](), }, { - value: 'radial', + value: LayoutType.BALANCE as any, label: t[ 'com.affine.settings.editorSettings.edgeless.mind-map.layout.radial' ](), }, { - value: 'right', + value: LayoutType.RIGHT as any, label: t[ 'com.affine.settings.editorSettings.edgeless.mind-map.layout.right' @@ -46,6 +78,21 @@ export const MindMapSettings = () => { [t] ); + const styleItems = useMemo(() => { + const { style } = settings.mindmap; + return MINDMAP_STYLES.map(({ name, value }) => { + const handler = () => { + editorSetting.set('mindmap', { style: value }); + }; + const isSelected = style === value; + return ( + + {name} + + ); + }); + }, [editorSetting, settings]); + const getElements = useCallback((doc: Doc) => { const surface = getSurfaceBlock(doc); return surface?.getElementsByType('mindmap') || []; @@ -65,10 +112,10 @@ export const MindMapSettings = () => { desc={''} > Style 1} + items={styleItems} trigger={ - - Style 1 + + {`Style ${settings.mindmap.style}`} } /> @@ -80,11 +127,11 @@ export const MindMapSettings = () => { desc={''} > diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/shape.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/shape.tsx index e21995fabd..a09a6d963f 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/shape.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/shape.tsx @@ -47,10 +47,12 @@ import { EdgelessSnapshot } from './snapshot'; import { getSurfaceBlock } from './utils'; enum ShapeTextFontSize { - '12px' = '12', + '16px' = '16', '20px' = '20', - '28px' = '28', - '36px' = '36', + '24px' = '24', + '32px' = '32', + '40px' = '40', + '64px' = '64', } const ShapeFillColorMap = createEnumMap(ShapeFillColor); @@ -557,7 +559,7 @@ export const ShapeSettings = () => { items={fontStyleItems} trigger={ - {String(settings[`shape:${currentShape}`].fontStyle)} + {settings[`shape:${currentShape}`].fontStyle} } /> diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/snapshot.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/snapshot.tsx index 31a46e57e5..96380a9f93 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/snapshot.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/edgeless/snapshot.tsx @@ -1,3 +1,4 @@ +import { Skeleton } from '@affine/component'; import type { EditorSettingSchema } from '@affine/core/modules/editor-settting'; import { EditorSettingService } from '@affine/core/modules/editor-settting'; import type { EditorHost } from '@blocksuite/block-std'; @@ -12,7 +13,12 @@ import { isEqual } from 'lodash-es'; import { useCallback, useEffect, useRef } from 'react'; import { map, pairwise } from 'rxjs'; -import { snapshotContainer, snapshotLabel, snapshotTitle } from '../style.css'; +import { + snapshotContainer, + snapshotLabel, + snapshotSkeleton, + snapshotTitle, +} from '../style.css'; import { type DocName, getDocByName } from './docs'; import { getFrameBlock } from './utils'; @@ -136,7 +142,13 @@ export const EdgelessSnapshot = (props: Props) => { overflow: 'hidden', height, }} - > + > + + {children} ); diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/style.css.ts b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/style.css.ts index 27e2d76697..b8db0252c4 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/style.css.ts +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/editor/style.css.ts @@ -43,6 +43,12 @@ export const snapshotTitle = style({ color: cssVarV2('text/secondary'), }); +export const snapshotSkeleton = style({ + position: 'absolute', + top: 0, + left: 0, +}); + export const snapshot = style({ width: '100%', height: '180px',