mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-01 01:29:31 +08:00
151 lines
3.9 KiB
TypeScript
151 lines
3.9 KiB
TypeScript
import type { ShapeToolOption } from '@blocksuite/affine-gfx-shape';
|
|
import { render, type TemplateResult } from 'lit';
|
|
|
|
type TransformState = {
|
|
/** horizental offset base on center */
|
|
x?: number | string;
|
|
/** vertical offset base on center */
|
|
y?: number | string;
|
|
/** scale */
|
|
s?: number;
|
|
/** z-index */
|
|
z?: number;
|
|
};
|
|
|
|
export type DraggableShape = {
|
|
name: ShapeToolOption['shapeName'];
|
|
svg: TemplateResult;
|
|
style: {
|
|
default?: TransformState;
|
|
hover?: TransformState;
|
|
/**
|
|
* The next shape when previous shape is dragged outside toolbar
|
|
*/
|
|
next?: TransformState;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Helper function to build the CSS variables object for the shape
|
|
* @returns
|
|
*/
|
|
export const buildVariablesObject = (style: DraggableShape['style']) => {
|
|
const states: Array<keyof DraggableShape['style']> = [
|
|
'default',
|
|
'hover',
|
|
'next',
|
|
];
|
|
const variables: Array<keyof TransformState> = ['x', 'y', 's', 'z'];
|
|
|
|
const resolveValue = (
|
|
variable: keyof TransformState,
|
|
value: string | number
|
|
) => {
|
|
if (['x', 'y'].includes(variable)) {
|
|
return typeof value === 'number' ? `${value}px` : value;
|
|
}
|
|
return value;
|
|
};
|
|
|
|
return states.reduce((acc, state) => {
|
|
return {
|
|
...acc,
|
|
...variables.reduce((acc, variable) => {
|
|
const defaultValue = style.default?.[variable];
|
|
const value = style[state]?.[variable] ?? defaultValue;
|
|
if (value === undefined) return acc;
|
|
return {
|
|
...acc,
|
|
[`--${state}-${variable}`]: resolveValue(variable, value),
|
|
};
|
|
}, {}),
|
|
};
|
|
}, {});
|
|
};
|
|
|
|
// drag helper
|
|
export type ShapeDragEvent = {
|
|
inputType: 'mouse' | 'touch';
|
|
x: number;
|
|
y: number;
|
|
el: HTMLElement;
|
|
originalEvent: MouseEvent | TouchEvent;
|
|
};
|
|
|
|
export const touchResolver = (event: TouchEvent) =>
|
|
({
|
|
inputType: 'touch',
|
|
x: event.touches[0].clientX,
|
|
y: event.touches[0].clientY,
|
|
el: event.currentTarget as HTMLElement,
|
|
originalEvent: event,
|
|
}) satisfies ShapeDragEvent;
|
|
|
|
export const mouseResolver = (event: MouseEvent) =>
|
|
({
|
|
inputType: 'mouse',
|
|
x: event.clientX,
|
|
y: event.clientY,
|
|
el: event.currentTarget as HTMLElement,
|
|
originalEvent: event,
|
|
}) satisfies ShapeDragEvent;
|
|
|
|
// overlay helper
|
|
export const defaultDraggingInfo = {
|
|
startPos: { x: 0, y: 0 },
|
|
toolbarRect: {} as DOMRect,
|
|
edgelessRect: {} as DOMRect,
|
|
shapeRectOriginal: {} as DOMRect,
|
|
shapeEl: null as unknown as HTMLElement,
|
|
parentToMount: null as unknown as HTMLElement,
|
|
moved: false,
|
|
shape: null as unknown as DraggableShape,
|
|
style: {} as CSSStyleDeclaration,
|
|
};
|
|
export type DraggingInfo = typeof defaultDraggingInfo;
|
|
|
|
export const createShapeDraggingOverlay = (info: DraggingInfo) => {
|
|
const { edgelessRect, parentToMount } = info;
|
|
const overlay = document.createElement('div');
|
|
Object.assign(overlay.style, {
|
|
position: 'absolute',
|
|
top: '0',
|
|
left: '0',
|
|
width: edgelessRect.width + 'px',
|
|
// always clip
|
|
// height: toolbarRect.bottom - edgelessRect.top + 'px',
|
|
height: edgelessRect.height + 'px',
|
|
overflow: 'hidden',
|
|
zIndex: '9999',
|
|
|
|
// for debug purpose
|
|
// background: 'rgba(255, 0, 0, 0.1)',
|
|
});
|
|
|
|
const shape = document.createElement('div');
|
|
const shapeScaleWrapper = document.createElement('div');
|
|
Object.assign(shapeScaleWrapper.style, {
|
|
transform: 'scale(var(--s, 1))',
|
|
transition: 'transform 0.1s',
|
|
transformOrigin: 'var(--o, center)',
|
|
});
|
|
render(info.shape.svg, shapeScaleWrapper);
|
|
Object.assign(shape.style, {
|
|
position: 'absolute',
|
|
color: info.style.color,
|
|
stroke: info.style.stroke,
|
|
filter: `var(--shape-filter, ${info.style.filter})`,
|
|
transform: 'translate(var(--x, 0), var(--y, 0))',
|
|
left: 'var(--left, 0)',
|
|
top: 'var(--top, 0)',
|
|
cursor: 'grabbing',
|
|
transition: 'inherit',
|
|
});
|
|
|
|
shape.append(shapeScaleWrapper);
|
|
overlay.append(shape);
|
|
parentToMount.append(overlay);
|
|
|
|
return overlay;
|
|
};
|