Skip to content

Commit

Permalink
feat(core): 添加全局跨域通讯对象,并使用此技术重写模态框调出方法
Browse files Browse the repository at this point in the history
  • Loading branch information
enncy committed Nov 8, 2022
1 parent f1f4271 commit 81b1daf
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 101 deletions.
1 change: 1 addition & 0 deletions packages/core/assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ model-element.confirm .model-input {
position: fixed;
top: 0px;
left: 0px;
z-index: 9999999;
}
.model-wrapper {
position: fixed;
Expand Down
1 change: 1 addition & 0 deletions packages/core/assets/less/style.less
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ model-element.confirm {
position: fixed;
top: 0px;
left: 0px;
z-index: 9999999;
}

.model-wrapper {
Expand Down
136 changes: 136 additions & 0 deletions packages/core/src/interfaces/cors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {
listValues,
deleteValue,
setValue,
getValue,
addConfigChangeListener,
removeConfigChangeListener
} from '../utils/common';

/**
* 跨域脚本事件通讯
*/
export class CorsEventEmitter {
eventMap: Map<string, number> = new Map();

init() {
// 删除全部未处理的模态框临时变量
listValues().forEach((key) => {
if (/_temp_.event.[0-9a-z]{32}.(state|return|arguments)/.test(key)) {
deleteValue(key);
}
});
}

private uuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}

private eventKey(name: string) {
return 'cors.events.' + name;
}

tempKey(...args: string[]) {
return ['_temp_', ...args].join('.');
}

keyOfReturn(id: string) {
return this.tempKey('event', id, 'return');
}

keyOfArguments(id: string) {
return this.tempKey('event', id, 'arguments');
}

keyOfState(id: string) {
return this.tempKey('event', id, 'state');
}

/**
* 提交事件
* @param name 事件名
* @param args 事件参数
* @param callback 事件回调,可以接收返回值
*/
emit(name: string, args: any[], callback: (returnValue: any, remote: boolean) => void): void {
const id = this.uuid().replace(/-/g, '');
const key = this.eventKey(name);

/** 状态, 0 等待交互 , 1 确定 , 2 取消 , 后面紧跟着模态框中获取到的值,如果模态框类型是 prompt 则有值,否则为空字符串 */
setValue(this.keyOfState(id), 0);
/** 模态框所需参数 */
setValue(this.keyOfArguments(id), args);

const listenerId = addConfigChangeListener(this.keyOfState(id), (pre, curr, remote) => {
// 移除此监听器
removeConfigChangeListener(listenerId);
// 执行回调
callback(getValue(this.keyOfReturn(id)), remote);
// 移除冗余的本地临时存储变量
deleteValue(this.keyOfState(id));
deleteValue(this.keyOfReturn(id));
deleteValue(this.keyOfArguments(id));
});

/** 添加 id 到监听队列 */
setValue(key, (getValue(key) ? String(getValue(key)).split(',') : []).concat(id).join(','));
}

/**
* 监听跨域事件
* @param name 事件名
* @param handler 处理器,可以通过处理器返回任意值作为另外一端的回调值
* @returns
*/
on(name: string, handler: (args: any[]) => any): number {
const key = this.eventKey(name);
const originId = this.eventMap.get(key);
if (originId) {
return originId;
} else {
// 清空未处理的事件
setValue(key, '');
// 添加 models 监听队列
const id = addConfigChangeListener(key, async (pre, curr, remote) => {
if (remote) {
const list = String(curr).split(',');
// 处理队列
const id = list.pop();

if (id) {
// 设置返回参数
setValue(this.keyOfReturn(id), await handler(getValue(this.keyOfArguments(id))));

// 更新队列
setTimeout(() => {
// 这里改变参数,可以触发另一端的监听
setValue(this.keyOfState(id), 1);
// 完成监听,删除id
setValue(key, list.join(','));
}, 100);
}
}
});
this.eventMap.set(key, id);
return id;
}
}

off(name: string) {
const key = this.eventKey(name);
const originId = this.eventMap.get(key);
if (originId) {
this.eventMap.delete(key);
removeConfigChangeListener(originId);
}
}
}

/**
* 全局跨域对象
*/
export const cors = new CorsEventEmitter();
24 changes: 17 additions & 7 deletions packages/core/src/interfaces/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,25 @@ export interface ScriptOptions<T extends Record<string, Config>> {
hideInPanel?: boolean;
}

export class BaseScript {
/** 在脚本加载时立即运行的钩子 */
onstart?: (...args: any) => any;
/** 在页面初始化完成时(元素可被访问)时运行的钩子 */
onactive?: (...args: any) => any;
/** 在页面完全加载时运行的钩子 */
oncomplete?: (...args: any) => any;
/** 在页面离开时执行的钩子 */
onbeforeunload?: (...args: any) => any;
}

export interface ScriptEvent {
[name: string]: any;
}

/**
* 脚本
*/
export class Script<T extends Record<string, Config> = Record<string, Config>> {
export class Script<T extends Record<string, Config> = Record<string, Config>> extends BaseScript {
/** 名字 */
name: string;
/** 匹配路径 */
Expand All @@ -33,12 +48,6 @@ export class Script<T extends Record<string, Config> = Record<string, Config>> {
private _configs?: ScriptConfigsProvider<T>;
/** 存储已经处理过的 configs 对象,避免重复调用方法 */
private _resolvedConfigs?: T;
/** 在脚本加载时立即运行的钩子 */
onstart?: (...args: any) => any;
/** 在页面初始化完成时(元素可被访问)时运行的钩子 */
onactive?: (...args: any) => any;
/** 在页面完全加载时运行的钩子 */
oncomplete?: (...args: any) => any;

get configs() {
if (!this._resolvedConfigs) {
Expand Down Expand Up @@ -66,6 +75,7 @@ export class Script<T extends Record<string, Config> = Record<string, Config>> {
onactive?: (this: Script<T>, ...args: any) => any;
oncomplete?: (this: Script<T>, ...args: any) => any;
}) {
super();
this.name = name;
this.namespace = namespace;
this.url = url;
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/projects/cx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export const CXProject: Project = {
if (list.length) {
list[0].click();
} else {
this.notes[0] = '全部任务已完成!';
$model('prompt', {
content: '全部任务已完成!',
onConfirm(val) {
Expand Down
105 changes: 17 additions & 88 deletions packages/core/src/projects/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,10 @@ import { MessageElement } from '../elements/message';
import { ModelElement } from '../elements/model';

import { Config } from '../interfaces/config';
import { cors } from '../interfaces/cors';
import { Project } from '../interfaces/project';
import { Script } from '../interfaces/script';
import {
addConfigChangeListener,
getValue,
getMatchedScripts,
namespaceKey,
removeConfigChangeListener,
setValue,
deleteValue,
listValues
} from '../utils/common';
import { getMatchedScripts, namespaceKey } from '../utils/common';
import { el, enableElementDraggable, tooltip } from '../utils/dom';
import { StartConfig } from '../utils/start';
import { humpToTarget } from '../utils/string';
Expand Down Expand Up @@ -294,35 +286,14 @@ const InitPanelScript = new Script({

/** 初始化模态框系统 */
const initModelSystem = () => {
// 删除全部未处理的模态框临时变量
listValues().forEach((key) => {
if (/model-[0-9a-z]{32}-(state|arguments)/.test(key)) {
deleteValue(key);
}
});
// 添加 models 监听队列
addConfigChangeListener('init.panel.models', (pre, curr, remote) => {
if (remote) {
const list = String(curr).split(',');
// 处理队列
const id = list.pop();

if (id) {
const { type, ...attrs }: ModelAttrs & { type: ModelElement['type'] } = getValue(id + '-arguments');
attrs.onConfirm = (val) => {
setValue(id + '-state', '1-' + val);
};
attrs.onCancel = () => {
setValue(id + '-state', '2-');
};
// 弹出模态框
$model(type, attrs);
// 更新队列
setTimeout(() => {
setValue('init.panel.models', list.join(','));
}, 1000);
}
}
cors.on('model', async ([type, _attrs]) => {
return new Promise((resolve, reject) => {
const attrs = _attrs as ModelAttrs;
attrs.onCancel = () => resolve('');
attrs.onConfirm = resolve;
$model(type, attrs);
});
});
};

Expand All @@ -337,10 +308,9 @@ const InitPanelScript = new Script({
/** 在顶级页面显示操作面板 */
if (matchedScripts.length !== 0 && self === top) {
// 随机位置插入操作面板到页面
const panel = el('span');
const panel = el('div');
panel.attachShadow({ mode: 'closed' }).append(modelContainer, container);
const children = allVisibleChildren(document.body);
children[random(0, children.length - 1)].after(panel);
document.body.children[random(0, document.body.children.length - 1)].after(panel);

render();
initModelSystem();
Expand All @@ -362,9 +332,9 @@ export const InitProject: Project = {
* 创建一个模态框代替原生的 alert, confirm, prompt
*/
export function $model(type: ModelElement['type'], attrs: ModelAttrs) {
const { disableWrapperCloseable, onConfirm, onCancel, ..._attrs } = attrs;

if (self === top) {
const { disableWrapperCloseable, onConfirm, onCancel, ..._attrs } = attrs;

modelContainer.append(
el('div', { className: 'model-wrapper' }, (wrapper) => {
const model = el('model-element', {
Expand All @@ -391,35 +361,13 @@ export function $model(type: ModelElement['type'], attrs: ModelAttrs) {
})
);
} else {
const id = 'model-' + uuid().replace(/-/g, '');

/** 状态, 0 等待交互 , 1 确定 , 2 取消 , 后面紧跟着模态框中获取到的值,如果模态框类型是 prompt 则有值,否则为空字符串 */
setValue(id + '-state', ['0', ''].join('-'));
/** 模态框所需参数 */
setValue(id + '-arguments', { type, ...attrs });

const listenerId = addConfigChangeListener(id + '-state', (pre, curr) => {
const args = String(curr).split('-');

if (args[0] === '1') {
onConfirm?.(args[1]);
cors.emit('model', [type, attrs], (args, remote) => {
if (args) {
attrs.onConfirm?.(args);
} else {
onCancel?.();
attrs.onCancel?.();
}

// 移除冗余的本地临时存储变量
deleteValue(id + '-state');
deleteValue(id + '-arguments');

// 移除此监听器
removeConfigChangeListener(listenerId);
});

/** 添加 id 到监听队列 */
setValue(
'init.panel.models',
(getValue('init.panel.models') ? String(getValue('init.panel.models')).split(',') : []).concat(id).join(',')
);
}
}

Expand Down Expand Up @@ -449,25 +397,6 @@ function addFunctionEventListener(obj: any, type: string) {
};
}

function uuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}

function random(min: number, max: number) {
return Math.round(Math.random() * (max - min)) + min;
}

function allVisibleChildren(element: HTMLElement) {
const list: HTMLElement[] = [];
for (const child of Array.from(element.children) as HTMLElement[]) {
list.push(child, ...allVisibleChildren(child));
if (child.style.display !== 'none' && child.hidden !== false && child.style.visibility !== 'hidden') {
list.push(...allVisibleChildren(child));
}
}
return list;
}
Loading

0 comments on commit 81b1daf

Please sign in to comment.