mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(draw): lock shape
This commit is contained in:
@@ -22,3 +22,4 @@ export * from './translate-shapes';
|
||||
export * from './ungroup-shapes';
|
||||
export * from './update-shapes';
|
||||
export * from './set-shapes-props';
|
||||
export * from './set-shapes-lock-status';
|
||||
|
||||
59
libs/components/board-commands/src/set-shapes-lock-status.ts
Normal file
59
libs/components/board-commands/src/set-shapes-lock-status.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type {
|
||||
TDShape,
|
||||
TldrawCommand,
|
||||
} from '@toeverything/components/board-types';
|
||||
import type { TldrawApp } from '@toeverything/components/board-state';
|
||||
|
||||
export function setShapesLockStatus<T extends TDShape>(
|
||||
app: TldrawApp,
|
||||
ids: string[],
|
||||
isLocked: boolean
|
||||
): TldrawCommand {
|
||||
const { currentPageId, selectedIds } = app;
|
||||
|
||||
const initialShapes = ids.map(id => app.getShape<T>(id));
|
||||
|
||||
const before: Record<string, Partial<TDShape>> = {};
|
||||
const after: Record<string, Partial<TDShape>> = {};
|
||||
|
||||
initialShapes.forEach(shape => {
|
||||
before[shape.id] = {
|
||||
isLocked: shape.isLocked,
|
||||
};
|
||||
after[shape.id] = {
|
||||
isLocked,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
id: 'set_shapes_lock_status',
|
||||
before: {
|
||||
document: {
|
||||
pages: {
|
||||
[currentPageId]: {
|
||||
shapes: before,
|
||||
},
|
||||
},
|
||||
pageStates: {
|
||||
[currentPageId]: {
|
||||
selectedIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
after: {
|
||||
document: {
|
||||
pages: {
|
||||
[currentPageId]: {
|
||||
shapes: after,
|
||||
},
|
||||
},
|
||||
pageStates: {
|
||||
[currentPageId]: {
|
||||
selectedIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { FontSizeConfig } from './FontSizeConfig';
|
||||
import { StrokeLineStyleConfig } from './stroke-line-style-config';
|
||||
import { Group, UnGroup } from './GroupOperation';
|
||||
import { DeleteShapes } from './DeleteOperation';
|
||||
import { Lock, Unlock } from './LockOperation';
|
||||
|
||||
export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
||||
const state = app.useStore();
|
||||
@@ -63,6 +64,16 @@ export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
||||
shapes={config.ungroup.selectedShapes}
|
||||
/>
|
||||
) : null,
|
||||
lock: config.lock.selectedShapes.length ? (
|
||||
<Lock key="lock" app={app} shapes={config.lock.selectedShapes} />
|
||||
) : null,
|
||||
unlock: config.unlock.selectedShapes.length ? (
|
||||
<Unlock
|
||||
key="unlock"
|
||||
app={app}
|
||||
shapes={config.unlock.selectedShapes}
|
||||
/>
|
||||
) : null,
|
||||
delete: (
|
||||
<DeleteShapes
|
||||
key="deleteShapes"
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { FC } from 'react';
|
||||
import type { TldrawApp } from '@toeverything/components/board-state';
|
||||
import type { TDShape } from '@toeverything/components/board-types';
|
||||
import { IconButton, Tooltip } from '@toeverything/components/ui';
|
||||
import { LockIcon, UnlockIcon } from '@toeverything/components/icons';
|
||||
import { getShapeIds } from './utils';
|
||||
|
||||
interface GroupAndUnGroupProps {
|
||||
app: TldrawApp;
|
||||
shapes: TDShape[];
|
||||
}
|
||||
|
||||
export const Lock: FC<GroupAndUnGroupProps> = ({ app, shapes }) => {
|
||||
const lock = () => {
|
||||
app.lock(getShapeIds(shapes));
|
||||
};
|
||||
return (
|
||||
<Tooltip content="Lock">
|
||||
<IconButton onClick={lock}>
|
||||
<UnlockIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const Unlock: FC<GroupAndUnGroupProps> = ({ app, shapes }) => {
|
||||
const unlock = () => {
|
||||
app.unlock(getShapeIds(shapes));
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip content="Unlock">
|
||||
<IconButton onClick={unlock}>
|
||||
<LockIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,15 @@ import { TDShapeType } from '@toeverything/components/board-types';
|
||||
import { TLDR } from '@toeverything/components/board-state';
|
||||
|
||||
interface Config {
|
||||
type: 'stroke' | 'fill' | 'font' | 'group' | 'ungroup' | 'deleteShapes';
|
||||
type:
|
||||
| 'stroke'
|
||||
| 'fill'
|
||||
| 'font'
|
||||
| 'group'
|
||||
| 'ungroup'
|
||||
| 'deleteShapes'
|
||||
| 'lock'
|
||||
| 'unlock';
|
||||
selectedShapes: TDShape[];
|
||||
}
|
||||
|
||||
@@ -34,6 +42,14 @@ const _createInitConfig = (): Record<Config['type'], Config> => {
|
||||
type: 'deleteShapes',
|
||||
selectedShapes: [],
|
||||
},
|
||||
lock: {
|
||||
type: 'lock',
|
||||
selectedShapes: [],
|
||||
},
|
||||
unlock: {
|
||||
type: 'unlock',
|
||||
selectedShapes: [],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -64,6 +80,17 @@ const _isSupportFill = (shape: TDShape): boolean => {
|
||||
].some(type => type === shape.type);
|
||||
};
|
||||
|
||||
const _isSupportFont = (shape: TDShape): boolean => {
|
||||
return [
|
||||
TDShapeType.Rectangle,
|
||||
TDShapeType.Ellipse,
|
||||
TDShapeType.Hexagon,
|
||||
TDShapeType.Triangle,
|
||||
TDShapeType.WhiteArrow,
|
||||
TDShapeType.Pentagram,
|
||||
].some(type => type === shape.type);
|
||||
};
|
||||
|
||||
export const useConfig = (app: TldrawApp): Record<Config['type'], Config> => {
|
||||
const state = app.useStore();
|
||||
const selectedShapes = TLDR.get_selected_shapes(state, app.currentPageId);
|
||||
@@ -74,6 +101,8 @@ export const useConfig = (app: TldrawApp): Record<Config['type'], Config> => {
|
||||
}
|
||||
if (_isSupportFill(cur)) {
|
||||
acc.fill.selectedShapes.push(cur);
|
||||
}
|
||||
if (_isSupportFont(cur)) {
|
||||
acc.font.selectedShapes.push(cur);
|
||||
}
|
||||
return acc;
|
||||
@@ -81,6 +110,7 @@ export const useConfig = (app: TldrawApp): Record<Config['type'], Config> => {
|
||||
_createInitConfig()
|
||||
);
|
||||
|
||||
// group
|
||||
if (
|
||||
selectedShapes.length === 1 &&
|
||||
selectedShapes[0].type === TDShapeType.Group
|
||||
@@ -91,6 +121,13 @@ export const useConfig = (app: TldrawApp): Record<Config['type'], Config> => {
|
||||
config.group.selectedShapes = selectedShapes;
|
||||
}
|
||||
|
||||
// lock
|
||||
if (selectedShapes.length === 1 && selectedShapes[0].isLocked) {
|
||||
config.unlock.selectedShapes = selectedShapes;
|
||||
} else {
|
||||
config.lock.selectedShapes = selectedShapes;
|
||||
}
|
||||
|
||||
config.deleteShapes.selectedShapes = selectedShapes;
|
||||
|
||||
return config;
|
||||
|
||||
@@ -3577,6 +3577,24 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
);
|
||||
};
|
||||
|
||||
lock = (ids = this.selectedIds): this => {
|
||||
if (ids.length === 0) {
|
||||
return this;
|
||||
}
|
||||
return this.set_state(
|
||||
this.commands.setShapesLockStatus(this, ids, true)
|
||||
);
|
||||
};
|
||||
|
||||
unlock = (ids = this.selectedIds): this => {
|
||||
if (ids.length === 0) {
|
||||
return this;
|
||||
}
|
||||
return this.set_state(
|
||||
this.commands.setShapesLockStatus(this, ids, false)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the fixed-aspect-ratio property of one or more shapes.
|
||||
* @param ids The ids to change (defaults to selection).
|
||||
|
||||
@@ -102,4 +102,9 @@ export interface Commands {
|
||||
updates: ({ id: string } & Partial<TDShape>)[],
|
||||
pageId: string
|
||||
): TldrawCommand;
|
||||
setShapesLockStatus(
|
||||
app: TldrawApp,
|
||||
ids: string[],
|
||||
isLocked: boolean
|
||||
): TldrawCommand;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user