Skip to content

Commit

Permalink
feat(core): frame editor settings (#9970)
Browse files Browse the repository at this point in the history
Co-authored-by: L-Sun <zover.v@gmail.com>
Co-authored-by: Mirone <Saul-Mirone@outlook.com>
  • Loading branch information
3 people authored Feb 10, 2025
1 parent 03b8063 commit d4f0c53
Show file tree
Hide file tree
Showing 14 changed files with 211 additions and 84 deletions.
30 changes: 15 additions & 15 deletions blocksuite/affine/model/src/blocks/frame/frame-model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
GfxBlockElementModel,
GfxCompatibleProps,
GfxElementGeometry,
GfxGroupCompatibleInterface,
GfxModel,
Expand All @@ -10,41 +11,40 @@ import {
descendantElementsImpl,
generateKeyBetweenV2,
GfxCompatible,
GfxCompatibleZodSchema,
gfxGroupCompatibleSymbol,
hasDescendantElementImpl,
} from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils';
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
import { z } from 'zod';

import { ColorSchema, DefaultTheme } from '../../themes/index.js';
import { type Color, ColorSchema, DefaultTheme } from '../../themes/index.js';

export type FrameBlockProps = {
title: Text;
background: Color;
childElementIds?: Record<string, boolean>;
presentationIndex?: string;
} & GfxCompatibleProps;

export const FrameZodSchema = z
.object({
background: ColorSchema,
childElementIds: z.record(z.boolean()),
presentationIndex: z.string(),
})
.and(GfxCompatibleZodSchema)
.default({
background: DefaultTheme.transparent,
xywh: `[0,0,100,100]`,
index: 'a0',
childElementIds: Object.create(null),
presentationIndex: generateKeyBetweenV2(null, null),
lockedBySelf: false,
});

export type FrameBlockProps = z.infer<typeof FrameZodSchema> & {
title: Text;
};

export const FrameBlockSchema = defineBlockSchema({
flavour: 'affine:frame',
props: (internal): FrameBlockProps => ({
title: internal.Text(),
...FrameZodSchema.parse(undefined),
background: 'transparent',
xywh: `[0,0,100,100]`,
index: 'a0',
childElementIds: Object.create(null),
presentationIndex: generateKeyBetweenV2(null, null),
lockedBySelf: false,
}),
metadata: {
version: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,12 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
const { service, surfaceBlockModel } = edgeless;
const frameMgr = service.frame;
const frameIndex = service.frames.length + 1;
const id = this.crud.addBlock(
'affine:frame',
{
title: new Y.Text(`Frame ${frameIndex}`),
xywh: serializeXYWH(...xywh),
presentationIndex: frameMgr.generatePresentationIndex(),
},
surfaceBlockModel
);
const props = this.std.get(EditPropsStore).applyLastProps('affine:frame', {
title: new Y.Text(`Frame ${frameIndex}`),
xywh: serializeXYWH(...xywh),
presentationIndex: frameMgr.generatePresentationIndex(),
});
const id = this.crud.addBlock('affine:frame', props, surfaceBlockModel);
edgeless.doc.captureSync();
const frame = this.crud.getElementById(id);
if (!frame) return;
Expand Down
15 changes: 8 additions & 7 deletions blocksuite/blocks/src/root-block/edgeless/frame-manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
import { Overlay } from '@blocksuite/affine-block-surface';
import type { FrameBlockModel, NoteBlockModel } from '@blocksuite/affine-model';
import { EditPropsStore } from '@blocksuite/affine-shared/services';
import {
generateKeyBetweenV2,
getTopElements,
Expand All @@ -23,7 +25,6 @@ import type { Store } from '@blocksuite/store';
import { Text } from '@blocksuite/store';
import * as Y from 'yjs';

import type { FrameBlockModel, NoteBlockModel } from '../../index.js';
import { areSetsEqual } from './utils/misc.js';
import { isFrameBlock } from './utils/query.js';

Expand Down Expand Up @@ -194,16 +195,16 @@ export class EdgelessFrameManager extends GfxExtension {

private _addFrameBlock(bound: Bound) {
const surfaceModel = this.gfx.surface as SurfaceBlockModel;
const id = this.gfx.doc.addBlock(
'affine:frame',
{
const props = this.gfx.std
.get(EditPropsStore)
.applyLastProps('affine:frame', {
title: new Text(new Y.Text(`Frame ${this.frames.length + 1}`)),
xywh: bound.serialize(),
index: this.gfx.layer.generateIndex(true),
presentationIndex: this.generatePresentationIndex(),
},
surfaceModel
);
});

const id = this.gfx.doc.addBlock('affine:frame', props, surfaceModel);
const frameModel = this.gfx.getElementById(id);

if (!frameModel || !isFrameBlock(frameModel)) {
Expand Down
18 changes: 11 additions & 7 deletions blocksuite/blocks/src/root-block/edgeless/gfx-tool/frame-tool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { OverlayIdentifier } from '@blocksuite/affine-block-surface';
import type { FrameBlockModel } from '@blocksuite/affine-model';
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
import {
EditPropsStore,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import type { PointerEventState } from '@blocksuite/block-std';
import {
BaseTool,
Expand Down Expand Up @@ -70,16 +73,17 @@ export class FrameTool extends BaseTool {
const frames = this.gfx.layer.blocks.filter(
block => block.flavour === 'affine:frame'
) as FrameBlockModel[];
const id = this.doc.addBlock(
'affine:frame',
{

const props = this.std
.get(EditPropsStore)
.applyLastProps('affine:frame', {
title: new Text(new Y.Text(`Frame ${frames.length + 1}`)),
xywh: Bound.fromPoints([this._startPoint, currentPoint]).serialize(),
index: this.gfx.layer.generateIndex(true),
presentationIndex: this.frameManager.generatePresentationIndex(),
},
this.gfx.surface
);
});

const id = this.doc.addBlock('affine:frame', props, this.gfx.surface);

this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
control: 'canvas:draw',
Expand Down
2 changes: 0 additions & 2 deletions blocksuite/framework/block-std/src/gfx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ export {
export {
GfxBlockElementModel,
type GfxCommonBlockProps,
GfxCommonBlockZodSchema,
GfxCompatibleBlockModel as GfxCompatible,
type GfxCompatibleProps,
GfxCompatibleZodSchema,
} from './model/gfx-block-model.js';
export { type GfxModel } from './model/model.js';
export {
Expand Down
24 changes: 9 additions & 15 deletions blocksuite/framework/block-std/src/gfx/model/gfx-block-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ import {
polygonGetPointTangent,
polygonNearestPoint,
rotatePoints,
SerializedXYWHZodSchema,
} from '@blocksuite/global/utils';
import { BlockModel } from '@blocksuite/store';
import { z } from 'zod';

import {
isLockedByAncestorImpl,
Expand All @@ -35,24 +33,20 @@ import type { SurfaceBlockModel } from './surface/surface-model.js';
/**
* The props that a graphics block model should have.
*/
export const GfxCompatibleZodSchema = z.object({
xywh: SerializedXYWHZodSchema,
index: z.string(),
lockedBySelf: z.boolean().optional(),
});
export type GfxCompatibleProps = z.infer<typeof GfxCompatibleZodSchema>;
export type GfxCompatibleProps = {
xywh: SerializedXYWH;
index: string;
lockedBySelf?: boolean;
};

/**
* This type include the common props for the graphic block model.
* You can use this type with Omit to define the props of a graphic block model.
*/
export const GfxCommonBlockZodSchema = GfxCompatibleZodSchema.and(
z.object({
rotate: z.number(),
scale: z.number(),
})
);
export type GfxCommonBlockProps = z.infer<typeof GfxCommonBlockZodSchema>;
export type GfxCommonBlockProps = GfxCompatibleProps & {
rotate: number;
scale: number;
};

/**
* The graphic block model that can be rendered in the graphics mode.
Expand Down
1 change: 0 additions & 1 deletion blocksuite/framework/global/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,4 @@ export * from './slot.js';
export * from './types.js';
export * from './with-disposable.js';
export type { SerializedXYWH, XYWH } from './xywh.js';
export { SerializedXYWHZodSchema } from './xywh.js';
export { deserializeXYWH, serializeXYWH } from './xywh.js';
26 changes: 0 additions & 26 deletions blocksuite/framework/global/src/utils/xywh.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { z } from 'zod';

/**
* XYWH represents the x, y, width, and height of an element or block.
*/
Expand All @@ -10,30 +8,6 @@ export type XYWH = [number, number, number, number];
*/
export type SerializedXYWH = `[${number},${number},${number},${number}]`;

export const SerializedXYWHZodSchema = z.custom<SerializedXYWH>((val: any) => {
if (typeof val !== 'string') {
throw new Error('SerializedXYWH should be a string');
}

if (!val.startsWith('[') || !val.endsWith(']')) {
throw new Error('SerializedXYWH should be wrapped in square brackets');
}

const parts = val.slice(1, -1).split(',');

if (parts.length !== 4) {
throw new Error('SerializedXYWH should have 4 parts');
}

for (const part of parts) {
if (!/^\d+$/.test(part)) {
throw new Error('Each part of SerializedXYWH should be a number');
}
}

return val as SerializedXYWH;
});

export function serializeXYWH(
x: number,
y: number,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"type": "page",
"meta": {
"id": "gIUEIHL42k",
"title": "FrameDoc",
"createDate": 1738858597791,
"tags": []
},
"blocks": {
"type": "block",
"id": "MCYCDPz_QH",
"flavour": "affine:page",
"version": 2,
"props": {
"title": {
"$blocksuite:internal:text$": true,
"delta": [
{
"insert": "FrameDoc"
}
]
}
},
"children": [
{
"type": "block",
"id": "I_s0rS2Pbl",
"flavour": "affine:surface",
"version": 5,
"props": {
"elements": {}
},
"children": [
{
"type": "block",
"id": "mHE8GI9UWx",
"flavour": "affine:frame",
"version": 1,
"props": {
"title": {
"$blocksuite:internal:text$": true,
"delta": [
{
"insert": "Frame 1"
}
]
},
"background": "transparent",
"xywh": "[-186.37939453125,-68.03814697265625,375.8616943359375,114.787109375]",
"index": "Zz",
"childElementIds": {},
"presentationIndex": "a00zHdtY37OLhj7JOhCr9ywYQXy1SbAI6kM",
"lockedBySelf": false
},
"children": []
}
]
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export type DocName =
| 'flow'
| 'text'
| 'connector'
| 'mindmap';
| 'mindmap'
| 'frame';

const docMap = new Map<DocName, Promise<Store | undefined>>();

Expand All @@ -40,6 +41,10 @@ async function loadShape() {
return (await import('./shape.json')).default;
}

async function loadFrame() {
return (await import('./frame.json')).default;
}

async function loadFlow() {
return (await import('./flow.json')).default;
}
Expand All @@ -60,6 +65,7 @@ const loaders = {
note: loadNote,
pen: loadPen,
shape: loadShape,
frame: loadFrame,
flow: loadFlow,
text: loadText,
connector: loadConnector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { SettingWrapper } from '@affine/component/setting-components';
import { useI18n } from '@affine/i18n';

import { ConnectorSettings } from './connector';
import { FrameSettings } from './frame';
import { GeneralEdgelessSetting } from './general';
import { MindMapSettings } from './mind-map';
import { NoteSettings } from './note';
Expand All @@ -17,6 +18,7 @@ export const Edgeless = () => {
<NoteSettings />
<TextSettings />
<ShapeSettings />
<FrameSettings />
<ConnectorSettings />
<PenSettings />
<MindMapSettings />
Expand Down
Loading

0 comments on commit d4f0c53

Please sign in to comment.