feat: 更新container版本,修改新的引入方式
This commit is contained in:
parent
6c8fd040da
commit
7327a134b1
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,3 +4,5 @@ build
|
||||
.cache
|
||||
.DS_Store
|
||||
*.log
|
||||
|
||||
.turbo
|
3
.npmrc
Normal file
3
.npmrc
Normal file
@ -0,0 +1,3 @@
|
||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||
@abearxiong:registry=https://npm.pkg.github.com
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kevisual/container",
|
||||
"version": "0.0.3",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/container.js",
|
||||
"publishConfig": {
|
||||
@ -37,6 +37,7 @@
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^28.0.0",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^12.1.0",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/react": "^18.3.4",
|
||||
@ -53,6 +54,7 @@
|
||||
"dependencies": {
|
||||
"nanoid": "^5.0.7",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"scheduler": "^0.23.2",
|
||||
"zustand": "^4.5.5"
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import copy from 'rollup-plugin-copy';
|
||||
import { dts } from 'rollup-plugin-dts';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
|
||||
/**
|
||||
* @type {import('rollup').RollupOptions}
|
||||
@ -18,6 +19,15 @@ const config1 = {
|
||||
plugins: [
|
||||
resolve(), // 使用 @rollup/plugin-node-resolve 解析 node_modules 中的模块
|
||||
commonjs(), //
|
||||
terser({
|
||||
format: {
|
||||
comments: false, // 移除注释
|
||||
},
|
||||
compress: {
|
||||
drop_console: true, // 移除 console.log
|
||||
drop_debugger: true, // 移除 debugger
|
||||
},
|
||||
}),
|
||||
typescript({
|
||||
allowImportingTsExtensions: true,
|
||||
noEmit: true,
|
||||
@ -50,9 +60,9 @@ const config2 = {
|
||||
declaration: false,
|
||||
}), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件
|
||||
// 复制/src/container.css 到dist/container.css
|
||||
copy({
|
||||
targets: [{ src: 'src/container.css', dest: 'dist' }],
|
||||
}),
|
||||
// copy({
|
||||
// targets: [{ src: 'src/container.css', dest: 'dist' }],
|
||||
// }),
|
||||
],
|
||||
};
|
||||
const config2Dts = {
|
||||
|
78
src/code-url.ts
Normal file
78
src/code-url.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { MD5 } from 'crypto-js';
|
||||
|
||||
type CodeUrl = {
|
||||
url: string; // 地址
|
||||
code?: string; // 代码
|
||||
id?: string; // codeId
|
||||
hash?: string;
|
||||
};
|
||||
export class CodeUrlManager {
|
||||
urls: CodeUrl[] = [];
|
||||
urlMap: { [key: string]: CodeUrl } = {};
|
||||
constructor() {
|
||||
this.urls = [];
|
||||
}
|
||||
addCode(code: string, id: string, hash?: string) {
|
||||
const urlMap = this.urlMap;
|
||||
const resultContent = (content: CodeUrl) => {
|
||||
const index = this.urls.findIndex((item) => item.id === id);
|
||||
if (index === -1) {
|
||||
this.urls.push(content);
|
||||
}
|
||||
return content;
|
||||
};
|
||||
if (urlMap[hash]) {
|
||||
// 可能id不同,但是hash相同。已经加载过了。
|
||||
const content = {
|
||||
...urlMap[hash],
|
||||
id,
|
||||
};
|
||||
return resultContent(content);
|
||||
}
|
||||
const _hash = hash || MD5(code).toString();
|
||||
if (urlMap[_hash]) {
|
||||
const content = {
|
||||
...urlMap[_hash],
|
||||
id,
|
||||
};
|
||||
return resultContent(content);
|
||||
}
|
||||
const index = this.urls.findIndex((item) => item.id === id);
|
||||
if (index > -1) {
|
||||
// 没有共同hash的值了,但是还是找了id,这个时候应该移除之前
|
||||
const url = this.urls[index];
|
||||
const list = this.urls.filter((item) => item.hash === url.hash);
|
||||
if (list.length <= 1) {
|
||||
// 这个hash的值没有人用了
|
||||
URL.revokeObjectURL(url.url);
|
||||
this.urlMap[url.hash] = null;
|
||||
}
|
||||
this.urls.splice(index, 1);
|
||||
}
|
||||
/**
|
||||
* 全新代码
|
||||
*
|
||||
*/
|
||||
const url = URL.createObjectURL(new Blob([code], { type: 'application/javascript' }));
|
||||
const content = {
|
||||
id,
|
||||
url,
|
||||
code,
|
||||
hash: _hash,
|
||||
};
|
||||
|
||||
this.urls.push(content);
|
||||
urlMap[_hash] = { ...content, id: null };
|
||||
return content;
|
||||
}
|
||||
removeUrl(id?: string, url?: string) {
|
||||
const index = this.urls.findIndex((item) => item.url === url || item.id === id);
|
||||
if (index > -1) {
|
||||
const url = this.urls[index];
|
||||
URL.revokeObjectURL(url.url);
|
||||
this.urlMap[url.hash] = null;
|
||||
this.urls.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
export const codeUrlManager = new CodeUrlManager();
|
@ -1,8 +1,10 @@
|
||||
import { Container, ContainerOpts } from './container';
|
||||
import { Container, ContainerOpts, createEmotion } from './container';
|
||||
import { addListener } from './listener/dom';
|
||||
|
||||
export type ContainerEditOpts = {
|
||||
edit?: boolean;
|
||||
mask?: boolean;
|
||||
initCss?: string;
|
||||
} & ContainerOpts;
|
||||
let isListener = false;
|
||||
export class ContainerEdit extends Container {
|
||||
@ -28,9 +30,12 @@ export class ContainerEdit extends Container {
|
||||
addListener(this.root);
|
||||
}
|
||||
}
|
||||
if (opts.initCss && this.root) {
|
||||
this.root.classList.add(kvContainerStyle);
|
||||
}
|
||||
renderChildren(cid: string, parentElement?: any, pid?: any) {
|
||||
const el = super.renderChildren(cid, parentElement, pid);
|
||||
}
|
||||
renderChildren(cid: string, parentElement?: any, pid?: any, isNew = true) {
|
||||
const el = super.renderChildren(cid, parentElement, pid, isNew);
|
||||
|
||||
if (el) {
|
||||
const computedStyle = window.getComputedStyle(el);
|
||||
@ -72,3 +77,28 @@ export class ContainerEdit extends Container {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const css = `.kv-container.active {
|
||||
background: #000 ;
|
||||
border: 2px solid #195ca9;
|
||||
z-index: 100;
|
||||
}
|
||||
.kv-container.dragging {
|
||||
opacity: 0.5; /* 拖动时降低透明度 */
|
||||
box-sizing: content-box;
|
||||
border: 2px dashed blue;
|
||||
}
|
||||
.kv-container.hover {
|
||||
cursor: move;
|
||||
}
|
||||
.kv-container > .resizer, .kv-container > .drag-title {
|
||||
display: none;
|
||||
}
|
||||
.kv-container.active > .resizer, .kv-container.active > .drag-title {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
export const emotion = createEmotion({ key: 'kv-container-style', speedy: true });
|
||||
|
||||
const kvContainerStyle = emotion.css(css);
|
||||
|
351
src/container-old.ts
Normal file
351
src/container-old.ts
Normal file
@ -0,0 +1,351 @@
|
||||
// @ts-nocheck
|
||||
import createEmotion from '@emotion/css/create-instance';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { ContainerEvent } from './event/continer';
|
||||
import { RenderCode } from './render';
|
||||
import { codeUrlManager } from './code-url';
|
||||
|
||||
const handleOneCode = async (data: RenderData) => {
|
||||
const { code } = data;
|
||||
const urlsBegin = ['http', 'https', 'blob', '//:'];
|
||||
if (typeof code === 'string' && code && urlsBegin.find((item) => code.startsWith(item))) {
|
||||
const importContent = await import(/* @vite-ignore */ code);
|
||||
const { render, unmount, ...rest } = importContent || {};
|
||||
const _data = {
|
||||
...data,
|
||||
code: {
|
||||
render,
|
||||
unmount,
|
||||
...rest,
|
||||
},
|
||||
};
|
||||
return _data;
|
||||
}
|
||||
if (typeof code === 'string' && data.codeId) {
|
||||
const codeId = data.codeId;
|
||||
const { url, hash } = codeUrlManager.addCode(code, codeId, data.hash);
|
||||
const importContent = await import(/* @vite-ignore */ url);
|
||||
const { render, unmount, ...rest } = importContent || {};
|
||||
const _data = {
|
||||
...data,
|
||||
codeId,
|
||||
hash,
|
||||
code: {
|
||||
render,
|
||||
unmount,
|
||||
...rest,
|
||||
},
|
||||
};
|
||||
return _data;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const handleCode = async (data: RenderData[]) => {
|
||||
const handleData = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = data[i];
|
||||
const _data = await handleOneCode(item);
|
||||
handleData.push(_data);
|
||||
}
|
||||
return handleData;
|
||||
};
|
||||
|
||||
export type RenderData = {
|
||||
id: string; // id不会重复
|
||||
children?: string[];
|
||||
parents?: string[];
|
||||
className?: string;
|
||||
style?: React.CSSProperties | { [key: string]: string };
|
||||
code: RenderCode | string;
|
||||
codeId?: string;
|
||||
shadowRoot?: boolean;
|
||||
hash?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type ContainerOpts = {
|
||||
root?: HTMLDivElement | string;
|
||||
shadowRoot?: ShadowRoot;
|
||||
destroy?: () => void;
|
||||
data?: RenderData[];
|
||||
showChild?: boolean;
|
||||
};
|
||||
|
||||
export class Container {
|
||||
data: RenderData[];
|
||||
root: HTMLDivElement;
|
||||
globalCss: any;
|
||||
showChild: boolean;
|
||||
event: EventEmitter;
|
||||
entryId: string = '';
|
||||
loading: boolean = false;
|
||||
loaded: boolean = false;
|
||||
constructor(opts: ContainerOpts) {
|
||||
const data = opts.data || [];
|
||||
this.loadData.apply(this, [data]);
|
||||
|
||||
const rootElement = typeof opts.root === 'string' ? document.querySelector(opts.root) : opts.root;
|
||||
this.root = rootElement || (document.body as any);
|
||||
this.globalCss = createEmotion({ key: 'css-global', speedy: true }).css;
|
||||
this.showChild = opts.showChild ?? true;
|
||||
const event = new EventEmitter();
|
||||
this.event = event;
|
||||
const listening = this.root.dataset.listening;
|
||||
if (listening !== 'true') {
|
||||
this.root.addEventListener('onContainer', (e: ContainerEvent) => {
|
||||
const { type, data } = e.data;
|
||||
this.event.emit(type, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
async loadData(data: RenderData[], key: string = '') {
|
||||
if (data.length === 0) {
|
||||
this.data = data;
|
||||
this.loaded = true;
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
this.data = await handleCode(data);
|
||||
this.loading = false;
|
||||
this.event.emit('loadedData' + key);
|
||||
this.loaded = true;
|
||||
}
|
||||
async getData({ id, codeId }: { id?: string; codeId?: string }) {
|
||||
if (id && codeId) {
|
||||
return this.data.find((item) => item.id === id && item.codeId === codeId);
|
||||
}
|
||||
if (id) {
|
||||
return this.data.find((item) => item.id === id);
|
||||
}
|
||||
if (codeId) {
|
||||
return this.data.find((item) => item.codeId === codeId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async updateDataCode(data: RenderData[]) {
|
||||
if (this.loading) {
|
||||
console.warn('loading');
|
||||
return;
|
||||
}
|
||||
const _data = this.data.map((item) => {
|
||||
const node = data.find((node) => node.codeId && node.codeId === item.codeId);
|
||||
if (node) {
|
||||
return {
|
||||
...item,
|
||||
...node,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
await this.loadData(_data);
|
||||
}
|
||||
async updateData(data: RenderData[]) {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
const _data = this.data.map((item) => {
|
||||
const node = data.find((node) => node.id === item.id);
|
||||
if (node) {
|
||||
return {
|
||||
...item,
|
||||
...node,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
console.log('updateData', _data.length, data.length);
|
||||
await this.loadData(_data);
|
||||
}
|
||||
|
||||
renderChildren(cid: string, parentElement?: any, pid?: any): void | HTMLDivElement {
|
||||
const data = this.data;
|
||||
console.log('renderChildren', cid, data);
|
||||
const globalCss = this.globalCss;
|
||||
if (!parentElement) {
|
||||
parentElement = this.root;
|
||||
this.root.dataset.entryId = cid;
|
||||
this.entryId = cid;
|
||||
}
|
||||
const renderChildren = this.renderChildren.bind(this);
|
||||
const node = data.find((node: RenderData) => node.id === cid);
|
||||
const event = this.event;
|
||||
if (!node) {
|
||||
console.warn('node not found', cid);
|
||||
return;
|
||||
}
|
||||
const { style, code } = node;
|
||||
const el = document.createElement('div');
|
||||
const root = parentElement.appendChild(el);
|
||||
const parentIds = parentElement.dataset.parentIds || '';
|
||||
const shadowRoot = node.shadowRoot ? el.attachShadow({ mode: 'open' }) : null;
|
||||
const { css, sheet, cache } = createEmotion({ key: 'css' });
|
||||
el.dataset.cid = cid;
|
||||
el.dataset.pid = pid;
|
||||
el.dataset.parentIds = parentIds ? parentIds + ',' + cid : cid;
|
||||
if (shadowRoot) {
|
||||
cache.insert = (selector: string, serialized: any, sheet: any, shouldCache: boolean) => {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = selector;
|
||||
shadowRoot.appendChild(style);
|
||||
};
|
||||
}
|
||||
if (el.style) {
|
||||
el.className = !shadowRoot ? globalCss(style as any) : css(style as any);
|
||||
}
|
||||
el.classList.add(cid, 'kv-container');
|
||||
const { render, unmount } = (code as RenderCode) || {};
|
||||
let renderRoot = node.shadowRoot ? shadowRoot : root;
|
||||
const ctx = {
|
||||
root: root,
|
||||
shadowRoot,
|
||||
renderRoot,
|
||||
event,
|
||||
container: this,
|
||||
code: code as RenderCode,
|
||||
data: node,
|
||||
css: shadowRoot ? css : globalCss,
|
||||
};
|
||||
if (render) {
|
||||
render(ctx);
|
||||
} else {
|
||||
// no render, so no insert
|
||||
}
|
||||
|
||||
if (event) {
|
||||
const destroy = (id: string) => {
|
||||
if (id) {
|
||||
if (id === cid || node?.parents?.find?.((item) => item === id)) {
|
||||
unmount?.(ctx); // 销毁父亲元素有这个元素的
|
||||
event.off('destroy', destroy); // 移除监听
|
||||
} else {
|
||||
// console.warn('destroy id not found, and not find parentIds', id, 'currentid', cid);
|
||||
}
|
||||
} else if (!id) {
|
||||
unmount?.(ctx); // 所有的都销毁
|
||||
event.off('destroy', destroy);
|
||||
}
|
||||
// 不需要销毁子元素
|
||||
};
|
||||
event.on('destroy', destroy);
|
||||
}
|
||||
if (shadowRoot) {
|
||||
const slot = document.createElement('slot');
|
||||
shadowRoot.appendChild(slot);
|
||||
}
|
||||
if (!this.showChild) {
|
||||
return;
|
||||
}
|
||||
const childrenIds = node.children || [];
|
||||
childrenIds.forEach((childId) => {
|
||||
renderChildren(childId, root, cid);
|
||||
});
|
||||
return el;
|
||||
}
|
||||
async destroy(id?: string) {
|
||||
if (!id) {
|
||||
this.root.innerHTML = '';
|
||||
} else {
|
||||
const elements = this.root.querySelectorAll(`[data-cid="${id}"]`);
|
||||
elements.forEach((element) => element.remove());
|
||||
}
|
||||
this.event.emit('destroy', id);
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(null);
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 只能用一次
|
||||
* @param entryId
|
||||
* @param data
|
||||
* @param opts
|
||||
* @returns
|
||||
*/
|
||||
async render(entryId: string, data?: RenderData[], opts: { reload?: boolean } = {}) {
|
||||
if (this.entryId && this.entryId !== entryId) {
|
||||
await this.destroy();
|
||||
} else if (this.entryId) {
|
||||
this.destroy(entryId);
|
||||
}
|
||||
const _data = data || this.data;
|
||||
this.entryId = entryId;
|
||||
if (opts?.reload) {
|
||||
this.loading = true;
|
||||
await this.loadData.apply(this, [_data]);
|
||||
}
|
||||
|
||||
if (this.loading || !this.loaded) {
|
||||
this.event.once('loadedData', () => {
|
||||
this.renderChildren(entryId);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
this.renderChildren(entryId);
|
||||
}
|
||||
}
|
||||
async hotReload(id: string) {
|
||||
await this.destroy(id);
|
||||
// const node = document.querySelector(`[data-cid="${id}"]`);
|
||||
if (this.loading) {
|
||||
this.event.once('loadedData', () => {
|
||||
this.renderChildren(id);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
this.renderChildren(id);
|
||||
}
|
||||
}
|
||||
async reRender() {
|
||||
await this.destroy();
|
||||
this.renderChildren(this.entryId);
|
||||
}
|
||||
async renderId(id: string) {
|
||||
if (!this.entryId) {
|
||||
this.render(id);
|
||||
return;
|
||||
}
|
||||
if (id === this.entryId) {
|
||||
this.reRender();
|
||||
return;
|
||||
}
|
||||
|
||||
const node = this.data.find((item) => item.id === id);
|
||||
if (node?.parents && node.parents.length > 0) {
|
||||
const parent = node.parents[node.parents.length - 1];
|
||||
const parentElement = this.root.querySelector(`[data-cid="${parent}"]`);
|
||||
await this.destroy(id);
|
||||
this.renderChildren(id, parentElement, parent);
|
||||
}
|
||||
}
|
||||
close() {
|
||||
this.destroy();
|
||||
const event = this.event;
|
||||
event && event.removeAllListeners();
|
||||
}
|
||||
}
|
||||
export class ContainerOne extends Container {
|
||||
constructor(opts: ContainerOpts) {
|
||||
super(opts);
|
||||
}
|
||||
async renderOne({ code: RenderCode }) {
|
||||
const newData = { codeId: 'test-reneder-one-code-id', id: 'test-render-one', code: RenderCode };
|
||||
console.log('loading', this.loading);
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
console.log('renderOne', newData);
|
||||
if (this.data.length === 0) {
|
||||
await this.loadData([newData]);
|
||||
} else {
|
||||
await this.updateData([newData]);
|
||||
}
|
||||
this.hotReload('test-render-one');
|
||||
}
|
||||
}
|
||||
export const mount = ({ render, unmount }, root: string | HTMLDivElement) => {
|
||||
let _root = typeof root === 'string' ? document.querySelector(root) : root;
|
||||
_root = _root || (document.body as any);
|
||||
render({ root: _root });
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
.kv-container.active {
|
||||
background: #000 !important;
|
||||
background: #000;
|
||||
border: 2px solid #195ca9;
|
||||
z-index: 100;
|
||||
}
|
||||
@ -11,10 +11,11 @@
|
||||
.kv-container.hover {
|
||||
cursor: move;
|
||||
}
|
||||
.kv-container > .resizer, .kv-container > .drag-title {
|
||||
.kv-container > .resizer,
|
||||
.kv-container > .drag-title {
|
||||
display: none;
|
||||
}
|
||||
.kv-container.active > .resizer, .kv-container.active > .drag-title {
|
||||
.kv-container.active > .resizer,
|
||||
.kv-container.active > .drag-title {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
420
src/container.ts
420
src/container.ts
@ -1,130 +1,12 @@
|
||||
import createEmotion from '@emotion/css/create-instance';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { MD5 } from 'crypto-js';
|
||||
import { ContainerEvent } from './event/continer';
|
||||
import { RenderCode } from './render';
|
||||
import * as schedule from './scheduler';
|
||||
import { CodeUrlAdapter } from './handle/adapter';
|
||||
import { codeUrlManager } from './code-url';
|
||||
|
||||
type CodeUrl = {
|
||||
url: string; // 地址
|
||||
code?: string; // 代码
|
||||
id?: string; // codeId
|
||||
hash?: string;
|
||||
};
|
||||
export class CodeUrlManager {
|
||||
urls: CodeUrl[] = [];
|
||||
urlMap: { [key: string]: CodeUrl } = {};
|
||||
constructor() {
|
||||
this.urls = [];
|
||||
}
|
||||
addCode(code: string, id: string, hash?: string) {
|
||||
const urlMap = this.urlMap;
|
||||
const resultContent = (content: CodeUrl) => {
|
||||
const index = this.urls.findIndex((item) => item.id === id);
|
||||
if (index === -1) {
|
||||
this.urls.push(content);
|
||||
}
|
||||
return content;
|
||||
};
|
||||
if (urlMap[hash]) {
|
||||
// 可能id不同,但是hash相同。已经加载过了。
|
||||
const content = {
|
||||
...urlMap[hash],
|
||||
id,
|
||||
};
|
||||
return resultContent(content);
|
||||
}
|
||||
const _hash = hash || MD5(code).toString();
|
||||
if (urlMap[_hash]) {
|
||||
const content = {
|
||||
...urlMap[_hash],
|
||||
id,
|
||||
};
|
||||
return resultContent(content);
|
||||
}
|
||||
const index = this.urls.findIndex((item) => item.id === id);
|
||||
if (index > -1) {
|
||||
// 没有共同hash的值了,但是还是找了id,这个时候应该移除之前
|
||||
const url = this.urls[index];
|
||||
const list = this.urls.filter((item) => item.hash === url.hash);
|
||||
if (list.length <= 1) {
|
||||
// 这个hash的值没有人用了
|
||||
URL.revokeObjectURL(url.url);
|
||||
this.urlMap[url.hash] = null;
|
||||
}
|
||||
this.urls.splice(index, 1);
|
||||
}
|
||||
/**
|
||||
* 全新代码
|
||||
*
|
||||
*/
|
||||
const url = URL.createObjectURL(new Blob([code], { type: 'application/javascript' }));
|
||||
const content = {
|
||||
id,
|
||||
url,
|
||||
code,
|
||||
hash: _hash,
|
||||
};
|
||||
|
||||
this.urls.push(content);
|
||||
urlMap[_hash] = { ...content, id: null };
|
||||
return content;
|
||||
}
|
||||
removeUrl(id?: string, url?: string) {
|
||||
const index = this.urls.findIndex((item) => item.url === url || item.id === id);
|
||||
if (index > -1) {
|
||||
const url = this.urls[index];
|
||||
URL.revokeObjectURL(url.url);
|
||||
this.urlMap[url.hash] = null;
|
||||
this.urls.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
export const codeUrlManager = new CodeUrlManager();
|
||||
const handleOneCode = async (data: RenderData) => {
|
||||
const { code } = data;
|
||||
const urlsBegin = ['http', 'https', 'blob', '//:'];
|
||||
if (typeof code === 'string' && code && urlsBegin.find((item) => code.startsWith(item))) {
|
||||
const importContent = await import(/* @vite-ignore */ code);
|
||||
const { render, unmount, ...rest } = importContent || {};
|
||||
const _data = {
|
||||
...data,
|
||||
code: {
|
||||
render,
|
||||
unmount,
|
||||
...rest,
|
||||
},
|
||||
};
|
||||
return _data;
|
||||
}
|
||||
if (typeof code === 'string' && data.codeId) {
|
||||
const codeId = data.codeId;
|
||||
const { url, hash } = codeUrlManager.addCode(code, codeId, data.hash);
|
||||
const importContent = await import(/* @vite-ignore */ url);
|
||||
const { render, unmount, ...rest } = importContent || {};
|
||||
const _data = {
|
||||
...data,
|
||||
codeId,
|
||||
hash,
|
||||
code: {
|
||||
render,
|
||||
unmount,
|
||||
...rest,
|
||||
},
|
||||
};
|
||||
return _data;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
const handleCode = async (data: RenderData[]) => {
|
||||
const handleData = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = data[i];
|
||||
const _data = await handleOneCode(item);
|
||||
handleData.push(_data);
|
||||
}
|
||||
return handleData;
|
||||
};
|
||||
|
||||
export { createEmotion };
|
||||
export type RenderData = {
|
||||
id: string; // id不会重复
|
||||
children?: string[];
|
||||
@ -141,31 +23,27 @@ export type RenderData = {
|
||||
export type ContainerOpts = {
|
||||
root?: HTMLDivElement | string;
|
||||
shadowRoot?: ShadowRoot;
|
||||
destroy?: () => void;
|
||||
data?: RenderData[];
|
||||
showChild?: boolean;
|
||||
adapter?: any;
|
||||
};
|
||||
|
||||
export class Container {
|
||||
data: RenderData[];
|
||||
root: HTMLDivElement;
|
||||
globalCss: any;
|
||||
showChild: boolean;
|
||||
event: EventEmitter;
|
||||
entryId: string = '';
|
||||
loading: boolean = false;
|
||||
loaded: boolean = false;
|
||||
adapter: CodeUrlAdapter;
|
||||
constructor(opts: ContainerOpts) {
|
||||
const data = opts.data || [];
|
||||
this.loadData.apply(this, [data]);
|
||||
|
||||
this.data = data;
|
||||
const rootElement = typeof opts.root === 'string' ? document.querySelector(opts.root) : opts.root;
|
||||
this.root = rootElement || (document.body as any);
|
||||
this.globalCss = createEmotion({ key: 'css-global', speedy: true }).css;
|
||||
this.showChild = opts.showChild ?? true;
|
||||
const event = new EventEmitter();
|
||||
this.event = event;
|
||||
const listening = this.root.dataset.listening;
|
||||
this.adapter = opts.adapter || this.createAdapter();
|
||||
if (listening !== 'true') {
|
||||
this.root.addEventListener('onContainer', (e: ContainerEvent) => {
|
||||
const { type, data } = e.data;
|
||||
@ -173,68 +51,31 @@ export class Container {
|
||||
});
|
||||
}
|
||||
}
|
||||
async loadData(data: RenderData[], key: string = '') {
|
||||
if (data.length === 0) {
|
||||
this.data = data;
|
||||
this.loaded = true;
|
||||
return;
|
||||
protected createAdapter() {
|
||||
const adapter = new CodeUrlAdapter({ codeUrlManager });
|
||||
this.adapter = adapter;
|
||||
return adapter;
|
||||
}
|
||||
this.loading = true;
|
||||
this.data = await handleCode(data);
|
||||
this.loading = false;
|
||||
this.event.emit('loadedData' + key);
|
||||
this.loaded = true;
|
||||
}
|
||||
async getData({ id, codeId }: { id?: string; codeId?: string }) {
|
||||
if (id && codeId) {
|
||||
return this.data.find((item) => item.id === id && item.codeId === codeId);
|
||||
}
|
||||
if (id) {
|
||||
return this.data.find((item) => item.id === id);
|
||||
}
|
||||
if (codeId) {
|
||||
return this.data.find((item) => item.codeId === codeId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async updateDataCode(data: RenderData[]) {
|
||||
if (this.loading) {
|
||||
console.warn('loading');
|
||||
return;
|
||||
}
|
||||
const _data = this.data.map((item) => {
|
||||
const node = data.find((node) => node.codeId && node.codeId === item.codeId);
|
||||
if (node) {
|
||||
return {
|
||||
...item,
|
||||
...node,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
await this.loadData(_data);
|
||||
}
|
||||
async updateData(data: RenderData[]) {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
const _data = this.data.map((item) => {
|
||||
const node = data.find((node) => node.id === item.id);
|
||||
if (node) {
|
||||
return {
|
||||
...item,
|
||||
...node,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
console.log('updateData', _data.length, data.length);
|
||||
await this.loadData(_data);
|
||||
}
|
||||
|
||||
renderChildren(cid: string, parentElement?: any, pid?: any): void | HTMLDivElement {
|
||||
getData({ id }: { id: string }) {
|
||||
const data = this.data;
|
||||
const node = data.find((node) => node.id === id);
|
||||
return node;
|
||||
}
|
||||
updateData(data: RenderData[]) {
|
||||
this.data = this.data.map((node) => {
|
||||
const find = data.find((item) => item.id === node.id);
|
||||
return find || node;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 渲染模块
|
||||
* @param cid
|
||||
* @param parentElement
|
||||
* @param pid
|
||||
* @returns
|
||||
*/
|
||||
renderChildren(cid: string, parentElement?: any, pid?: any, isNew = true): void | HTMLDivElement {
|
||||
const data = this.data;
|
||||
console.log('renderChildren', cid, data);
|
||||
const globalCss = this.globalCss;
|
||||
if (!parentElement) {
|
||||
parentElement = this.root;
|
||||
@ -243,20 +84,64 @@ export class Container {
|
||||
}
|
||||
const renderChildren = this.renderChildren.bind(this);
|
||||
const node = data.find((node: RenderData) => node.id === cid);
|
||||
const event = this.event;
|
||||
if (!node) {
|
||||
console.warn('node not found', cid);
|
||||
return;
|
||||
}
|
||||
const { style, code } = node;
|
||||
const el = document.createElement('div');
|
||||
const root = parentElement.appendChild(el);
|
||||
let el;
|
||||
if (isNew) {
|
||||
el = document.createElement('div');
|
||||
parentElement.appendChild(el);
|
||||
const parentIds = parentElement.dataset.parentIds || '';
|
||||
const shadowRoot = node.shadowRoot ? el.attachShadow({ mode: 'open' }) : null;
|
||||
const { css, sheet, cache } = createEmotion({ key: 'css' });
|
||||
|
||||
el.dataset.cid = cid;
|
||||
el.dataset.pid = pid;
|
||||
el.dataset.parentIds = parentIds ? parentIds + ',' + cid : cid;
|
||||
// 代码渲染
|
||||
const { renderFn } = this.getContext({ renderEl: el, entryId: cid });
|
||||
schedule.addTask(renderFn, 'high');
|
||||
// 代码渲染结束
|
||||
} else {
|
||||
el = parentElement.querySelector(`[data-cid="${cid}"]`);
|
||||
}
|
||||
|
||||
if (!el) {
|
||||
console.warn('when no createNewElement el not found', cid);
|
||||
return;
|
||||
}
|
||||
const childrenIds = node.children || [];
|
||||
childrenIds.forEach((childId) => {
|
||||
renderChildren(childId, el, cid);
|
||||
});
|
||||
return el;
|
||||
}
|
||||
/**
|
||||
* 渲染根节点
|
||||
* @param entryId
|
||||
* @returns
|
||||
*/
|
||||
async renderRoot(entryId: string) {
|
||||
if (this.entryId && this.entryId !== entryId) {
|
||||
await this.destroy();
|
||||
} else if (this.entryId) {
|
||||
await this.destroy(entryId);
|
||||
}
|
||||
this.renderChildren(entryId);
|
||||
}
|
||||
async reRender() {
|
||||
await this.destroy();
|
||||
this.renderChildren(this.entryId);
|
||||
}
|
||||
protected getContext({ renderEl, entryId }: { renderEl: HTMLDivElement; entryId: string }) {
|
||||
const node = this.data.find((node) => node.id === entryId);
|
||||
const { style, code } = node;
|
||||
const event = this.event;
|
||||
const globalCss = this.globalCss;
|
||||
const root = renderEl;
|
||||
const adapter = this.adapter;
|
||||
const containerThis = this;
|
||||
const shadowRoot = node.shadowRoot ? renderEl.attachShadow({ mode: 'open' }) : null;
|
||||
const { css, sheet, cache } = createEmotion({ key: 'css' });
|
||||
if (shadowRoot) {
|
||||
cache.insert = (selector: string, serialized: any, sheet: any, shouldCache: boolean) => {
|
||||
const style = document.createElement('style');
|
||||
@ -264,19 +149,27 @@ export class Container {
|
||||
shadowRoot.appendChild(style);
|
||||
};
|
||||
}
|
||||
if (el.style) {
|
||||
el.className = !shadowRoot ? globalCss(style as any) : css(style as any);
|
||||
const cid = entryId;
|
||||
renderEl.className = '';
|
||||
if (renderEl.style) {
|
||||
renderEl.className = !shadowRoot ? globalCss(style as any) : css(style as any);
|
||||
}
|
||||
el.classList.add(cid, 'kv-container');
|
||||
const { render, unmount } = (code as RenderCode) || {};
|
||||
renderEl.classList.add(cid, 'kv-container');
|
||||
if (shadowRoot) {
|
||||
const slot = document.createElement('slot');
|
||||
shadowRoot.appendChild(slot);
|
||||
}
|
||||
const renderFn = async () => {
|
||||
const { code: newCode } = await adapter.handle(node as any);
|
||||
const { render, unmount } = newCode || {};
|
||||
let renderRoot = node.shadowRoot ? shadowRoot : root;
|
||||
const ctx = {
|
||||
root: root,
|
||||
shadowRoot,
|
||||
renderRoot,
|
||||
event,
|
||||
container: this,
|
||||
code: code as RenderCode,
|
||||
container: containerThis,
|
||||
code: newCode as RenderCode,
|
||||
data: node,
|
||||
css: shadowRoot ? css : globalCss,
|
||||
};
|
||||
@ -285,7 +178,6 @@ export class Container {
|
||||
} else {
|
||||
// no render, so no insert
|
||||
}
|
||||
|
||||
if (event) {
|
||||
const destroy = (id: string) => {
|
||||
if (id) {
|
||||
@ -303,25 +195,48 @@ export class Container {
|
||||
};
|
||||
event.on('destroy', destroy);
|
||||
}
|
||||
if (shadowRoot) {
|
||||
const slot = document.createElement('slot');
|
||||
shadowRoot.appendChild(slot);
|
||||
};
|
||||
return { renderFn, shadowRoot };
|
||||
}
|
||||
if (!this.showChild) {
|
||||
// 更新渲染
|
||||
async render(entryId: string, el?: HTMLDivElement) {
|
||||
if (schedule.getLoading()) {
|
||||
console.log('schedule is loading');
|
||||
}
|
||||
if (!this.entryId) {
|
||||
console.warn('root is use renderRoot method, not render');
|
||||
return this.renderRoot(entryId);
|
||||
}
|
||||
const isRoot = this.entryId === entryId;
|
||||
if (isRoot) {
|
||||
await this.reRender();
|
||||
return;
|
||||
}
|
||||
const childrenIds = node.children || [];
|
||||
childrenIds.forEach((childId) => {
|
||||
renderChildren(childId, root, cid);
|
||||
});
|
||||
return el;
|
||||
let renderEl = el;
|
||||
if (!el) {
|
||||
renderEl = this.root.querySelector(`[data-cid="${entryId}"]`);
|
||||
}
|
||||
if (!renderEl) {
|
||||
console.warn('render element not found', entryId);
|
||||
return;
|
||||
} else {
|
||||
await this.destroy(entryId);
|
||||
}
|
||||
const parentElement = renderEl.parentElement;
|
||||
const pid = renderEl.dataset.pid;
|
||||
const { renderFn } = this.getContext({ renderEl, entryId });
|
||||
schedule.addTask(renderFn, 'high');
|
||||
this.renderChildren(entryId, parentElement, pid, false);
|
||||
}
|
||||
|
||||
async destroy(id?: string) {
|
||||
if (!id) {
|
||||
this.root.innerHTML = '';
|
||||
} else {
|
||||
const elements = this.root.querySelectorAll(`[data-cid="${id}"]`);
|
||||
elements.forEach((element) => element.remove());
|
||||
elements.forEach((element) => {
|
||||
element.innerHTML = '';
|
||||
});
|
||||
}
|
||||
this.event.emit('destroy', id);
|
||||
await new Promise((resolve) => {
|
||||
@ -330,92 +245,23 @@ export class Container {
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 只能用一次
|
||||
* @param entryId
|
||||
* @param data
|
||||
* @param opts
|
||||
* @returns
|
||||
*/
|
||||
async render(entryId: string, data?: RenderData[], opts: { reload?: boolean } = {}) {
|
||||
if (this.entryId && this.entryId !== entryId) {
|
||||
await this.destroy();
|
||||
} else if (this.entryId) {
|
||||
this.destroy(entryId);
|
||||
}
|
||||
const _data = data || this.data;
|
||||
this.entryId = entryId;
|
||||
if (opts?.reload) {
|
||||
this.loading = true;
|
||||
await this.loadData.apply(this, [_data]);
|
||||
}
|
||||
|
||||
if (this.loading || !this.loaded) {
|
||||
this.event.once('loadedData', () => {
|
||||
this.renderChildren(entryId);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
this.renderChildren(entryId);
|
||||
}
|
||||
}
|
||||
async hotReload(id: string) {
|
||||
await this.destroy(id);
|
||||
// const node = document.querySelector(`[data-cid="${id}"]`);
|
||||
if (this.loading) {
|
||||
this.event.once('loadedData', () => {
|
||||
this.renderChildren(id);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
this.renderChildren(id);
|
||||
}
|
||||
}
|
||||
async reRender() {
|
||||
await this.destroy();
|
||||
this.renderChildren(this.entryId);
|
||||
}
|
||||
async renderId(id: string) {
|
||||
if (!this.entryId) {
|
||||
this.render(id);
|
||||
return;
|
||||
}
|
||||
if (id === this.entryId) {
|
||||
this.reRender();
|
||||
return;
|
||||
}
|
||||
|
||||
const node = this.data.find((item) => item.id === id);
|
||||
if (node?.parents && node.parents.length > 0) {
|
||||
const parent = node.parents[node.parents.length - 1];
|
||||
const parentElement = this.root.querySelector(`[data-cid="${parent}"]`);
|
||||
await this.destroy(id);
|
||||
this.renderChildren(id, parentElement, parent);
|
||||
}
|
||||
}
|
||||
close() {
|
||||
this.destroy();
|
||||
const event = this.event;
|
||||
event && event.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
export class ContainerOne extends Container {
|
||||
constructor(opts: ContainerOpts) {
|
||||
super(opts);
|
||||
}
|
||||
async renderOne({ code: RenderCode }) {
|
||||
const newData = { codeId: 'test-reneder-one-code-id', id: 'test-render-one', code: RenderCode };
|
||||
console.log('loading', this.loading);
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
console.log('renderOne', newData);
|
||||
if (this.data.length === 0) {
|
||||
await this.loadData([newData]);
|
||||
} else {
|
||||
await this.updateData([newData]);
|
||||
}
|
||||
this.hotReload('test-render-one');
|
||||
this.data = [newData];
|
||||
setTimeout(() => {
|
||||
this.renderRoot('test-render-one');
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
export const mount = ({ render, unmount }, root: string | HTMLDivElement) => {
|
||||
|
@ -1,7 +1,5 @@
|
||||
export * from './container-edit';
|
||||
|
||||
export * from './container';
|
||||
|
||||
export * from './render';
|
||||
|
||||
export * from './event/unload';
|
||||
@ -9,3 +7,5 @@ export * from './event/unload';
|
||||
export * from './event/emitter';
|
||||
|
||||
export * from './event/continer';
|
||||
|
||||
export * from './code-url';
|
85
src/handle/adapter.ts
Normal file
85
src/handle/adapter.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import type { CodeUrlManager } from '../code-url';
|
||||
type Fn = (args: any) => any;
|
||||
export type RenderCode = {
|
||||
render: Fn;
|
||||
unmount?: Fn;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
type AdapterData = {
|
||||
codeId?: string;
|
||||
code?: string;
|
||||
hash?: string;
|
||||
codeUrl?: string;
|
||||
};
|
||||
type CodeAdapterOpts<T = any> = {
|
||||
handle?: any;
|
||||
} & T;
|
||||
export class CodeAdapter {
|
||||
handle: (data: AdapterData) => Promise<any>;
|
||||
constructor(opts: CodeAdapterOpts) {
|
||||
this.handle = opts.handle;
|
||||
}
|
||||
setHandle(handle: (data: AdapterData) => Promise<any>) {
|
||||
this.handle = handle;
|
||||
}
|
||||
}
|
||||
type Opts = {
|
||||
codeUrlManager: CodeUrlManager;
|
||||
};
|
||||
export class CodeUrlAdapter extends CodeAdapter {
|
||||
codeUrlManager: CodeUrlManager;
|
||||
constructor(opts: CodeAdapterOpts<Opts>) {
|
||||
super(opts);
|
||||
this.handle = (data) => handleOneCode(data, this.codeUrlManager);
|
||||
this.codeUrlManager = opts.codeUrlManager;
|
||||
}
|
||||
setCodeUrlManager(codeUrlManager: CodeUrlManager) {
|
||||
this.codeUrlManager = codeUrlManager;
|
||||
}
|
||||
}
|
||||
export const handleOneCode = async (data: AdapterData, codeUrlManager: CodeUrlManager) => {
|
||||
const { code, codeId } = data;
|
||||
const urlsBegin = ['http', 'https', 'blob'];
|
||||
|
||||
let importUrl = '';
|
||||
// codeUrl优先级最高
|
||||
if (data?.codeUrl) {
|
||||
importUrl = data.codeUrl;
|
||||
} else if (typeof code === 'string' && code && urlsBegin.find((item) => code.startsWith(item))) {
|
||||
importUrl = code;
|
||||
}
|
||||
if (importUrl) {
|
||||
const importContent = await import(/* @vite-ignore */ importUrl);
|
||||
const { render, unmount, ...rest } = importContent || {};
|
||||
const _data = {
|
||||
...data,
|
||||
code: {
|
||||
render,
|
||||
unmount,
|
||||
...rest,
|
||||
},
|
||||
};
|
||||
return _data;
|
||||
}
|
||||
if (typeof code === 'string' && codeId) {
|
||||
const { url, hash } = codeUrlManager.addCode(code, codeId, data.hash);
|
||||
const importContent = await import(/* @vite-ignore */ url);
|
||||
const { render, unmount, ...rest } = importContent || {};
|
||||
const _data = {
|
||||
...data,
|
||||
codeId,
|
||||
hash,
|
||||
code: {
|
||||
render,
|
||||
unmount,
|
||||
...rest,
|
||||
},
|
||||
};
|
||||
return _data;
|
||||
} else if (typeof code === 'string' && !codeId) {
|
||||
console.error('codeId is required', data);
|
||||
}
|
||||
// 无需处理
|
||||
return data;
|
||||
};
|
@ -7,3 +7,5 @@ export * from './event/unload';
|
||||
export * from './event/emitter';
|
||||
|
||||
export * from './event/continer';
|
||||
|
||||
export * from './code-url';
|
||||
|
@ -1,12 +1,13 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import type { Container } from './container';
|
||||
import type { ContainerEdit } from './container-edit';
|
||||
// import type { ContainerEdit } from './container-edit';
|
||||
import { Emotion } from '@emotion/css/create-instance';
|
||||
|
||||
export type RenderContext<T extends { [key: string]: any } = {}> = {
|
||||
root?: HTMLDivElement;
|
||||
shadowRoot?: ShadowRoot;
|
||||
container?: Container | ContainerEdit;
|
||||
// container?: Container | ContainerEdit;
|
||||
container?: Container;
|
||||
event?: EventEmitter;
|
||||
code?: {
|
||||
render: Render;
|
||||
|
1
src/scheduler/index.ts
Normal file
1
src/scheduler/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './scheduler';
|
55
src/scheduler/scheduler.ts
Normal file
55
src/scheduler/scheduler.ts
Normal file
@ -0,0 +1,55 @@
|
||||
// 定义任务队列
|
||||
const highPriorityTasks = [];
|
||||
const lowPriorityTasks = [];
|
||||
let isSchedulerRunning = false; // 调度器状态
|
||||
|
||||
export const fnvoid = () => {};
|
||||
export type Priority = 'high' | 'low';
|
||||
// 添加任务的辅助函数
|
||||
export const addTask = (callback: any, priority: Priority = 'low') => {
|
||||
if (priority === 'high') {
|
||||
highPriorityTasks.push(callback);
|
||||
} else {
|
||||
lowPriorityTasks.push(callback);
|
||||
}
|
||||
// 如果调度器未运行且有任务,启动调度
|
||||
if (!isSchedulerRunning) {
|
||||
isSchedulerRunning = true;
|
||||
requestIdleCallback(processTasks);
|
||||
}
|
||||
};
|
||||
|
||||
// 调度函数
|
||||
const processTasks: IdleRequestCallback = (deadline) => {
|
||||
// 优先处理高优先级任务
|
||||
while (highPriorityTasks.length && deadline.timeRemaining() > 0) {
|
||||
const task = highPriorityTasks.shift();
|
||||
task();
|
||||
}
|
||||
|
||||
// 如果有剩余时间,再处理低优先级任务
|
||||
while (lowPriorityTasks.length && deadline.timeRemaining() > 0) {
|
||||
const task = lowPriorityTasks.shift();
|
||||
task();
|
||||
}
|
||||
|
||||
// 检查是否还有任务
|
||||
if (highPriorityTasks.length || lowPriorityTasks.length) {
|
||||
requestIdleCallback(processTasks);
|
||||
} else {
|
||||
// 没有任务,停止调度器
|
||||
isSchedulerRunning = false;
|
||||
}
|
||||
};
|
||||
// 开始调度
|
||||
requestIdleCallback(processTasks);
|
||||
|
||||
// 添加一些测试任务
|
||||
// addTask(() => console.log('Low priority task 1'), 'low');
|
||||
// addTask(() => console.log('High priority task 1'), 'high');
|
||||
// addTask(() => console.log('Low priority task 2'), 'low');
|
||||
// addTask(() => console.log('High priority task 2'), 'high');
|
||||
|
||||
export const getLoading = () => {
|
||||
return isSchedulerRunning;
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { handleCode, RenderData } from '../container';
|
||||
import { handleCode, RenderData } from '../container-old';
|
||||
import { createStore } from 'zustand/vanilla';
|
||||
export type ContainerStore = {
|
||||
loading: boolean;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { RenderData } from '../container';
|
||||
import { RenderData } from '../container-old';
|
||||
|
||||
export const getTree = (data: RenderData[], id: string) => {
|
||||
const node = data.find((node) => node.id === id);
|
||||
|
Loading…
x
Reference in New Issue
Block a user