mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-10-20 13:22:10 +08:00
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; }
113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
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;
|