Browse Source

refactor: optimize guide rendering performance and enhance drag event handling

carlos/505-improve-grapesjs-absolute-mode
Carlos 11 months ago
parent
commit
7a105106d2
  1. 205
      packages/core/src/commands/view/ComponentDrag.ts

205
packages/core/src/commands/view/ComponentDrag.ts

@ -10,7 +10,7 @@ import type ComponentView from '../../dom_components/view/ComponentView';
const evName = 'dmode';
export default {
run(editor, _sender, opts = {}) {
run(editor, _sender, opts = {} as ComponentDragOpts) {
bindAll(
this,
'setPosition',
@ -23,8 +23,10 @@ export default {
'getGuidesTarget',
);
if (!opts.target) throw new Error('Target option is required');
const config = {
doc: opts.target?.getEl()?.ownerDocument,
doc: opts.target.getEl()?.ownerDocument,
onStart: this.onStart,
onEnd: this.onEnd,
onDrag: this.onDrag,
@ -54,8 +56,6 @@ export default {
}
opts.event && drg.start(opts.event);
// TODO: check this
opts.addStyle?.({ component: this.target, styles: {}, partial: false });
this.toggleDrag(true);
this.em.trigger(`${evName}:start`, this.getEventOpts());
@ -63,11 +63,14 @@ export default {
},
getEventOpts() {
const activeGuides = this.guidesTarget?.filter((item) => item.active) ?? [];
return {
mode: this.opts?.mode,
target: this.target,
guidesTarget: this.guidesTarget,
guidesStatic: this.guidesStatic,
mode: this.opts.mode,
component: this.target,
target: this.target, // deprecated
guidesTarget: this.guidesTarget, // deprecated
guidesStatic: this.guidesStatic, // deprecated
guidesMatched: this.getMatchedGuides(activeGuides).map((item) => item.matched),
};
},
@ -88,7 +91,7 @@ export default {
if (!guidesEl) {
const { editor, em, opts } = this;
const pfx = editor!.getConfig().stylePrefix ?? '';
const pfx = editor.getConfig().stylePrefix ?? '';
const elInfoX = document.createElement('div');
const elInfoY = document.createElement('div');
const guideContent = `<div class="${pfx}guide-info__line ${pfx}danger-bg">
@ -102,7 +105,7 @@ export default {
elInfoY.innerHTML = guideContent;
guidesEl.appendChild(elInfoX);
guidesEl.appendChild(elInfoY);
editor!.Canvas.getGlobalToolsEl()?.appendChild(guidesEl);
editor.Canvas.getGlobalToolsEl()?.appendChild(guidesEl);
this.guidesEl = guidesEl;
this.elGuideInfoX = elInfoX;
this.elGuideInfoY = elInfoY;
@ -113,7 +116,7 @@ export default {
'canvas:update frame:scroll',
debounce(() => {
this.updateGuides();
opts?.debug && this.guides?.forEach((item) => this.renderGuide(item));
opts.debug && this.guides?.forEach((item) => this.renderGuide(item));
}, 200),
);
}
@ -123,7 +126,7 @@ export default {
getGuidesStatic() {
let result: Guide[] = [];
const el = this.target?.getEl();
const el = this.target.getEl();
const parentNode = el?.parentElement;
if (!parentNode) return [];
each(
@ -135,7 +138,7 @@ export default {
},
getGuidesTarget() {
const targetEl = this.target?.getEl();
const targetEl = this.target.getEl();
if (!targetEl) return [];
return this.getElementGuides(targetEl);
},
@ -184,7 +187,7 @@ export default {
},
renderGuide(item) {
if (this.opts?.skipGuidesRender) return;
if (this.opts.skipGuidesRender) return;
const el = item.guide ?? document.createElement('div');
const un = 'px';
const guideSize = item.active ? 2 : 1;
@ -214,7 +217,7 @@ export default {
},
getElementPos(el) {
return this.editor!.Canvas.getElementPos(el, { noScroll: 1 });
return this.editor.Canvas.getElementPos(el, { noScroll: 1 });
},
getElementGuides(el) {
@ -235,7 +238,7 @@ export default {
];
const guides = guidePoints.map((guidePoint) => {
const guide = opts?.debug ? this.renderGuide(guidePoint) : undefined;
const guide = opts.debug ? this.renderGuide(guidePoint) : undefined;
// INFO: origin, originRect, and guide are repeated to don't introduce breaking changes
return {
...guidePoint,
@ -283,11 +286,11 @@ export default {
getPosition() {
const { target, isTran } = this;
const targetStyle = target?.getStyle();
const targetStyle = target.getStyle();
const transform = targetStyle?.transform as string | undefined;
const left = targetStyle?.left as string | undefined;
const top = targetStyle?.top as string | undefined;
const transform = targetStyle.transform as string;
const left = targetStyle.left as string;
const top = targetStyle.top as string;
let x = 0;
let y = 0;
@ -304,7 +307,7 @@ export default {
},
setPosition({ x, y, end, position, width, height }) {
const { target, isTran, em } = this;
const { target, isTran, em, opts } = this;
const unit = 'px';
const __p = !end; // Indicate if partial change
const left = `${parseInt(`${x}`, 10)}${unit}`;
@ -312,7 +315,7 @@ export default {
let styleUp = {};
if (isTran) {
let transform = (target?.getStyle()?.transform ?? '') as string;
let transform = (target.getStyle()?.transform ?? '') as string;
transform = this.setTranslate(transform, 'x', left);
transform = this.setTranslate(transform, 'y', top);
styleUp = { transform, __p };
@ -326,33 +329,38 @@ export default {
styleUp = style;
}
target?.addStyle(styleUp, { avoidStore: !end });
em?.Styles.__emitCmpStyleUpdate(styleUp, { components: em.getSelected() });
if (opts.addStyle) {
opts.addStyle({ component: target, styles: styleUp, partial: !end });
} else {
target.addStyle(styleUp, { avoidStore: !end });
}
em.Styles.__emitCmpStyleUpdate(styleUp, { components: em.getSelected() });
},
_getDragData() {
const { target } = this;
return {
target,
parent: target?.parent(),
index: target?.index(),
parent: target.parent(),
index: target.index(),
};
},
onStart(event) {
const { target, editor, isTran, opts } = this;
const { Canvas } = editor!;
const style = target?.getStyle();
const { Canvas } = editor;
const style = target.getStyle();
const position = 'absolute';
const relPos = [position, 'relative'];
opts?.onStart?.(this._getDragData());
opts.onStart?.(this._getDragData());
if (isTran) return;
if (style?.position !== position) {
const targetEl = target?.getEl();
const targetEl = target.getEl();
const offset = targetEl ? Canvas.offset(targetEl) : { left: 0, top: 0, width: 0, height: 0 };
let { left, top, width, height } = offset;
let parent = target?.parent();
let parent = target.parent();
let parentRel;
// Check for the relative parent
@ -366,7 +374,7 @@ export default {
} while (parent && !parentRel);
// Center the target to the pointer position (used in Droppable for Blocks)
if (opts?.center) {
if (opts.center) {
const { x, y } = Canvas.getMouseRelativeCanvas(event as MouseEvent);
left = x;
top = y;
@ -391,15 +399,17 @@ export default {
const { guidesTarget, opts } = this;
this.updateGuides(guidesTarget);
opts?.debug && guidesTarget?.forEach((item) => this.renderGuide(item));
opts?.guidesInfo && this.renderGuideInfo(guidesTarget?.filter((item) => item.active) ?? []);
opts?.onDrag?.(this._getDragData());
opts.debug && guidesTarget?.forEach((item) => this.renderGuide(item));
opts.guidesInfo && this.renderGuideInfo(guidesTarget?.filter((item) => item.active) ?? []);
opts.onDrag?.(this._getDragData());
this.em.trigger(`${evName}:move`, this.getEventOpts());
},
onEnd(ev, _dragger, opt) {
const { editor, opts, id } = this;
opts?.onEnd?.(ev, opt, { event: ev, ...opt, ...this._getDragData() });
editor!.stopCommand(`${id}`);
opts.onEnd?.(ev, opt, { event: ev, ...opt, ...this._getDragData() });
editor.stopCommand(`${id}`);
this.hideGuidesInfo();
this.em.trigger(`${evName}:end`, this.getEventOpts());
@ -418,7 +428,7 @@ export default {
this.hideGuidesInfo();
matchedGuides.forEach((matchedGuide) => {
// TODO: improve this
if (!this.opts?.skipGuidesRender) {
if (!this.opts.skipGuidesRender) {
this.renderSingleGuideInfo(matchedGuide);
}
@ -513,15 +523,21 @@ export default {
const { ppfx, editor } = this;
const methodCls = enable ? 'add' : 'remove';
const classes = [`${ppfx}is__grabbing`];
const { Canvas } = editor!;
const { Canvas } = editor;
const body = Canvas.getBody();
classes.forEach((cls) => body.classList[methodCls](cls));
Canvas[enable ? 'startAutoscroll' : 'stopAutoscroll']();
},
// These properties values are set in the run method, they need to be initialized here to avoid TS errors
editor: undefined as unknown as Editor,
em: undefined as unknown as EditorModel,
opts: undefined as unknown as ComponentDragOpts,
target: undefined as unknown as Component,
} as CommandObject<ComponentDragOpts, ComponentDragProps>;
interface ComponentDragProps {
editor?: Editor;
editor: Editor;
em?: EditorModel;
guides?: Guide[];
guidesContainer?: HTMLElement;
@ -529,15 +545,15 @@ interface ComponentDragProps {
guidesStatic?: Guide[];
guidesTarget?: Guide[];
isTran?: boolean;
opts?: ComponentDragOpts;
target?: Component;
opts: ComponentDragOpts;
target: Component;
elGuideInfoX?: HTMLElement;
elGuideInfoY?: HTMLElement;
elGuideInfoContentX?: HTMLElement;
elGuideInfoContentY?: HTMLElement;
dragger?: Dragger;
getEventOpts: () => { mode: string; target: Component; guidesTarget: Guide[]; guidesStatic: Guide[] };
getEventOpts: () => ComponentDragEventProps;
stop: () => void;
setupGuides: () => void;
getGuidesContainer: () => HTMLElement;
@ -552,7 +568,7 @@ interface ComponentDragProps {
setTranslate: (transform: string, axis: string, value: string) => string;
getPosition: DraggerOptions['getPosition'];
setPosition: (data: any) => void; // TODO: fix any
_getDragData: () => { target?: Component; parent?: Component; index?: number };
_getDragData: () => { target: Component; parent?: Component; index?: number };
onStart: DraggerOptions['onStart'];
onDrag: DraggerOptions['onDrag'];
onEnd: DraggerOptions['onEnd'];
@ -564,33 +580,108 @@ interface ComponentDragProps {
}
type ComponentDragOpts = {
target: Component;
center?: number;
debug?: boolean;
dragger?: DraggerOptions;
event?: Event;
guidesInfo?: number;
mode?: 'absolute' | 'translate';
target?: Component;
skipGuidesRender?: boolean;
addStyle?: (data: { component?: Component; styles?: Record<string, unknown>; partial?: boolean }) => void;
onDrag?: (data: any) => Editor; // TODO: fix any
onEnd?: (ev: Event, opt: any, data: any) => void; // TODO: fix any
onStart?: (data: any) => Editor; // TODO: fix any
onStart?: (data: any) => Editor;
onDrag?: (data: any) => Editor;
onEnd?: (ev: Event, opt: any, data: any) => void;
};
/**
* Represents the properties of the drag events (eg., dmode:start, dmode:active, dmode:end).
*/
type ComponentDragEventProps = {
/**
* The mode of the drag (absolute or translate).
*/
mode: ComponentDragOpts['mode'];
/**
* The component being dragged.
* @deprecated Use `component` instead.
*/
target: Component;
/**
* The component being dragged.
*/
component: Component;
/**
* The guides of the component being dragged.
* @deprecated Use `guidesMatched` instead.
*/
guidesTarget: Guide[];
/**
* All the guides except the ones of the component being dragged.
* @deprecated Use `guidesMatched` instead.
*/
guidesStatic: Guide[];
/**
* The guides that are being matched.
*/
guidesMatched: Guide[];
};
/**
* Represents a guide used during component dragging.
*/
type Guide = {
/**
* The type of the guide (e.g., 't', 'b', 'l', 'r', 'x', 'y').
*/
type: string;
/**
* The vertical position of the guide.
*/
y: number;
/**
* The horizontal position of the guide.
*/
x: number;
/**
* The component associated with the guide.
*/
component: Component;
/**
* The view of the component associated with the guide.
*/
componentView: ComponentView;
/**
* The HTML element associated with the guide.
* @deprecated Use `componentEl` instead.
*/
origin: HTMLElement;
/**
* The HTML element associated with the guide.
*/
componentEl: HTMLElement;
origin: HTMLElement; // @deprecated: use componentEl instead
/**
* The rectangle (position and dimensions) of the guide's element.
* @deprecated Use `componentElRect` instead.
*/
originRect: ComponentOrigRect;
/**
* The rectangle (position and dimensions) of the guide's element.
*/
componentElRect: ComponentOrigRect;
originRect: ComponentOrigRect; // @deprecated: use componentElRect instead
/**
* The HTML element representing the guide.
* @deprecated Use `guideEl` instead.
*/
guide?: HTMLElement;
/**
* The HTML element representing the guide.
*/
guideEl?: HTMLElement;
guide?: HTMLElement; // @deprecated: use guideEl instead
active?: boolean; // TODO: is this used?
/**
* Indicates whether the guide is active.
* @todo Check if this property is used.
*/
active?: boolean;
};
type MatchedGuide = {
@ -610,13 +701,3 @@ type ComponentRect = { left: number; width: number; top: number; height: number
type ComponentOrigRect = ComponentRect & { rect: ComponentRect };
type ElGuideInfoKey = 'elGuideInfoX' | 'elGuideInfoY';
type ElGuideInfoContentKey = 'elGuideInfoContentX' | 'elGuideInfoContentY';
// TODO: should we export this type? and if so, we should create 1 type for every event?
export type DragEventProps = {
originComponent?: Component;
originComponentView?: ComponentView;
originGuides?: MatchedGuide[];
matchedComponent?: Component;
matchedComponentView?: ComponentView;
matchedGuides?: MatchedGuide[];
};

Loading…
Cancel
Save