oceanzhu 3fb880d09b refactor(stage):重构魔方编辑器stage代码
Squash merge branch 'feature/oc_actionbox_879360293' into 'master'
    对魔方编辑器核心代码stage进行重构,这部分代码主要是负责编辑器中间画布区域的处理,包括渲染一个所见即所得的画布,支持组件的增删改查和拖拽、高亮操作。
    旧代码存在的问题以及解决方案:
    1、过多暴露属性和循环引用,导致stageCore、stageDragResize、StageMultiDragResize、StageRender、StageMask、StageHighlight之间形成复杂的网状依赖,非常不可控。StageCore负责创建后面5个类的实例,并把这些实例作为自己的公共属性,同时core把自己的this传给这些实例,这些实例就会通过core传进来的this,通过core间接的访问其它实例的方法和属性,比如在stageDragResize中可能存在这样的一个访问:this.core.stageRender.contentWindow.document
    解决方案:
    1)、属性尽量设置为私有,对外暴露方法,不暴露属性;
    2)、core避免向其它类传递this,改为传递接口,需要什么就传什么

    2、事件传递较多,跳来跳去,定位问题较为困难
    解决方案:
    重新梳理各个类的职责,尽量在类中闭环,减少事件传递。
    新增了actionManager类,core负责管理render、mask、actionManager三个类;
    actionManager负责管理单选、多选、高亮三个类,同时将mask中的事件监听,转移到actionManager监听,actionManager形成单选、多选、高亮行为后,直接调动单选、多选、高亮完成功能。

    3、存在一些重复代码
    主要是拖拽框的代码在单选、多选、高亮中各自维护,改为统一维护

    4、多选不支持辅助线对齐
    将单选中的moveableOption管理逻辑抽取出来成为单选和多选的父类,使多选支持辅助线对齐

    本次改动取消了一些对外暴露的属性,moveableOption回调函数签名也有变化,详细情况如下:
    删除stageCore公共属性:
    public selectedDom: HTMLElement | undefined;
    public selectedDomList: HTMLElement[] = [];
    public highlightedDom: Element | undefined;
    public dr: StageDragResize;
    public multiDr: StageMultiDragResize;
    public highlightLayer: StageHighlight;
    public config: StageCoreConfig;
    public zoom = DEFAULT_ZOOM;
    public containerHighlightClassName: string;
    public containerHighlightDuration: number;
    public containerHighlightType?: ContainerHighlightType;
    public isContainer: IsContainer;

    stageCore入参改动:
    这两个参数原本定义:
    moveableOptions?: ((core?: StageCore) => MoveableOptions) | MoveableOptions;
    multiMoveableOptions?: ((core?: StageCore) => MoveableOptions) | MoveableOptions;
    修改后定义:
    moveableOptions?: CustomizeMoveableOptions;
    multiMoveableOptions?: CustomizeMoveableOptions;
    CustomizeMoveableOptions =
      | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions)
      | MoveableOptions
      | undefined;
    export interface CustomizeMoveableOptionsCallbackConfig {
      targetElId?: string;
    }
2022-11-24 21:19:56 +08:00

113 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

export const asyncLoadJs = (() => {
// 正在加载或加载成功的存入此Map中
const documentMap = new Map();
return (url: string, crossOrigin?: string, document = globalThis.document) => {
let loaded = documentMap.get(document);
if (!loaded) {
loaded = new Map();
documentMap.set(document, loaded);
}
// 正在加载或已经加载成功的,直接返回
if (loaded.get(url)) return loaded.get(url);
const load = new Promise<void>((resolve, reject) => {
const script = document.createElement('script');
script.type = 'text/javascript';
if (crossOrigin) {
script.crossOrigin = crossOrigin;
}
script.src = url;
document.body.appendChild(script);
script.onload = () => {
resolve();
};
script.onerror = () => {
reject(new Error('加载失败'));
};
setTimeout(() => {
reject(new Error('timeout'));
}, 60 * 1000);
}).catch((err) => {
// 加载失败的从map中移除第二次加载时可以再次执行加载
loaded.delete(url);
throw err;
});
loaded.set(url, load);
return loaded.get(url);
};
})();
export const asyncLoadCss = (() => {
// 正在加载或加载成功的存入此Map中
const documentMap = new Map();
return (url: string, document = globalThis.document) => {
let loaded = documentMap.get(document);
if (!loaded) {
loaded = new Map();
documentMap.set(document, loaded);
}
// 正在加载或已经加载成功的,直接返回
if (loaded.get(url)) return loaded.get(url);
const load = new Promise<void>((resolve, reject) => {
const node = document.createElement('link');
node.rel = 'stylesheet';
node.href = url;
document.head.appendChild(node);
node.onload = () => {
resolve();
};
node.onerror = () => {
reject(new Error('加载失败'));
};
setTimeout(() => {
reject(new Error('timeout'));
}, 60 * 1000);
}).catch((err) => {
// 加载失败的从map中移除第二次加载时可以再次执行加载
loaded.delete(url);
throw err;
});
loaded.set(url, load);
return loaded.get(url);
};
})();
export const addClassName = (el: Element, doc: Document, className: string) => {
const oldEl = doc.querySelector(`.${className}`);
if (oldEl && oldEl !== el) removeClassName(oldEl, className);
if (!el.classList.contains(className)) el.classList.add(className);
};
export const removeClassName = (el: Element, className: string) => {
el.classList.remove(className);
};
export const removeClassNameByClassName = (doc: Document, className: string) => {
const el: HTMLElement | null = doc.querySelector(`.${className}`);
el?.classList.remove(className);
return el;
};
export const injectStyle = (doc: Document, style: string) => {
const styleEl = doc.createElement('style');
styleEl.innerHTML = style;
doc.head.appendChild(styleEl);
return styleEl;
};
export const createDiv = ({ className, cssText }: { className: string; cssText: string }) => {
const el = globalThis.document.createElement('div');
el.className = className;
el.style.cssText = cssText;
return el;
};
export const getDocument = () => globalThis.document;