mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
feat(core): frame editor settings (#9970)
Co-authored-by: L-Sun <zover.v@gmail.com> Co-authored-by: Mirone <Saul-Mirone@outlook.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
GfxBlockElementModel,
|
||||
GfxCompatibleProps,
|
||||
GfxElementGeometry,
|
||||
GfxGroupCompatibleInterface,
|
||||
GfxModel,
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
descendantElementsImpl,
|
||||
generateKeyBetweenV2,
|
||||
GfxCompatible,
|
||||
GfxCompatibleZodSchema,
|
||||
gfxGroupCompatibleSymbol,
|
||||
hasDescendantElementImpl,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
@@ -18,33 +18,33 @@ 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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
@@ -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';
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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,
|
||||
@@ -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',
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -15,10 +15,8 @@ import {
|
||||
polygonGetPointTangent,
|
||||
polygonNearestPoint,
|
||||
rotatePoints,
|
||||
SerializedXYWHZodSchema,
|
||||
} from '@blocksuite/global/utils';
|
||||
import { BlockModel } from '@blocksuite/store';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
isLockedByAncestorImpl,
|
||||
@@ -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.
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* XYWH represents the x, y, width, and height of an element or block.
|
||||
*/
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user