Files
AFFiNE-Mirror/blocksuite/affine/gfx/mindmap/src/toolbar/basket-elements.ts
T

209 lines
5.7 KiB
TypeScript

import { addAttachments } from '@blocksuite/affine-block-attachment';
import { addImages } from '@blocksuite/affine-block-image';
import {
CanvasElementType,
EdgelessCRUDIdentifier,
} from '@blocksuite/affine-block-surface';
import {
insertEdgelessTextCommand,
mountTextElementEditor,
} from '@blocksuite/affine-gfx-text';
import {
MAX_IMAGE_WIDTH,
type MindmapStyle,
TextElementModel,
} from '@blocksuite/affine-model';
import {
FeatureFlagService,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import { openFileOrFiles } from '@blocksuite/affine-shared/utils';
import { Bound } from '@blocksuite/global/gfx';
import type { BlockComponent } from '@blocksuite/std';
import type { TemplateResult } from 'lit';
import * as Y from 'yjs';
export type ConfigProperty = 'x' | 'y' | 'r' | 's' | 'z' | 'o';
export type ConfigState = 'default' | 'active' | 'hover' | 'next';
export type ConfigStyle = Partial<Record<ConfigProperty, number | string>>;
export type ToolConfig = Record<ConfigState, ConfigStyle>;
export type DraggableTool = {
name: 'text' | 'mindmap' | 'media';
icon: TemplateResult;
config: ToolConfig;
standardWidth?: number;
render: (bound: Bound, edgeless: BlockComponent) => Promise<string | null>;
};
const unitMap = { x: 'px', y: 'px', r: 'deg', s: '', z: '', o: '' };
export const textConfig: ToolConfig = {
default: { x: -20, y: -8, r: 7.74, s: 0.92, z: 3 },
active: { x: -22, y: -9, r: -8, s: 0.92 },
hover: { x: -22, y: -9, r: -8, s: 1, z: 3 },
next: { x: -22, y: 64, r: 0 },
};
export const mindmapConfig: ToolConfig = {
default: { x: 4, y: -4, s: 1, r: -7, z: 2 },
active: { x: 11, y: -14, r: 9, s: 1 },
hover: { x: 11, y: -14, r: 9, s: 1.16, z: 3 },
next: { y: 64, r: 0 },
};
export const mediaConfig: ToolConfig = {
default: { x: -20, y: -15, r: 23, s: 1.2, z: 1 },
active: { x: -25, y: -20, r: -9, s: 1.2 },
hover: { x: -25, y: -20, r: -9, s: 1.5, z: 3 },
next: { y: 64, r: 0 },
};
export const getMindmapRender =
(mindmapStyle: MindmapStyle): DraggableTool['render'] =>
async (bound, edgeless) => {
const [x, y, _, h] = bound.toXYWH();
const rootW = 145;
const rootH = 50;
const nodeW = 80;
const nodeH = 35;
const centerVertical = y + h / 2;
const rootX = x;
const rootY = centerVertical - rootH / 2;
type MindMapNode = {
children: MindMapNode[];
text: string;
xywh: string;
};
const root: MindMapNode = {
children: [],
text: 'Mind Map',
xywh: `[${rootX},${rootY},${rootW},${rootH}]`,
};
for (let i = 0; i < 3; i++) {
const nodeX = x + rootW + 300;
const nodeY = centerVertical - nodeH / 2 + (i - 1) * 50;
root.children.push({
children: [],
text: 'Text',
xywh: `[${nodeX},${nodeY},${nodeW},${nodeH}]`,
});
}
const crud = edgeless.std.get(EdgelessCRUDIdentifier);
const mindmapId = crud.addElement('mindmap', {
style: mindmapStyle,
children: root,
}) as string;
edgeless.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
control: 'toolbar:dnd', // for now we use toolbar:dnd for all mindmap creation here
page: 'whiteboard editor',
module: 'toolbar',
segment: 'toolbar',
type: 'mindmap',
});
return mindmapId;
};
export const textRender: DraggableTool['render'] = async (bound, edgeless) => {
const vCenter = bound.y + bound.h / 2;
const w = 100;
const h = 32;
const flag = edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_edgeless_text');
let id: string;
if (flag) {
const [_, { textId }] = edgeless.std.command.exec(
insertEdgelessTextCommand,
{
x: bound.x,
y: vCenter - h / 2,
}
);
id = textId!;
} else {
const crud = edgeless.std.get(EdgelessCRUDIdentifier);
id = crud.addElement(CanvasElementType.TEXT, {
xywh: new Bound(bound.x, vCenter - h / 2, w, h).serialize(),
text: new Y.Text(),
}) as string;
edgeless.doc.captureSync();
const textElement = crud.getElementById(id);
if (!(textElement instanceof TextElementModel)) {
console.error('Cannot mount text editor on a non-text element');
return null;
}
mountTextElementEditor(textElement, edgeless);
}
edgeless.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
control: 'toolbar:dnd',
page: 'whiteboard editor',
module: 'toolbar',
segment: 'toolbar',
type: 'text',
});
return id;
};
export const mediaRender: DraggableTool['render'] = async (bound, edgeless) => {
let file: File | null = null;
try {
file = await openFileOrFiles();
} catch (e) {
console.error(e);
return null;
}
if (!file) return null;
// image
if (file.type.startsWith('image/')) {
const [id] = await addImages(edgeless.std, [file], {
point: [bound.x, bound.y],
maxWidth: MAX_IMAGE_WIDTH,
transformPoint: false,
});
if (id) return id;
return null;
}
// attachment
const [id] = await addAttachments(
edgeless.std,
[file],
[bound.x, bound.y],
false
);
return id;
};
const toolStyle2StyleObj = (state: ConfigState, style: ConfigStyle = {}) => {
const styleObj = {} as Record<string, string>;
for (const [key, value] of Object.entries(style)) {
styleObj[`--${state}-${key}`] = `${value}${unitMap[key as ConfigProperty]}`;
}
return styleObj;
};
export const toolConfig2StyleObj = (config: ToolConfig) => {
const styleObj = {} as Record<string, string>;
for (const [state, style] of Object.entries(config)) {
Object.assign(
styleObj,
toolStyle2StyleObj(state as ConfigState, {
...config.default,
...style,
})
);
}
return styleObj;
};