mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-06-06 23:50:48 +00:00
chore: lint fixed
This commit is contained in:
parent
6b8d0c13bc
commit
764e841336
@ -1,23 +1,18 @@
|
||||
import stylistic from '@stylistic/eslint-plugin';
|
||||
import tseslint from 'typescript-eslint'
|
||||
import tseslint from 'typescript-eslint';
|
||||
import js from '@eslint/js';
|
||||
import react from 'eslint-plugin-react'
|
||||
import react from 'eslint-plugin-react';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||
import globals from 'globals'
|
||||
import globals from 'globals';
|
||||
|
||||
export default tseslint.config({
|
||||
files: ['packages/*/src/**/*.{ts?(x),js?(x)}'],
|
||||
ignores: ["**/*.test.ts"],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
],
|
||||
files: ['packages/*/{src,__tests__}/**/*.{ts?(x),js?(x)}'],
|
||||
ignores: ['**/*.test.ts'],
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
plugins: {
|
||||
'@stylistic': stylistic,
|
||||
react,
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh
|
||||
},
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
@ -28,18 +23,22 @@ export default tseslint.config({
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.nodeBuiltin,
|
||||
...globals.jest
|
||||
...globals.jest,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@stylistic/indent': ['error', 2],
|
||||
'@stylistic/indent-binary-ops': ['error', 2],
|
||||
'@stylistic/max-len': ['error', { tabWidth: 2, "ignoreStrings": true }],
|
||||
'@stylistic/max-len': ['error', { code: 100, tabWidth: 2, ignoreStrings: true, ignoreComments: true }],
|
||||
'@stylistic/no-tabs': 'error',
|
||||
'@stylistic/quotes': ['error', 'single'],
|
||||
'@stylistic/jsx-pascal-case': [2],
|
||||
'@stylistic/jsx-indent': [2, 2, { checkAttributes: true, indentLogicalExpressions: true }],
|
||||
'@stylistic/semi': ['error', 'always'],
|
||||
'@stylistic/eol-last': ['error', 'always'],
|
||||
'@stylistic/jsx-quotes': ['error', 'prefer-double'],
|
||||
|
||||
"@typescript-eslint/ban-ts-comment": ["error", { 'ts-expect-error': 'allow-with-description' }],
|
||||
|
||||
'react/jsx-no-undef': 'error',
|
||||
'react/jsx-uses-vars': 'error',
|
||||
@ -50,7 +49,5 @@ export default tseslint.config({
|
||||
|
||||
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
|
||||
|
||||
'react-refresh/only-export-components': 'warn',
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"playground": "pnpm --filter playground dev",
|
||||
"test": "pnpm -r test",
|
||||
"build": "node ./scripts/build.js",
|
||||
"test": "vitest",
|
||||
"clean": "rimraf ./packages/*/dist",
|
||||
"clean:lib": "rimraf ./node_modules ./packages/*/node_modules",
|
||||
"lint": "eslint . --cache",
|
||||
@ -35,7 +35,6 @@
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"globals": "^15.0.0",
|
||||
"husky": "^9.0.11",
|
||||
"less": "^4.2.0",
|
||||
|
||||
@ -11,6 +11,10 @@
|
||||
"import": "./dist/low-code-designer.js",
|
||||
"require": "./dist/low-code-designer.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./dist/": {
|
||||
"import": "./dist/",
|
||||
"require": "./dist/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
|
||||
@ -50,8 +50,8 @@ function getTitle(title: string | IPublicTypeI18nData | ReactElement) {
|
||||
export class BorderContainer extends Component<{
|
||||
host: BuiltinSimulatorHost;
|
||||
}, {
|
||||
target?: INode;
|
||||
}> {
|
||||
target?: INode;
|
||||
}> {
|
||||
state = {} as any;
|
||||
|
||||
@computed get scale() {
|
||||
@ -70,7 +70,7 @@ export class BorderContainer extends Component<{
|
||||
const { host } = this.props;
|
||||
|
||||
host.designer.editor.eventBus.on('designer.dropLocation.change', (loc: DropLocation) => {
|
||||
let { target } = this.state;
|
||||
const { target } = this.state;
|
||||
if (target === loc?.target) return;
|
||||
this.setState({
|
||||
target: loc?.target,
|
||||
|
||||
@ -1225,9 +1225,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
const childrenCanMove =
|
||||
onChildMoveHook && parentContainerNode && typeof onChildMoveHook === 'function'
|
||||
? onChildMoveHook(
|
||||
node!.internalToShellNode(),
|
||||
(parentContainerNode as any).internalToShellNode(),
|
||||
)
|
||||
node!.internalToShellNode(),
|
||||
(parentContainerNode as any).internalToShellNode(),
|
||||
)
|
||||
: true;
|
||||
|
||||
return canMove && childrenCanMove;
|
||||
@ -1313,9 +1313,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
const inst = instances
|
||||
? instances.length > 1
|
||||
? instances.find(
|
||||
(_inst) =>
|
||||
this.getClosestNodeInstance(_inst, container.id)?.instance === containerInstance,
|
||||
)
|
||||
(_inst) =>
|
||||
this.getClosestNodeInstance(_inst, container.id)?.instance === containerInstance,
|
||||
)
|
||||
: instances[0]
|
||||
: null;
|
||||
const rect = inst
|
||||
|
||||
@ -197,8 +197,8 @@ export class LiveEditing {
|
||||
|
||||
export type SpecificRule = (target: EditingTarget) =>
|
||||
| (IPublicTypeLiveTextEditingConfig & {
|
||||
propElement?: HTMLElement;
|
||||
})
|
||||
propElement?: HTMLElement;
|
||||
})
|
||||
| null;
|
||||
|
||||
export interface SaveHandler {
|
||||
|
||||
@ -81,19 +81,19 @@ export default class InstanceNodeSelector extends React.Component<IProps, IState
|
||||
|
||||
onMouseOver =
|
||||
(node: INode) =>
|
||||
(_: any, flag = true) => {
|
||||
if (node && typeof node.hover === 'function') {
|
||||
node.hover(flag);
|
||||
}
|
||||
};
|
||||
(_: any, flag = true) => {
|
||||
if (node && typeof node.hover === 'function') {
|
||||
node.hover(flag);
|
||||
}
|
||||
};
|
||||
|
||||
onMouseOut =
|
||||
(node: INode) =>
|
||||
(_: any, flag = false) => {
|
||||
if (node && typeof node.hover === 'function') {
|
||||
node.hover(flag);
|
||||
}
|
||||
};
|
||||
(_: any, flag = false) => {
|
||||
if (node && typeof node.hover === 'function') {
|
||||
node.hover(flag);
|
||||
}
|
||||
};
|
||||
|
||||
renderNodes = () => {
|
||||
const nodes = this.state.parentNodes;
|
||||
|
||||
@ -226,10 +226,10 @@ export class ComponentMeta implements IComponentMeta {
|
||||
this._title =
|
||||
typeof title === 'string'
|
||||
? {
|
||||
type: 'i18n',
|
||||
'en-US': this.componentName,
|
||||
'zh-CN': title,
|
||||
}
|
||||
type: 'i18n',
|
||||
'en-US': this.componentName,
|
||||
'zh-CN': title,
|
||||
}
|
||||
: title;
|
||||
}
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ export class GlobalContextMenuActions {
|
||||
event.preventDefault();
|
||||
|
||||
const actions: IPublicTypeContextMenuAction[] = [];
|
||||
let contextMenu: ContextMenuActions = this.contextMenuActionsMap.values().next().value;
|
||||
const contextMenu: ContextMenuActions = this.contextMenuActionsMap.values().next().value;
|
||||
this.contextMenuActionsMap.forEach((contextMenu) => {
|
||||
actions.push(...contextMenu.actions);
|
||||
});
|
||||
|
||||
@ -55,9 +55,9 @@ export interface DesignerProps {
|
||||
onDragstart?: (e: IPublicModelLocateEvent) => void;
|
||||
onDrag?: (e: IPublicModelLocateEvent) => void;
|
||||
onDragend?: (
|
||||
e: { dragObject: IPublicModelDragObject; copy: boolean },
|
||||
loc?: DropLocation,
|
||||
) => void;
|
||||
e: { dragObject: IPublicModelDragObject; copy: boolean },
|
||||
loc?: DropLocation,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export class Designer {
|
||||
@ -406,8 +406,8 @@ export class Designer {
|
||||
|
||||
if (components) {
|
||||
// 合并 assets
|
||||
let assets = this.editor.get('assets') || {};
|
||||
let newAssets = mergeAssets(assets, incrementalAssets);
|
||||
const assets = this.editor.get('assets') || {};
|
||||
const newAssets = mergeAssets(assets, incrementalAssets);
|
||||
// 对于 assets 存在需要二次网络下载的过程,必须 await 等待结束之后,再进行事件触发
|
||||
await this.editor.set('assets', newAssets);
|
||||
}
|
||||
|
||||
@ -37,10 +37,10 @@ function getSettingFieldCollectorKey(
|
||||
// @ts-ignore
|
||||
export interface ISettingField
|
||||
extends ISettingPropEntry,
|
||||
Omit<
|
||||
IBaseModelSettingField<ISettingTopEntry, ISettingField, IComponentMeta, INode>,
|
||||
Omit<
|
||||
IBaseModelSettingField<ISettingTopEntry, ISettingField, IComponentMeta, INode>,
|
||||
'setValue' | 'key' | 'node'
|
||||
> {
|
||||
> {
|
||||
readonly isSettingField: true;
|
||||
|
||||
readonly isRequired: boolean;
|
||||
|
||||
@ -21,7 +21,7 @@ function generateSessionId(nodes: INode[]) {
|
||||
|
||||
export interface ISettingTopEntry
|
||||
extends ISettingEntry,
|
||||
IPublicModelSettingTopEntry<INode, ISettingField> {
|
||||
IPublicModelSettingTopEntry<INode, ISettingField> {
|
||||
readonly top: ISettingTopEntry;
|
||||
|
||||
readonly parent: ISettingTopEntry;
|
||||
|
||||
@ -11,9 +11,9 @@ function getHotterFromSetter(setter: any) {
|
||||
function getTransducerFromSetter(setter: any) {
|
||||
return (
|
||||
(setter &&
|
||||
(setter.transducer ||
|
||||
setter.Transducer ||
|
||||
(setter.type && (setter.type.transducer || setter.type.Transducer)))) ||
|
||||
(setter.transducer ||
|
||||
setter.Transducer ||
|
||||
(setter.type && (setter.type.transducer || setter.type.Transducer)))) ||
|
||||
null
|
||||
); // eslint-disable-line
|
||||
}
|
||||
|
||||
@ -46,14 +46,14 @@ import { EDITOR_EVENT } from '../types';
|
||||
|
||||
export type GetDataType<T, NodeType> = T extends undefined
|
||||
? NodeType extends {
|
||||
schema: infer R;
|
||||
}
|
||||
schema: infer R;
|
||||
}
|
||||
? R
|
||||
: any
|
||||
: T;
|
||||
|
||||
export class DocumentModel
|
||||
implements
|
||||
implements
|
||||
Omit<
|
||||
IPublicModelDocumentModel<
|
||||
ISelection,
|
||||
|
||||
@ -41,10 +41,10 @@ export class History<T = IPublicTypeNodeSchema> implements IHistory {
|
||||
private timeGap: number = 1000;
|
||||
|
||||
constructor(
|
||||
dataFn: () => T | null,
|
||||
private redoer: (data: T) => void,
|
||||
private document?: IDocumentModel,
|
||||
) {
|
||||
dataFn: () => T | null,
|
||||
private redoer: (data: T) => void,
|
||||
private document?: IDocumentModel,
|
||||
) {
|
||||
this.session = new Session(0, null, this.timeGap);
|
||||
this.records = [this.session];
|
||||
|
||||
|
||||
@ -11,10 +11,10 @@ export interface IOnChangeOptions {
|
||||
}
|
||||
|
||||
export class NodeChildren implements Omit<IPublicModelNodeChildren<INode>,
|
||||
'importSchema' |
|
||||
'exportSchema' |
|
||||
'isEmpty' |
|
||||
'notEmpty'
|
||||
'importSchema' |
|
||||
'exportSchema' |
|
||||
'isEmpty' |
|
||||
'notEmpty'
|
||||
> {
|
||||
@obx.shallow children: INode[];
|
||||
|
||||
@ -46,9 +46,9 @@ export class NodeChildren implements Omit<IPublicModelNodeChildren<INode>,
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly owner: INode,
|
||||
data: IPublicTypeNodeData | IPublicTypeNodeData[],
|
||||
) {
|
||||
readonly owner: INode,
|
||||
data: IPublicTypeNodeData | IPublicTypeNodeData[],
|
||||
) {
|
||||
makeObservable(this);
|
||||
this.children = (Array.isArray(data) ? data : [data]).filter(child => !!child).map((child) => {
|
||||
return this.owner.document?.createNode(child);
|
||||
|
||||
@ -97,7 +97,7 @@ export interface IBaseNode extends Node {}
|
||||
* hidden
|
||||
*/
|
||||
export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
|
||||
implements
|
||||
implements
|
||||
Omit<
|
||||
IBaseModelNode<
|
||||
IDocumentModel,
|
||||
@ -191,7 +191,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
|
||||
}
|
||||
|
||||
@computed get title(): string | IPublicTypeI18nData | ReactElement {
|
||||
let t = this.getExtraProp('title');
|
||||
const t = this.getExtraProp('title');
|
||||
// TODO: 暂时走不到这个分支
|
||||
// if (!t && this.componentMeta.descriptor) {
|
||||
// t = this.getProp(this.componentMeta.descriptor, false);
|
||||
@ -325,17 +325,17 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
|
||||
@action
|
||||
private initBuiltinProps() {
|
||||
this.props.has(getConvertedExtraKey('hidden')) ||
|
||||
this.props.add(false, getConvertedExtraKey('hidden'));
|
||||
this.props.add(false, getConvertedExtraKey('hidden'));
|
||||
this.props.has(getConvertedExtraKey('title')) ||
|
||||
this.props.add('', getConvertedExtraKey('title'));
|
||||
this.props.add('', getConvertedExtraKey('title'));
|
||||
this.props.has(getConvertedExtraKey('isLocked')) ||
|
||||
this.props.add(false, getConvertedExtraKey('isLocked'));
|
||||
this.props.add(false, getConvertedExtraKey('isLocked'));
|
||||
this.props.has(getConvertedExtraKey('condition')) ||
|
||||
this.props.add(true, getConvertedExtraKey('condition'));
|
||||
this.props.add(true, getConvertedExtraKey('condition'));
|
||||
this.props.has(getConvertedExtraKey('conditionGroup')) ||
|
||||
this.props.add('', getConvertedExtraKey('conditionGroup'));
|
||||
this.props.add('', getConvertedExtraKey('conditionGroup'));
|
||||
this.props.has(getConvertedExtraKey('loop')) ||
|
||||
this.props.add(undefined, getConvertedExtraKey('loop'));
|
||||
this.props.add(undefined, getConvertedExtraKey('loop'));
|
||||
}
|
||||
|
||||
@action
|
||||
@ -1164,7 +1164,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
|
||||
const isRGLContainerNode = this.isRGLContainer;
|
||||
const isRGLNode = this.getParent()?.isRGLContainer as boolean;
|
||||
const isRGL = isRGLContainerNode || (isRGLNode && (!isContainerNode || !isEmptyNode));
|
||||
let rglNode = isRGLContainerNode ? this : isRGL ? this?.getParent() : null;
|
||||
const rglNode = isRGLContainerNode ? this : isRGL ? this?.getParent() : null;
|
||||
return { isContainerNode, isEmptyNode, isRGLContainerNode, isRGLNode, isRGL, rglNode };
|
||||
}
|
||||
|
||||
|
||||
@ -646,7 +646,7 @@ export class Prop implements IProp, IPropParent {
|
||||
}
|
||||
}
|
||||
const prop = isProp(value) ? value : new Prop(this, value, key);
|
||||
let items = this._items! || [];
|
||||
const items = this._items! || [];
|
||||
if (this.type === 'list') {
|
||||
if (!isValidArrayIndex(key)) {
|
||||
return null;
|
||||
|
||||
@ -138,13 +138,13 @@ export class Props implements Omit<IBaseModelProps<IProp>, | 'getExtraProp' | 'g
|
||||
if (this.items.length < 1) {
|
||||
return {};
|
||||
}
|
||||
let allProps = {} as any;
|
||||
const allProps = {} as any;
|
||||
let props: any = {};
|
||||
const extras: any = {};
|
||||
if (this.type === 'list') {
|
||||
props = [];
|
||||
this.items.forEach((item) => {
|
||||
let value = item.export(stage);
|
||||
const value = item.export(stage);
|
||||
let name = item.key as string;
|
||||
if (name && typeof name === 'string' && name.startsWith(EXTRA_KEY_PREFIX)) {
|
||||
name = getOriginalExtraKey(name);
|
||||
@ -159,9 +159,9 @@ export class Props implements Omit<IBaseModelProps<IProp>, | 'getExtraProp' | 'g
|
||||
});
|
||||
} else {
|
||||
this.items.forEach((item) => {
|
||||
let name = item.key as string;
|
||||
const name = item.key as string;
|
||||
if (name == null || item.isUnset() || item.isVirtual()) return;
|
||||
let value = item.export(stage);
|
||||
const value = item.export(stage);
|
||||
if (value != null) {
|
||||
allProps[name] = value;
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ function propertyNameRequiresQuotes(propertyName: string) {
|
||||
}
|
||||
|
||||
function quoteString(str: string, { doubleQuote }: any) {
|
||||
return doubleQuote ? `"${str.replace(/"/gu, '\\"')}"` : `'${str.replace(/'/gu, "\\'")}'`;
|
||||
return doubleQuote ? `"${str.replace(/"/gu, '\\"')}"` : `'${str.replace(/'/gu, '\\\'')}'`;
|
||||
}
|
||||
|
||||
export function valueToSource(
|
||||
@ -96,12 +96,12 @@ export function valueToSource(
|
||||
|
||||
const itemsStayOnTheSameLine = value.every(
|
||||
item => typeof item === 'object' &&
|
||||
item &&
|
||||
!(item instanceof Date) &&
|
||||
!(item instanceof Map) &&
|
||||
!(item instanceof RegExp) &&
|
||||
!(item instanceof Set) &&
|
||||
(Object.keys(item).length || value.length === 1),
|
||||
item &&
|
||||
!(item instanceof Date) &&
|
||||
!(item instanceof Map) &&
|
||||
!(item instanceof RegExp) &&
|
||||
!(item instanceof Set) &&
|
||||
(Object.keys(item).length || value.length === 1),
|
||||
);
|
||||
|
||||
let previousIndex: number | null = null;
|
||||
|
||||
@ -52,9 +52,9 @@ export default class PluginContext implements
|
||||
command: IPublicApiCommand;
|
||||
|
||||
constructor(
|
||||
options: IPluginContextOptions,
|
||||
contextApiAssembler: ILowCodePluginContextApiAssembler,
|
||||
) {
|
||||
options: IPluginContextOptions,
|
||||
contextApiAssembler: ILowCodePluginContextApiAssembler,
|
||||
) {
|
||||
const { pluginName = 'anonymous', meta = {} } = options;
|
||||
contextApiAssembler.assembleApis(this, pluginName, meta);
|
||||
this.pluginEvent = createModuleEventBus(pluginName, 200);
|
||||
@ -71,7 +71,7 @@ export default class PluginContext implements
|
||||
const getPreferenceValue = (
|
||||
key: string,
|
||||
defaultValue?: IPublicTypePreferenceValueType,
|
||||
): IPublicTypePreferenceValueType | undefined => {
|
||||
): IPublicTypePreferenceValueType | undefined => {
|
||||
if (!isValidPreferenceKey(key, preferenceDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -16,24 +16,24 @@ import { ISimulatorHost } from '../simulator';
|
||||
export interface IProject extends Omit<IBaseApiProject<
|
||||
IDocumentModel
|
||||
>,
|
||||
'simulatorHost' |
|
||||
'importSchema' |
|
||||
'exportSchema' |
|
||||
'openDocument' |
|
||||
'getDocumentById' |
|
||||
'getCurrentDocument' |
|
||||
'addPropsTransducer' |
|
||||
'onRemoveDocument' |
|
||||
'onChangeDocument' |
|
||||
'onSimulatorHostReady' |
|
||||
'onSimulatorRendererReady' |
|
||||
'setI18n' |
|
||||
'setConfig' |
|
||||
'currentDocument' |
|
||||
'selection' |
|
||||
'documents' |
|
||||
'createDocument' |
|
||||
'getDocumentByFileName'
|
||||
'simulatorHost' |
|
||||
'importSchema' |
|
||||
'exportSchema' |
|
||||
'openDocument' |
|
||||
'getDocumentById' |
|
||||
'getCurrentDocument' |
|
||||
'addPropsTransducer' |
|
||||
'onRemoveDocument' |
|
||||
'onChangeDocument' |
|
||||
'onSimulatorHostReady' |
|
||||
'onSimulatorRendererReady' |
|
||||
'setI18n' |
|
||||
'setConfig' |
|
||||
'currentDocument' |
|
||||
'selection' |
|
||||
'documents' |
|
||||
'createDocument' |
|
||||
'getDocumentByFileName'
|
||||
> {
|
||||
|
||||
get designer(): IDesigner;
|
||||
|
||||
@ -34,7 +34,7 @@ export function normalizeTriggers(triggers: string[]) {
|
||||
/**
|
||||
* make a handler that listen all sensors:document, avoid frame lost
|
||||
*/
|
||||
export function makeEventsHandler(
|
||||
export function makeEventsHandler(
|
||||
boostEvent: MouseEvent | DragEvent,
|
||||
sensors: ISimulatorHost[],
|
||||
): (fn: (sdoc: Document) => void) => void {
|
||||
|
||||
0
packages/editor-core/__tests__/command.spec.ts
Normal file
0
packages/editor-core/__tests__/command.spec.ts
Normal file
@ -11,6 +11,10 @@
|
||||
".": {
|
||||
"import": "./dist/low-code-editor-core.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./dist/": {
|
||||
"import": "./dist/",
|
||||
"require": "./dist/"
|
||||
}
|
||||
},
|
||||
"sideEffects": [
|
||||
|
||||
@ -202,9 +202,9 @@ export class Editor extends EventEmitter implements IEditor {
|
||||
Array.isArray(d)
|
||||
? setArrayAssets(d, exportName, subName)
|
||||
: setAssetsComponent(d, {
|
||||
exportName,
|
||||
subName,
|
||||
});
|
||||
exportName,
|
||||
subName,
|
||||
});
|
||||
});
|
||||
}
|
||||
if ((window as any)[exportName]) {
|
||||
|
||||
@ -75,7 +75,7 @@ const KEYCODE_MAP: KeyMap = {
|
||||
219: '[',
|
||||
220: '\\',
|
||||
221: ']',
|
||||
222: "'",
|
||||
222: '\'',
|
||||
};
|
||||
|
||||
const SHIFT_MAP: CtrlKeyMap = {
|
||||
@ -93,7 +93,7 @@ const SHIFT_MAP: CtrlKeyMap = {
|
||||
_: '-',
|
||||
'+': '=',
|
||||
':': ';',
|
||||
'"': "'",
|
||||
'"': '\'',
|
||||
'<': ',',
|
||||
'>': '.',
|
||||
'?': '/',
|
||||
|
||||
@ -61,7 +61,7 @@ class GlobalLocale {
|
||||
}
|
||||
if (!result) {
|
||||
// store 2: config from window
|
||||
let localeFromConfig: string = getConfig('locale');
|
||||
const localeFromConfig: string = getConfig('locale');
|
||||
if (localeFromConfig) {
|
||||
result = languageMap[localeFromConfig] || localeFromConfig.replace('_', '-');
|
||||
logger.debug(`getting locale from config: ${result}`);
|
||||
@ -147,6 +147,6 @@ function hasLocalStorage(obj: any): obj is WindowLocalStorage {
|
||||
return obj.localStorage;
|
||||
}
|
||||
|
||||
let globalLocale = new GlobalLocale();
|
||||
const globalLocale = new GlobalLocale();
|
||||
|
||||
export { globalLocale };
|
||||
|
||||
@ -59,7 +59,7 @@ export class Title extends Component<IPublicTypeTitleProps> {
|
||||
}
|
||||
|
||||
renderLabel = (label: string | IPublicTypeI18nData | ReactNode) => {
|
||||
let { match, keywords } = this.props;
|
||||
const { match, keywords } = this.props;
|
||||
|
||||
if (!label) {
|
||||
return null;
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import { defineProject } from 'vitest/config'
|
||||
|
||||
export default defineProject({
|
||||
test: {}
|
||||
})
|
||||
@ -11,6 +11,10 @@
|
||||
"import": "./dist/low-code-editor-skeleton.js",
|
||||
"require": "./dist/low-code-editor-skeleton.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./dist/": {
|
||||
"import": "./dist/",
|
||||
"require": "./dist/"
|
||||
}
|
||||
},
|
||||
"sideEffects": [
|
||||
|
||||
@ -139,8 +139,8 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
|
||||
const { setter } = this.field;
|
||||
let setterProps:
|
||||
| ({
|
||||
setters?: (ReactNode | string)[];
|
||||
} & Record<string, unknown>)
|
||||
setters?: (ReactNode | string)[];
|
||||
} & Record<string, unknown>)
|
||||
| IPublicTypeDynamicProps = {};
|
||||
let setterType: any;
|
||||
let initialValue: any = null;
|
||||
@ -251,8 +251,8 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
|
||||
|
||||
const value = this.value;
|
||||
|
||||
let onChangeAPI = extraProps?.onChange;
|
||||
let stageName = this.stageName;
|
||||
const onChangeAPI = extraProps?.onChange;
|
||||
const stageName = this.stageName;
|
||||
|
||||
return createField(
|
||||
{
|
||||
@ -269,47 +269,47 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
|
||||
...extraProps,
|
||||
},
|
||||
!stageName &&
|
||||
this.setters?.createSetterContent(setterType, {
|
||||
...shallowIntl(setterProps),
|
||||
forceInline: extraProps.forceInline,
|
||||
key: field.id,
|
||||
// === injection
|
||||
prop: field.internalToShellField(), // for compatible vision
|
||||
selected: field.top?.getNode()?.internalToShellNode(),
|
||||
field: field.internalToShellField(),
|
||||
// === IO
|
||||
value, // reaction point
|
||||
initialValue,
|
||||
onChange: (value: any) => {
|
||||
this.setState({
|
||||
fromOnChange: true,
|
||||
// eslint-disable-next-line react/no-unused-state
|
||||
value,
|
||||
});
|
||||
field.setValue(value, true);
|
||||
if (onChangeAPI) onChangeAPI(value, field.internalToShellField());
|
||||
},
|
||||
onInitial: () => {
|
||||
if (initialValue == null) {
|
||||
return;
|
||||
}
|
||||
const value =
|
||||
this.setters?.createSetterContent(setterType, {
|
||||
...shallowIntl(setterProps),
|
||||
forceInline: extraProps.forceInline,
|
||||
key: field.id,
|
||||
// === injection
|
||||
prop: field.internalToShellField(), // for compatible vision
|
||||
selected: field.top?.getNode()?.internalToShellNode(),
|
||||
field: field.internalToShellField(),
|
||||
// === IO
|
||||
value, // reaction point
|
||||
initialValue,
|
||||
onChange: (value: any) => {
|
||||
this.setState({
|
||||
fromOnChange: true,
|
||||
// eslint-disable-next-line react/no-unused-state
|
||||
value,
|
||||
});
|
||||
field.setValue(value, true);
|
||||
if (onChangeAPI) onChangeAPI(value, field.internalToShellField());
|
||||
},
|
||||
onInitial: () => {
|
||||
if (initialValue == null) {
|
||||
return;
|
||||
}
|
||||
const value =
|
||||
typeof initialValue === 'function'
|
||||
? initialValue(field.internalToShellField())
|
||||
: initialValue;
|
||||
this.setState({
|
||||
// eslint-disable-next-line react/no-unused-state
|
||||
value,
|
||||
});
|
||||
field.setValue(value, true);
|
||||
},
|
||||
this.setState({
|
||||
// eslint-disable-next-line react/no-unused-state
|
||||
value,
|
||||
});
|
||||
field.setValue(value, true);
|
||||
},
|
||||
|
||||
removeProp: () => {
|
||||
if (field.name) {
|
||||
field.parent.clearPropValue(field.name);
|
||||
}
|
||||
},
|
||||
}),
|
||||
removeProp: () => {
|
||||
if (field.name) {
|
||||
field.parent.clearPropValue(field.name);
|
||||
}
|
||||
},
|
||||
}),
|
||||
extraProps.forceInline ? 'plain' : extraProps.display,
|
||||
);
|
||||
}
|
||||
|
||||
@ -104,29 +104,29 @@ export class SettingsPrimaryPane extends Component<
|
||||
l === 2
|
||||
? {}
|
||||
: {
|
||||
onMouseOver: hoverNode.bind(null, _node, true),
|
||||
onMouseOut: hoverNode.bind(null, _node, false),
|
||||
onClick: () => {
|
||||
if (!_node) {
|
||||
return;
|
||||
}
|
||||
selectNode.call(null, _node);
|
||||
const getName = (node: any) => {
|
||||
const npm = node?.componentMeta?.npm;
|
||||
return (
|
||||
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') ||
|
||||
node?.componentMeta?.componentName ||
|
||||
''
|
||||
);
|
||||
};
|
||||
const selected = getName(current);
|
||||
const target = getName(_node);
|
||||
editor?.eventBus.emit('skeleton.settingsPane.Breadcrumb', {
|
||||
selected,
|
||||
target,
|
||||
});
|
||||
},
|
||||
};
|
||||
onMouseOver: hoverNode.bind(null, _node, true),
|
||||
onMouseOut: hoverNode.bind(null, _node, false),
|
||||
onClick: () => {
|
||||
if (!_node) {
|
||||
return;
|
||||
}
|
||||
selectNode.call(null, _node);
|
||||
const getName = (node: any) => {
|
||||
const npm = node?.componentMeta?.npm;
|
||||
return (
|
||||
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') ||
|
||||
node?.componentMeta?.componentName ||
|
||||
''
|
||||
);
|
||||
};
|
||||
const selected = getName(current);
|
||||
const target = getName(_node);
|
||||
editor?.eventBus.emit('skeleton.settingsPane.Breadcrumb', {
|
||||
selected,
|
||||
target,
|
||||
});
|
||||
},
|
||||
};
|
||||
items.unshift(
|
||||
<Breadcrumb.Item {...props} key={node.id}>
|
||||
<Title title={node.title} />
|
||||
|
||||
@ -5,9 +5,9 @@ function getHotterFromSetter(setter: any) {
|
||||
function getTransducerFromSetter(setter: any) {
|
||||
return (
|
||||
(setter &&
|
||||
(setter.transducer ||
|
||||
setter.Transducer ||
|
||||
(setter.type && (setter.type.transducer || setter.type.Transducer)))) ||
|
||||
(setter.transducer ||
|
||||
setter.Transducer ||
|
||||
(setter.type && (setter.type.transducer || setter.type.Transducer)))) ||
|
||||
null
|
||||
); // eslint-disable-line
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ class Contents extends Component<{ area: Area; itemClassName?: string }> {
|
||||
right.push(content);
|
||||
}
|
||||
});
|
||||
let children = [];
|
||||
const children = [];
|
||||
if (left && left.length) {
|
||||
children.push(
|
||||
<div className="lc-workspace-sub-top-area-left lc-sub-top-area-left">{left}</div>,
|
||||
|
||||
@ -47,23 +47,23 @@ export enum SkeletonEvents {
|
||||
export interface ISkeleton extends Skeleton {}
|
||||
|
||||
export class Skeleton implements Omit<IPublicApiSkeleton,
|
||||
'showPanel' |
|
||||
'hidePanel' |
|
||||
'showWidget' |
|
||||
'enableWidget' |
|
||||
'hideWidget' |
|
||||
'disableWidget' |
|
||||
'showArea' |
|
||||
'onShowPanel' |
|
||||
'onHidePanel' |
|
||||
'onShowWidget' |
|
||||
'onHideWidget' |
|
||||
'remove' |
|
||||
'hideArea' |
|
||||
'add' |
|
||||
'getAreaItems' |
|
||||
'onDisableWidget' |
|
||||
'onEnableWidget'
|
||||
'showPanel' |
|
||||
'hidePanel' |
|
||||
'showWidget' |
|
||||
'enableWidget' |
|
||||
'hideWidget' |
|
||||
'disableWidget' |
|
||||
'showArea' |
|
||||
'onShowPanel' |
|
||||
'onHidePanel' |
|
||||
'onShowWidget' |
|
||||
'onHideWidget' |
|
||||
'remove' |
|
||||
'hideArea' |
|
||||
'add' |
|
||||
'getAreaItems' |
|
||||
'onDisableWidget' |
|
||||
'onEnableWidget'
|
||||
> {
|
||||
private panels = new Map<string, Panel>();
|
||||
|
||||
@ -425,7 +425,10 @@ export class Skeleton implements Omit<IPublicApiSkeleton,
|
||||
return this.configTransducers;
|
||||
}
|
||||
|
||||
add(config: IPublicTypeSkeletonConfig, extraConfig?: Record<string, any>): IWidget | Widget | Panel | Stage | Dock | PanelDock | undefined {
|
||||
add(
|
||||
config: IPublicTypeSkeletonConfig,
|
||||
extraConfig?: Record<string, any>
|
||||
): IWidget | Widget | Panel | Stage | Dock | PanelDock | undefined {
|
||||
const registeredTransducers = this.getRegisteredConfigTransducers();
|
||||
|
||||
const parsedConfig = registeredTransducers.reduce((prevConfig, current) => {
|
||||
|
||||
@ -221,23 +221,23 @@ export default function (
|
||||
setValue(field: IPublicModelSettingField, eventData) {
|
||||
const { eventDataList, eventList } = eventData;
|
||||
Array.isArray(eventList) &&
|
||||
eventList.map((item) => {
|
||||
field.parent.clearPropValue(item.name);
|
||||
return item;
|
||||
});
|
||||
eventList.map((item) => {
|
||||
field.parent.clearPropValue(item.name);
|
||||
return item;
|
||||
});
|
||||
Array.isArray(eventDataList) &&
|
||||
eventDataList.map((item) => {
|
||||
field.parent.setPropValue(item.name, {
|
||||
type: 'JSFunction',
|
||||
// 需要传下入参
|
||||
value: `function(){return this.${
|
||||
item.relatedEventName
|
||||
}.apply(this,Array.prototype.slice.call(arguments).concat([${
|
||||
item.paramStr ? item.paramStr : ''
|
||||
}])) }`,
|
||||
});
|
||||
return item;
|
||||
eventDataList.map((item) => {
|
||||
field.parent.setPropValue(item.name, {
|
||||
type: 'JSFunction',
|
||||
// 需要传下入参
|
||||
value: `function(){return this.${
|
||||
item.relatedEventName
|
||||
}.apply(this,Array.prototype.slice.call(arguments).concat([${
|
||||
item.paramStr ? item.paramStr : ''
|
||||
}])) }`,
|
||||
});
|
||||
return item;
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@ -20,7 +20,7 @@ function transformStringToFunction(str: string) {
|
||||
if (leadingFnNameRe.test(str) && !leadingFnRe.test(str)) {
|
||||
str = `function ${str}`;
|
||||
}
|
||||
let fnBody = `
|
||||
const fnBody = `
|
||||
return function() {
|
||||
const self = this;
|
||||
try {
|
||||
|
||||
@ -11,6 +11,10 @@
|
||||
"import": "./dist/ali-low-code-engine.js",
|
||||
"require": "./dist/ali-low-code-engine.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./dist/": {
|
||||
"import": "./dist/",
|
||||
"require": "./dist/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { createElement } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
import { isPlainObject } from '@alilc/lowcode-utils';
|
||||
import {
|
||||
globalContext,
|
||||
Editor,
|
||||
@ -28,16 +29,13 @@ import {
|
||||
PluginPreference,
|
||||
IDesigner,
|
||||
} from '@alilc/lowcode-designer';
|
||||
import {
|
||||
Skeleton as InnerSkeleton,
|
||||
registerDefaults,
|
||||
} from '@alilc/lowcode-editor-skeleton';
|
||||
import { Skeleton as InnerSkeleton, registerDefaults } from '@alilc/lowcode-editor-skeleton';
|
||||
|
||||
import {
|
||||
Workspace as InnerWorkspace,
|
||||
Workbench as WorkSpaceWorkbench,
|
||||
IWorkspace,
|
||||
} from './workspace';
|
||||
|
||||
import {
|
||||
Hotkey,
|
||||
Project,
|
||||
@ -54,10 +52,10 @@ import {
|
||||
CommonUI,
|
||||
Command,
|
||||
} from './shell';
|
||||
import { isPlainObject } from '@alilc/lowcode-utils';
|
||||
import './modules/live-editing';
|
||||
import * as classes from './modules/classes';
|
||||
import symbols from './modules/symbols';
|
||||
|
||||
import { componentMetaParser } from './inner-plugins/component-meta-parser';
|
||||
import { setterRegistry } from './inner-plugins/setter-registry';
|
||||
import { defaultPanelRegistry } from './inner-plugins/default-panel-registry';
|
||||
@ -66,13 +64,19 @@ import { builtinHotkey } from './inner-plugins/builtin-hotkey';
|
||||
import { defaultContextMenu } from './inner-plugins/default-context-menu';
|
||||
import { CommandPlugin } from '@alilc/lowcode-plugin-command';
|
||||
import { OutlinePlugin } from '@alilc/lowcode-plugin-outline-pane';
|
||||
import { version } from '../package.json'
|
||||
import { version } from '../package.json';
|
||||
|
||||
import '@alilc/lowcode-editor-skeleton/dist/style.css';
|
||||
|
||||
export * from './modules/skeleton-types';
|
||||
export * from './modules/designer-types';
|
||||
export * from './modules/lowcode-types';
|
||||
|
||||
async function registryInnerPlugin(designer: IDesigner, editor: IEditor, plugins: IPublicApiPlugins): Promise<IPublicTypeDisposable> {
|
||||
async function registryInnerPlugin(
|
||||
designer: IDesigner,
|
||||
editor: IEditor,
|
||||
plugins: IPublicApiPlugins
|
||||
): Promise<IPublicTypeDisposable> {
|
||||
// 注册一批内置插件
|
||||
const componentMetaParserPlugin = componentMetaParser(designer);
|
||||
const defaultPanelRegistryPlugin = defaultPanelRegistry(editor);
|
||||
@ -97,9 +101,13 @@ async function registryInnerPlugin(designer: IDesigner, editor: IEditor, plugins
|
||||
};
|
||||
}
|
||||
|
||||
const innerWorkspace: IWorkspace = new InnerWorkspace(registryInnerPlugin, shellModelFactory);
|
||||
const innerWorkspace: IWorkspace = new InnerWorkspace(
|
||||
registryInnerPlugin,
|
||||
shellModelFactory
|
||||
);
|
||||
const workspace: IPublicApiWorkspace = new Workspace(innerWorkspace);
|
||||
const editor = new Editor();
|
||||
|
||||
globalContext.register(editor, Editor);
|
||||
globalContext.register(editor, 'editor');
|
||||
globalContext.register(innerWorkspace, 'workspace');
|
||||
@ -121,24 +129,30 @@ const skeleton = new Skeleton(innerSkeleton, 'any', false);
|
||||
const innerSetters = new InnerSetters();
|
||||
const setters = new Setters(innerSetters);
|
||||
const innerCommand = new InnerCommand();
|
||||
const command = new Command(innerCommand, engineContext as IPublicModelPluginContext);
|
||||
|
||||
const command = new Command(
|
||||
innerCommand,
|
||||
engineContext as IPublicModelPluginContext
|
||||
);
|
||||
const material = new Material(editor);
|
||||
const commonUI = new CommonUI(editor);
|
||||
|
||||
editor.set('project', project);
|
||||
editor.set('setters' as any, setters);
|
||||
editor.set('material', material);
|
||||
editor.set('innerHotkey', innerHotkey);
|
||||
|
||||
const config = new Config(engineConfig);
|
||||
const event = new Event(commonEvent, { prefix: 'common' });
|
||||
const logger = new Logger({ level: 'warn', bizName: 'common' });
|
||||
const common = new Common(editor, innerSkeleton);
|
||||
const canvas = new Canvas(editor);
|
||||
let plugins: Plugins;
|
||||
|
||||
const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
assembleApis: (context: ILowCodePluginContextPrivate, pluginName: string, meta: IPublicTypePluginMeta) => {
|
||||
assembleApis: (
|
||||
context: ILowCodePluginContextPrivate,
|
||||
pluginName: string,
|
||||
meta: IPublicTypePluginMeta
|
||||
) => {
|
||||
context.hotkey = hotkey;
|
||||
context.project = project;
|
||||
context.skeleton = new Skeleton(innerSkeleton, pluginName, false);
|
||||
@ -154,9 +168,10 @@ const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
|
||||
context.logger = new Logger({ level: 'warn', bizName: `plugin:${pluginName}` });
|
||||
context.workspace = workspace;
|
||||
context.commonUI = commonUI;
|
||||
context.command = new Command(innerCommand, context as IPublicModelPluginContext, {
|
||||
commandScope,
|
||||
});
|
||||
context.command = new Command(
|
||||
innerCommand, context as IPublicModelPluginContext, {
|
||||
commandScope,
|
||||
});
|
||||
context.registerLevel = IPublicEnumPluginRegisterLevel.Default;
|
||||
context.isPluginRegisteredInWorkspace = false;
|
||||
editor.set('pluginContext', context);
|
||||
@ -164,7 +179,7 @@ const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
|
||||
};
|
||||
|
||||
const innerPlugins = new LowCodePluginManager(pluginContextApiAssembler);
|
||||
plugins = new Plugins(innerPlugins).toProxy();
|
||||
const plugins = new Plugins(innerPlugins).toProxy();
|
||||
editor.set('innerPlugins' as any, innerPlugins);
|
||||
editor.set('plugins' as any, plugins);
|
||||
|
||||
@ -198,19 +213,25 @@ export {
|
||||
commonUI,
|
||||
command,
|
||||
};
|
||||
|
||||
|
||||
// declare this is open-source version
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const isOpenSource = true;
|
||||
engineConfig.set('isOpenSource', isOpenSource);
|
||||
|
||||
engineConfig.set('ENGINE_VERSION', version);
|
||||
export { version };
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
|
||||
symbols,
|
||||
classes,
|
||||
};
|
||||
engineConfig.set('isOpenSource', isOpenSource);
|
||||
|
||||
// container which will host LowCodeEngine DOM
|
||||
let engineContainer: HTMLElement;
|
||||
|
||||
export { version }
|
||||
engineConfig.set('ENGINE_VERSION', version);
|
||||
|
||||
const pluginPromise = registryInnerPlugin(designer, editor, plugins);
|
||||
|
||||
@ -219,10 +240,14 @@ let root: Root | undefined;
|
||||
export async function init(
|
||||
container?: HTMLElement,
|
||||
options?: IPublicTypeEngineOptions,
|
||||
pluginPreference?: PluginPreference,
|
||||
) {
|
||||
pluginPreference?: PluginPreference
|
||||
) {
|
||||
await destroy();
|
||||
|
||||
// container which will host LowCodeEngine DOM
|
||||
let engineContainer: HTMLElement;
|
||||
let engineOptions = null;
|
||||
|
||||
if (isPlainObject(container)) {
|
||||
engineOptions = container;
|
||||
engineContainer = document.createElement('div');
|
||||
@ -237,6 +262,7 @@ export async function init(
|
||||
document.body.appendChild(engineContainer);
|
||||
}
|
||||
}
|
||||
|
||||
engineConfig.setEngineOptions(engineOptions as any);
|
||||
|
||||
const { Workbench } = common.skeletonCabin;
|
||||
@ -245,15 +271,15 @@ export async function init(
|
||||
disposeFun && disposeFun();
|
||||
|
||||
if (!root) {
|
||||
root = createRoot(
|
||||
engineContainer,
|
||||
root = createRoot(engineContainer);
|
||||
root.render(
|
||||
createElement(WorkSpaceWorkbench, {
|
||||
workspace: innerWorkspace,
|
||||
// skeleton: workspace.skeleton,
|
||||
className: 'engine-main',
|
||||
topAreaItemClassName: 'engine-actionitem',
|
||||
})
|
||||
);
|
||||
root.render(createElement(WorkSpaceWorkbench, {
|
||||
workspace: innerWorkspace,
|
||||
// skeleton: workspace.skeleton,
|
||||
className: 'engine-main',
|
||||
topAreaItemClassName: 'engine-actionitem',
|
||||
}))
|
||||
}
|
||||
|
||||
innerWorkspace.enableAutoOpenFirstWindow = engineConfig.get('enableAutoOpenFirstWindow', true);
|
||||
@ -267,12 +293,14 @@ export async function init(
|
||||
await plugins.init(pluginPreference as any);
|
||||
|
||||
if (!root) {
|
||||
root = createRoot(engineContainer)
|
||||
root.render(createElement(Workbench, {
|
||||
skeleton: innerSkeleton,
|
||||
className: 'engine-main',
|
||||
topAreaItemClassName: 'engine-actionitem',
|
||||
}))
|
||||
root = createRoot(engineContainer);
|
||||
root.render(
|
||||
createElement(Workbench, {
|
||||
skeleton: innerSkeleton,
|
||||
className: 'engine-main',
|
||||
topAreaItemClassName: 'engine-actionitem',
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,7 +308,9 @@ export async function destroy() {
|
||||
// remove all documents
|
||||
const { documents } = project;
|
||||
if (Array.isArray(documents) && documents.length > 0) {
|
||||
documents.forEach(((doc: IPublicModelDocumentModel) => project.removeDocument(doc)));
|
||||
documents.forEach(
|
||||
(doc: IPublicModelDocumentModel) => project.removeDocument(doc)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: delete plugins except for core plugins
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { version } from './engine-core';
|
||||
|
||||
export * from './engine-core';
|
||||
|
||||
console.log(
|
||||
`%c AliLowCodeEngine %c v${version} `,
|
||||
'padding: 2px 1px; border-radius: 3px 0 0 3px; color: #fff; background: #606060; font-weight: bold;',
|
||||
|
||||
@ -328,7 +328,7 @@ export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
let canAddComponentsTree = componentsTree.filter((node: IPublicModelNode) => {
|
||||
const canAddComponentsTree = componentsTree.filter((node: IPublicModelNode) => {
|
||||
const dragNodeObject: IPublicTypeDragNodeObject = {
|
||||
type: IPublicEnumDragObjectType.Node,
|
||||
nodes: [node],
|
||||
|
||||
@ -129,7 +129,7 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
|
||||
return;
|
||||
}
|
||||
if (parent) {
|
||||
let canAddNodes = nodeSchema.filter((nodeSchema: IPublicTypeNodeSchema) => {
|
||||
const canAddNodes = nodeSchema.filter((nodeSchema: IPublicTypeNodeSchema) => {
|
||||
const dragNodeObject: IPublicTypeDragNodeDataObject = {
|
||||
type: IPublicEnumDragObjectType.NodeData,
|
||||
data: nodeSchema,
|
||||
@ -177,7 +177,7 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
|
||||
if (nodeSchema.length === 0) {
|
||||
return;
|
||||
}
|
||||
let canAddNodes = nodeSchema.filter((nodeSchema: IPublicTypeNodeSchema) => {
|
||||
const canAddNodes = nodeSchema.filter((nodeSchema: IPublicTypeNodeSchema) => {
|
||||
const dragNodeObject: IPublicTypeDragNodeDataObject = {
|
||||
type: IPublicEnumDragObjectType.NodeData,
|
||||
data: nodeSchema,
|
||||
|
||||
@ -6,11 +6,13 @@ export const setterRegistry = (ctx: IPublicModelPluginContext) => {
|
||||
init() {
|
||||
const { config } = ctx;
|
||||
if (config.get('disableDefaultSetters')) return;
|
||||
// todo: 互相依赖
|
||||
|
||||
// const builtinSetters = require('@alilc/lowcode-engine-ext')?.setters;
|
||||
// if (builtinSetters) {
|
||||
// ctx.setters.registerSetter(builtinSetters);
|
||||
// }
|
||||
// @ts-expect-error: todo remove
|
||||
const builtinSetters = window.AliLowCodeEngineExt?.setters;
|
||||
if (builtinSetters) {
|
||||
ctx.setters.registerSetter(builtinSetters);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -13,4 +13,3 @@ export {
|
||||
SkeletonItem,
|
||||
} from '../shell';
|
||||
export { Node as InnerNode } from '@alilc/lowcode-designer';
|
||||
|
||||
|
||||
@ -26,13 +26,6 @@ export class Hotkey implements IPublicApiHotkey {
|
||||
return this[hotkeySymbol].callBacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
get callBacks() {
|
||||
return this.callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定快捷键
|
||||
* @param combos 快捷键,格式如:['command + s'] 、['ctrl + shift + s'] 等
|
||||
@ -41,10 +34,10 @@ export class Hotkey implements IPublicApiHotkey {
|
||||
* @returns
|
||||
*/
|
||||
bind(
|
||||
combos: string[] | string,
|
||||
callback: IPublicTypeHotkeyCallback,
|
||||
action?: string,
|
||||
): IPublicTypeDisposable {
|
||||
combos: string[] | string,
|
||||
callback: IPublicTypeHotkeyCallback,
|
||||
action?: string,
|
||||
): IPublicTypeDisposable {
|
||||
this[hotkeySymbol].bind(combos, callback, action);
|
||||
return () => {
|
||||
this[hotkeySymbol].unbind(combos, callback, action);
|
||||
|
||||
@ -141,7 +141,7 @@ export class Material implements IPublicApiMaterial {
|
||||
getComponentMetasMap(): Map<string, IPublicModelComponentMeta> {
|
||||
const map = new Map<string, IPublicModelComponentMeta>();
|
||||
const originalMap = this[designerSymbol].getComponentMetasMap();
|
||||
for (let componentName of originalMap.keys()) {
|
||||
for (const componentName of originalMap.keys()) {
|
||||
map.set(componentName, this.getComponentMeta(componentName)!);
|
||||
}
|
||||
return map;
|
||||
|
||||
@ -44,8 +44,8 @@ export class Plugins implements IPublicApiPlugins {
|
||||
}
|
||||
|
||||
getPluginPreference(
|
||||
pluginName: string,
|
||||
): Record<string, IPublicTypePreferenceValueType> | null | undefined {
|
||||
pluginName: string,
|
||||
): Record<string, IPublicTypePreferenceValueType> | null | undefined {
|
||||
return this[pluginsSymbol].getPluginPreference(pluginName);
|
||||
}
|
||||
|
||||
|
||||
@ -164,9 +164,9 @@ export class Project implements IPublicApiProject {
|
||||
* @param stage
|
||||
*/
|
||||
addPropsTransducer(
|
||||
transducer: IPublicTypePropsTransducer,
|
||||
stage: IPublicEnumTransformStage,
|
||||
): void {
|
||||
transducer: IPublicTypePropsTransducer,
|
||||
stage: IPublicEnumTransformStage,
|
||||
): void {
|
||||
this[projectSymbol].designer.addPropsReducer(transducer, stage);
|
||||
}
|
||||
|
||||
@ -177,9 +177,9 @@ export class Project implements IPublicApiProject {
|
||||
*/
|
||||
onRemoveDocument(fn: (data: { id: string}) => void): IPublicTypeDisposable {
|
||||
return this[editorSymbol].eventBus.on(
|
||||
'designer.document.remove',
|
||||
(data: { id: string }) => fn(data),
|
||||
);
|
||||
'designer.document.remove',
|
||||
(data: { id: string }) => fn(data),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -33,10 +33,10 @@ export class Skeleton implements IPublicApiSkeleton {
|
||||
}
|
||||
|
||||
constructor(
|
||||
skeleton: ISkeleton,
|
||||
pluginName: string,
|
||||
readonly workspaceMode: boolean = false,
|
||||
) {
|
||||
skeleton: ISkeleton,
|
||||
pluginName: string,
|
||||
readonly workspaceMode: boolean = false,
|
||||
) {
|
||||
this[innerSkeletonSymbol] = skeleton;
|
||||
this.pluginName = pluginName;
|
||||
}
|
||||
|
||||
@ -14,9 +14,9 @@ export class Clipboard implements IPublicModelClipboard {
|
||||
}
|
||||
|
||||
waitPasteData(
|
||||
keyboardEvent: KeyboardEvent,
|
||||
cb: (data: any, clipboardEvent: ClipboardEvent) => void,
|
||||
): void {
|
||||
keyboardEvent: KeyboardEvent,
|
||||
cb: (data: any, clipboardEvent: ClipboardEvent) => void,
|
||||
): void {
|
||||
this[clipboardSymbol].waitPasteData(keyboardEvent, cb);
|
||||
}
|
||||
}
|
||||
@ -130,14 +130,14 @@ export class ComponentMeta implements IPublicModelComponentMeta {
|
||||
* @returns
|
||||
*/
|
||||
checkNestingDown(
|
||||
my: IPublicModelNode | IPublicTypeNodeData,
|
||||
target: IPublicTypeNodeSchema | IPublicModelNode | IPublicTypeNodeSchema[],
|
||||
) {
|
||||
my: IPublicModelNode | IPublicTypeNodeData,
|
||||
target: IPublicTypeNodeSchema | IPublicModelNode | IPublicTypeNodeSchema[],
|
||||
) {
|
||||
const curNode = (my as any)?.isNode ? (my as any)[nodeSymbol] : my;
|
||||
return this[componentMetaSymbol].checkNestingDown(
|
||||
curNode as any,
|
||||
(target as any)[nodeSymbol] || target,
|
||||
);
|
||||
curNode as any,
|
||||
(target as any)[nodeSymbol] || target,
|
||||
);
|
||||
}
|
||||
|
||||
refreshMetadata(): void {
|
||||
|
||||
@ -111,8 +111,8 @@ export class DocumentModel implements IPublicModelDocumentModel {
|
||||
this._focusNode = node;
|
||||
this[editorSymbol].eventBus.emit(
|
||||
'shell.document.focusNodeChanged',
|
||||
{ document: this, focusNode: node },
|
||||
);
|
||||
{ document: this, focusNode: node },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,7 +121,7 @@ export class DocumentModel implements IPublicModelDocumentModel {
|
||||
*/
|
||||
get nodesMap(): Map<string, IPublicModelNode> {
|
||||
const map = new Map<string, IPublicModelNode>();
|
||||
for (let id of this[documentSymbol].nodesMap.keys()) {
|
||||
for (const id of this[documentSymbol].nodesMap.keys()) {
|
||||
map.set(id, this.getNodeById(id)!);
|
||||
}
|
||||
return map;
|
||||
@ -227,14 +227,14 @@ export class DocumentModel implements IPublicModelDocumentModel {
|
||||
* @returns boolean 是否可以放置
|
||||
*/
|
||||
checkNesting(
|
||||
dropTarget: IPublicModelNode,
|
||||
dragObject: IPublicTypeDragNodeObject | IPublicTypeDragNodeDataObject,
|
||||
): boolean {
|
||||
let innerDragObject = dragObject;
|
||||
dropTarget: IPublicModelNode,
|
||||
dragObject: IPublicTypeDragNodeObject | IPublicTypeDragNodeDataObject,
|
||||
): boolean {
|
||||
const innerDragObject = dragObject;
|
||||
if (isDragNodeObject(dragObject)) {
|
||||
innerDragObject.nodes = innerDragObject.nodes?.map(
|
||||
(node: IPublicModelNode) => ((node as any)[nodeSymbol] || node),
|
||||
);
|
||||
(node: IPublicModelNode) => ((node as any)[nodeSymbol] || node),
|
||||
);
|
||||
}
|
||||
return this[documentSymbol].checkNesting(
|
||||
((dropTarget as any)[nodeSymbol] || dropTarget) as any,
|
||||
|
||||
@ -41,9 +41,9 @@ export class Dragon implements IPublicModelDragon {
|
||||
}
|
||||
|
||||
static create(
|
||||
dragon: IDragon | null,
|
||||
workspaceMode: boolean,
|
||||
): IPublicModelDragon | null {
|
||||
dragon: IDragon | null,
|
||||
workspaceMode: boolean,
|
||||
): IPublicModelDragon | null {
|
||||
if (!dragon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ export class ModalNodesManager implements IPublicModelModalNodesManager {
|
||||
* 设置指定节点为不可见态
|
||||
* @param node Node
|
||||
*/
|
||||
setInvisible(node: IPublicModelNode): void {
|
||||
setInvisible(node: IPublicModelNode): void {
|
||||
this[modalNodesManagerSymbol].setInvisible((node as any)[nodeSymbol]);
|
||||
}
|
||||
}
|
||||
@ -518,9 +518,9 @@ export class Node implements IPublicModelNode {
|
||||
* @returns
|
||||
*/
|
||||
exportSchema(
|
||||
stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Render,
|
||||
options?: any,
|
||||
): IPublicTypeNodeSchema {
|
||||
stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Render,
|
||||
options?: any,
|
||||
): IPublicTypeNodeSchema {
|
||||
return this[nodeSymbol].export(stage, options);
|
||||
}
|
||||
|
||||
@ -531,15 +531,15 @@ export class Node implements IPublicModelNode {
|
||||
* @param useMutator
|
||||
*/
|
||||
insertBefore(
|
||||
node: IPublicModelNode,
|
||||
ref?: IPublicModelNode | undefined,
|
||||
useMutator?: boolean,
|
||||
): void {
|
||||
node: IPublicModelNode,
|
||||
ref?: IPublicModelNode | undefined,
|
||||
useMutator?: boolean,
|
||||
): void {
|
||||
this[nodeSymbol].insertBefore(
|
||||
(node as any)[nodeSymbol] || node,
|
||||
(ref as any)?.[nodeSymbol],
|
||||
useMutator,
|
||||
);
|
||||
(node as any)[nodeSymbol] || node,
|
||||
(ref as any)?.[nodeSymbol],
|
||||
useMutator,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -549,15 +549,15 @@ export class Node implements IPublicModelNode {
|
||||
* @param useMutator
|
||||
*/
|
||||
insertAfter(
|
||||
node: IPublicModelNode,
|
||||
ref?: IPublicModelNode | undefined,
|
||||
useMutator?: boolean,
|
||||
): void {
|
||||
node: IPublicModelNode,
|
||||
ref?: IPublicModelNode | undefined,
|
||||
useMutator?: boolean,
|
||||
): void {
|
||||
this[nodeSymbol].insertAfter(
|
||||
(node as any)[nodeSymbol] || node,
|
||||
(ref as any)?.[nodeSymbol],
|
||||
useMutator,
|
||||
);
|
||||
(node as any)[nodeSymbol] || node,
|
||||
(ref as any)?.[nodeSymbol],
|
||||
useMutator,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -56,7 +56,7 @@ import { IEditorWindow } from '../window';
|
||||
export interface IBasicContext extends BasicContext {}
|
||||
|
||||
export class BasicContext
|
||||
implements
|
||||
implements
|
||||
Omit<
|
||||
IPublicModelPluginContext,
|
||||
'workspace' | 'commonUI' | 'command' | 'isPluginRegisteredInWorkspace' | 'editorWindow'
|
||||
|
||||
@ -15,9 +15,9 @@ export class Workbench extends Component<{
|
||||
className?: string;
|
||||
topAreaItemClassName?: string;
|
||||
}, {
|
||||
workspaceEmptyComponent: any;
|
||||
theme?: string;
|
||||
}> {
|
||||
workspaceEmptyComponent: any;
|
||||
theme?: string;
|
||||
}> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const { config, components, workspace } = this.props;
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import { defineProject } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ['tests/*.spec.ts'],
|
||||
environment: 'jsdom'
|
||||
}
|
||||
export default defineProject({
|
||||
test: {}
|
||||
})
|
||||
|
||||
@ -195,7 +195,7 @@ export class PaneController implements IPublicModelSensor, ITreeBoard, IPublicTy
|
||||
if (
|
||||
originLoc &&
|
||||
((pos && pos === 'unchanged') ||
|
||||
(irect && globalY >= irect.top && globalY <= irect.bottom)) &&
|
||||
(irect && globalY >= irect.top && globalY <= irect.bottom)) &&
|
||||
dragObject
|
||||
) {
|
||||
const loc = originLoc.clone(e);
|
||||
|
||||
@ -66,7 +66,7 @@ export class TreeMaster {
|
||||
'en-US': enUS,
|
||||
'zh-CN': zhCN,
|
||||
});
|
||||
let _pluginContext: IOutlinePanelPluginContext = Object.assign(pluginContext, {
|
||||
const _pluginContext: IOutlinePanelPluginContext = Object.assign(pluginContext, {
|
||||
intl,
|
||||
intlNode,
|
||||
getLocale,
|
||||
|
||||
@ -13,8 +13,8 @@ export class Pane extends PureComponent<{
|
||||
controller: PaneController;
|
||||
hideFilter?: boolean;
|
||||
}, {
|
||||
tree: Tree | null;
|
||||
}> {
|
||||
tree: Tree | null;
|
||||
}> {
|
||||
private controller;
|
||||
|
||||
private simulatorRendererReadyDispose: IPublicTypeDisposable;
|
||||
|
||||
@ -69,10 +69,10 @@ interface ITreeNodeChildrenState {
|
||||
dropDetail: IPublicTypeLocationChildrenDetail | undefined | null;
|
||||
}
|
||||
class TreeNodeChildren extends PureComponent<{
|
||||
treeNode: TreeNode;
|
||||
isModal?: boolean;
|
||||
treeChildren: TreeNode[] | null;
|
||||
}, ITreeNodeChildrenState> {
|
||||
treeNode: TreeNode;
|
||||
isModal?: boolean;
|
||||
treeChildren: TreeNode[] | null;
|
||||
}, ITreeNodeChildrenState> {
|
||||
state: ITreeNodeChildrenState = {
|
||||
filterWorking: false,
|
||||
matchSelf: false,
|
||||
@ -96,7 +96,7 @@ class TreeNodeChildren extends PureComponent<{
|
||||
filterWorking: newFilterWorking,
|
||||
matchSelf: newMatchChild,
|
||||
keywords: newKeywords,
|
||||
} = treeNode.filterReult;
|
||||
} = treeNode.filterReult;
|
||||
this.setState({
|
||||
filterWorking: newFilterWorking,
|
||||
matchSelf: newMatchChild,
|
||||
@ -104,10 +104,10 @@ class TreeNodeChildren extends PureComponent<{
|
||||
});
|
||||
});
|
||||
this.offLocationChanged = project.currentDocument?.onDropLocationChanged(
|
||||
() => {
|
||||
this.setState({ dropDetail: treeNode.dropDetail });
|
||||
},
|
||||
);
|
||||
() => {
|
||||
this.setState({ dropDetail: treeNode.dropDetail });
|
||||
},
|
||||
);
|
||||
}
|
||||
componentWillUnmount(): void {
|
||||
this.offLocationChanged && this.offLocationChanged();
|
||||
@ -188,8 +188,8 @@ class TreeNodeChildren extends PureComponent<{
|
||||
}
|
||||
|
||||
class TreeNodeSlots extends PureComponent<{
|
||||
treeNode: TreeNode;
|
||||
}> {
|
||||
treeNode: TreeNode;
|
||||
}> {
|
||||
render() {
|
||||
const { treeNode } = this.props;
|
||||
if (!treeNode.hasSlots()) {
|
||||
|
||||
@ -103,21 +103,21 @@ export default class TreeNodeView extends PureComponent<{
|
||||
matchChild: boolean;
|
||||
matchSelf: boolean;
|
||||
} = {
|
||||
expanded: false,
|
||||
selected: false,
|
||||
hidden: false,
|
||||
locked: false,
|
||||
detecting: false,
|
||||
isRoot: false,
|
||||
highlight: false,
|
||||
dropping: false,
|
||||
conditionFlow: false,
|
||||
expandable: false,
|
||||
treeChildren: [],
|
||||
filterWorking: false,
|
||||
matchChild: false,
|
||||
matchSelf: false,
|
||||
};
|
||||
expanded: false,
|
||||
selected: false,
|
||||
hidden: false,
|
||||
locked: false,
|
||||
detecting: false,
|
||||
isRoot: false,
|
||||
highlight: false,
|
||||
dropping: false,
|
||||
conditionFlow: false,
|
||||
expandable: false,
|
||||
treeChildren: [],
|
||||
filterWorking: false,
|
||||
matchChild: false,
|
||||
matchSelf: false,
|
||||
};
|
||||
|
||||
eventOffCallbacks: Array<IPublicTypeDisposable | undefined> = [];
|
||||
constructor(props: any) {
|
||||
@ -232,7 +232,7 @@ export default class TreeNodeView extends PureComponent<{
|
||||
'condition-flow': this.state.conditionFlow,
|
||||
highlight: this.state.highlight,
|
||||
});
|
||||
let shouldShowModalTreeNode: boolean = this.shouldShowModalTreeNode();
|
||||
const shouldShowModalTreeNode: boolean = this.shouldShowModalTreeNode();
|
||||
|
||||
// filter 处理
|
||||
const { filterWorking, matchChild, matchSelf } = this.state;
|
||||
|
||||
@ -52,12 +52,12 @@ export default class TreeTitle extends PureComponent<{
|
||||
keywords: string;
|
||||
matchSelf: boolean;
|
||||
} = {
|
||||
editing: false,
|
||||
title: '',
|
||||
filterWorking: false,
|
||||
keywords: '',
|
||||
matchSelf: false,
|
||||
};
|
||||
editing: false,
|
||||
title: '',
|
||||
filterWorking: false,
|
||||
keywords: '',
|
||||
matchSelf: false,
|
||||
};
|
||||
|
||||
private lastInput?: HTMLInputElement;
|
||||
|
||||
|
||||
@ -30,8 +30,8 @@ export default class TreeView extends PureComponent<{
|
||||
state: {
|
||||
root: TreeNode | null;
|
||||
} = {
|
||||
root: null,
|
||||
};
|
||||
root: null,
|
||||
};
|
||||
|
||||
private hover(e: ReactMouseEvent) {
|
||||
const { project } = this.props.tree.pluginContext;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@alilc/lowcode-react-renderer",
|
||||
"version": "2.0.0-beta.0",
|
||||
"version": "2.0.0-alpha.0",
|
||||
"description": "react renderer for ali lowcode engine",
|
||||
"type": "module",
|
||||
"bugs": "https://github.com/alibaba/lowcode-engine/issues",
|
||||
@ -18,7 +18,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build:target": "vite build",
|
||||
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.mjs",
|
||||
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.js",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -3,3 +3,5 @@ import { createComponent as internalCreate, ComponentOptions } from '../componen
|
||||
export function createComponent(options: ComponentOptions) {
|
||||
return internalCreate(options);
|
||||
}
|
||||
|
||||
export type { ComponentOptions };
|
||||
|
||||
@ -81,7 +81,7 @@ export function compile(tokens: Token[], values: Record<string, any> | any[] = {
|
||||
break;
|
||||
case 'unknown':
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.warn(`Detect 'unknown' type of token!`);
|
||||
console.warn('Detect \'unknown\' type of token!');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ async function appendExternalCss(
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let el: HTMLLinkElement = document.createElement('link');
|
||||
const el: HTMLLinkElement = document.createElement('link');
|
||||
el.rel = 'stylesheet';
|
||||
el.href = url;
|
||||
|
||||
@ -122,7 +122,7 @@ export async function appendExternalStyle(
|
||||
root: HTMLElement = document.head,
|
||||
): Promise<HTMLElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let el: HTMLStyleElement = document.createElement('style');
|
||||
const el: HTMLStyleElement = document.createElement('style');
|
||||
el.innerText = cssText;
|
||||
|
||||
el.addEventListener(
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { useEvent, createHookStore, type HookStore } from '../../src/utils/hook';
|
||||
import { createEvent, createHookStore, type HookStore } from '../../src/utils/hook';
|
||||
|
||||
describe('event', () => {
|
||||
it("event's listener ops", () => {
|
||||
const event = useEvent();
|
||||
it('event\'s listener ops', () => {
|
||||
const event = createEvent();
|
||||
const fn = () => {};
|
||||
event.add(fn);
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
{
|
||||
"name": "@alilc/lowcode-renderer-core",
|
||||
"version": "2.0.0-beta.0",
|
||||
"description": "",
|
||||
"version": "2.0.0-alpha.0",
|
||||
"description": "core package for lowCode renderer",
|
||||
"type": "module",
|
||||
"bugs": "https://github.com/alibaba/lowcode-engine/issues",
|
||||
"homepage": "https://github.com/alibaba/lowcode-engine/#readme",
|
||||
"license": "MIT",
|
||||
"main": "dist/low-code-renderer-core.js",
|
||||
"main": "dist/low-code-renderer-core.cjs",
|
||||
"module": "dist/low-code-renderer-core.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/low-code-renderer-core.js",
|
||||
"require": "./dist/low-code-renderer-core.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
@ -22,7 +23,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build:target": "vite build",
|
||||
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.mjs",
|
||||
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.js",
|
||||
"test": "vitest --run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
|
||||
@ -5,7 +5,7 @@ export { createCodeRuntime, createScope } from './code-runtime';
|
||||
export { definePlugin } from './plugin';
|
||||
export { createWidget } from './widget';
|
||||
export { createContainer } from './container';
|
||||
export { createHookStore, useEvent } from './utils/hook';
|
||||
export { createHookStore, createEvent } from './utils/hook';
|
||||
export * from './utils/type-guard';
|
||||
export * from './utils/value';
|
||||
export * from './widget';
|
||||
|
||||
@ -8,7 +8,7 @@ export class RuntimeError extends Error {
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
appBoosts.hookStore.call(`app:error`, this);
|
||||
appBoosts.hookStore.call('app:error', this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import type { AnyFunction } from '../types';
|
||||
|
||||
export type EventName = string | number | symbol;
|
||||
|
||||
export function useEvent<T = AnyFunction>() {
|
||||
export function createEvent<T = AnyFunction>() {
|
||||
let events: T[] = [];
|
||||
|
||||
function add(fn: T) {
|
||||
@ -31,7 +31,7 @@ export function useEvent<T = AnyFunction>() {
|
||||
};
|
||||
}
|
||||
|
||||
export type Event<F = AnyFunction> = ReturnType<typeof useEvent<F>>;
|
||||
export type Event<F = AnyFunction> = ReturnType<typeof createEvent<F>>;
|
||||
|
||||
export type HookCallback = (...args: any) => Promise<any> | any;
|
||||
|
||||
@ -93,7 +93,7 @@ export function createHookStore<
|
||||
|
||||
let hooks = hooksMap.get(name);
|
||||
if (!hooks) {
|
||||
hooks = useEvent();
|
||||
hooks = createEvent();
|
||||
hooksMap.set(name, hooks);
|
||||
}
|
||||
|
||||
|
||||
@ -4,5 +4,6 @@ import baseConfigFn from '../../vite.base.config'
|
||||
export default defineConfig(async () => {
|
||||
return baseConfigFn({
|
||||
name: 'LowCodeRendererCore',
|
||||
defaultFormats: ['es', 'cjs']
|
||||
})
|
||||
});
|
||||
|
||||
7
packages/renderer-router/__tests__/matcher.spec.ts
Normal file
7
packages/renderer-router/__tests__/matcher.spec.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { createRouterMatcher } from '../src/matcher';
|
||||
|
||||
describe('RouterMatcher', () => {
|
||||
it('', () => {
|
||||
|
||||
});
|
||||
});
|
||||
@ -1,22 +1,24 @@
|
||||
{
|
||||
"name": "@alilc/lowcode-renderer-router",
|
||||
"version": "1.0.0-beta.0",
|
||||
"description": "",
|
||||
"version": "1.0.0-alpha.0",
|
||||
"description": "router for lowCode renderer",
|
||||
"type": "module",
|
||||
"bugs": "https://github.com/alibaba/lowcode-engine/issues",
|
||||
"homepage": "https://github.com/alibaba/lowcode-engine/#readme",
|
||||
"license": "MIT",
|
||||
"main": "dist/low-code-runtime-router.cjs",
|
||||
"module": "dist/low-code-runtime-router.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/low-code-runtime-router.js",
|
||||
"require": "./dist/low-code-runtime-router.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build:target": "vite build",
|
||||
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.mjs",
|
||||
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.js",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useEvent } from '@alilc/lowcode-renderer-core';
|
||||
import { createEvent } from '@alilc/lowcode-renderer-core';
|
||||
|
||||
export type HistoryState = History['state'];
|
||||
export type HistoryLocation = string;
|
||||
@ -166,8 +166,8 @@ export function createBrowserHistory(base?: string): RouterHistory {
|
||||
currentLocation = to;
|
||||
}
|
||||
|
||||
let listeners = useEvent<NavigationCallback>();
|
||||
let teardowns = useEvent<() => void>();
|
||||
const listeners = createEvent<NavigationCallback>();
|
||||
const teardowns = createEvent<() => void>();
|
||||
|
||||
let pauseState: HistoryLocation | null = null;
|
||||
|
||||
@ -285,7 +285,7 @@ function createCurrentLocation(base: string, location: Location) {
|
||||
// hash bases like #, /#, #/, #!, #!/, /#!/, or even /folder#end
|
||||
const hashPos = base.indexOf('#');
|
||||
if (hashPos > -1) {
|
||||
let slicePos = hash.includes(base.slice(hashPos)) ? base.slice(hashPos).length : 1;
|
||||
const slicePos = hash.includes(base.slice(hashPos)) ? base.slice(hashPos).length : 1;
|
||||
let pathFromHash = hash.slice(slicePos);
|
||||
// prepend the starting slash to hash so the url starts with /#
|
||||
if (pathFromHash[0] !== '/') pathFromHash = '/' + pathFromHash;
|
||||
@ -348,7 +348,7 @@ export function createMemoryHistory(base = ''): RouterHistory {
|
||||
historyStack.push({ location, state });
|
||||
}
|
||||
|
||||
const listeners = useEvent<NavigationCallback>();
|
||||
const listeners = createEvent<NavigationCallback>();
|
||||
|
||||
function triggerListeners(
|
||||
to: HistoryLocation,
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
// refer from https://github.com/vuejs/router/blob/main/packages/router/src/matcher/index.ts
|
||||
|
||||
import { type PlainObject, type RawLocation } from '@alilc/lowcode-renderer-core';
|
||||
import { pick } from 'lodash-es';
|
||||
import { createRouteRecordMatcher, type RouteRecordMatcher } from './utils/record-matcher';
|
||||
import { type PathParserOptions } from './utils/path-parser';
|
||||
import { type PathParserOptions, type PathParams, comparePathParserScore } from './utils/path-parser';
|
||||
|
||||
import type { RouteRecord, RouteParams, RouteLocationNormalized } from './types';
|
||||
import type { RouteRecord, RouteLocationNormalized } from './types';
|
||||
|
||||
export interface RouteRecordNormalized {
|
||||
/**
|
||||
@ -58,7 +60,9 @@ export interface RouterMatcher {
|
||||
* @param location - MatcherLocationRaw to resolve to a url
|
||||
* @param currentLocation - MatcherLocation of the current location
|
||||
*/
|
||||
resolve: (location: RawLocation, currentLocation: MatcherLocation) => MatcherLocation;
|
||||
resolve: (
|
||||
location: RawLocation, currentLocation: MatcherLocation
|
||||
) => MatcherLocation;
|
||||
}
|
||||
|
||||
export function createRouterMatcher(
|
||||
@ -83,7 +87,7 @@ export function createRouterMatcher(
|
||||
const parentPath = parent.record.path;
|
||||
const connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/';
|
||||
|
||||
normalizedRecord.path = parent.record.path + (path && connectingSlash + path);
|
||||
normalizedRecord.path = parent.record.path + (path ? connectingSlash + path : '');
|
||||
}
|
||||
|
||||
const matcher = createRouteRecordMatcher(normalizedRecord, parent, options);
|
||||
@ -96,7 +100,16 @@ export function createRouterMatcher(
|
||||
}
|
||||
|
||||
if (matcher.record.path) {
|
||||
matchers.push(matcher);
|
||||
let i = 0;
|
||||
while (
|
||||
i < matchers.length &&
|
||||
comparePathParserScore(matcher, matchers[i]) >= 0 &&
|
||||
(matcher.record.path !== matchers[i].record.path ||
|
||||
!isRecordChildOf(matcher, matchers[i]))
|
||||
) {
|
||||
i++;
|
||||
}
|
||||
matchers.splice(i, 0, matcher);
|
||||
|
||||
if (matcher.record.name) {
|
||||
matcherMap.set(matcher.record.name, matcher);
|
||||
@ -126,9 +139,12 @@ export function createRouterMatcher(
|
||||
return matcherMap.get(name);
|
||||
}
|
||||
|
||||
function resolve(location: RawLocation, currentLocation: MatcherLocation): MatcherLocation {
|
||||
function resolve(
|
||||
location: RawLocation,
|
||||
currentLocation: MatcherLocation
|
||||
): MatcherLocation {
|
||||
let matcher: RouteRecordMatcher | undefined;
|
||||
let params: RouteParams = {};
|
||||
let params: PathParams = {};
|
||||
let path: MatcherLocation['path'];
|
||||
let name: MatcherLocation['name'];
|
||||
|
||||
@ -136,7 +152,9 @@ export function createRouterMatcher(
|
||||
matcher = matcherMap.get(location.name);
|
||||
|
||||
if (!matcher) {
|
||||
throw new Error(`Router error: no match for ${JSON.stringify(location)}`);
|
||||
throw new Error(`
|
||||
Router error: no match for ${JSON.stringify(location)}
|
||||
`);
|
||||
}
|
||||
|
||||
name = matcher.record.name;
|
||||
@ -145,19 +163,20 @@ export function createRouterMatcher(
|
||||
paramsFromLocation(
|
||||
currentLocation.params ?? {},
|
||||
matcher.keys
|
||||
.filter((k) => {
|
||||
return !(k.modifier === '?' || k.modifier === '*');
|
||||
})
|
||||
.filter(k => !k.optional)
|
||||
.concat(
|
||||
matcher.parent ? matcher.parent.keys.filter(k => k.optional) : []
|
||||
)
|
||||
.map((k) => k.name),
|
||||
),
|
||||
location.params
|
||||
? paramsFromLocation(
|
||||
location.params,
|
||||
matcher.keys.map((k) => k.name),
|
||||
)
|
||||
location.params,
|
||||
matcher.keys.map((k) => k.name),
|
||||
)
|
||||
: {},
|
||||
);
|
||||
// throws if cannot be stringified
|
||||
|
||||
path = matcher.stringify(params);
|
||||
} else if ('path' in location) {
|
||||
path = location.path;
|
||||
@ -214,8 +233,8 @@ export function createRouterMatcher(
|
||||
};
|
||||
}
|
||||
|
||||
function paramsFromLocation(params: RouteParams, keys: (string | number)[]): RouteParams {
|
||||
const newParams = {} as RouteParams;
|
||||
function paramsFromLocation(params: PathParams, keys: (string | number)[]): PathParams {
|
||||
const newParams = {} as PathParams;
|
||||
for (const key of keys) {
|
||||
if (key in params) newParams[key] = params[key];
|
||||
}
|
||||
@ -233,3 +252,12 @@ export function normalizeRouteRecord(record: RouteRecord): RouteRecordNormalized
|
||||
children: record.children || [],
|
||||
};
|
||||
}
|
||||
|
||||
function isRecordChildOf(
|
||||
record: RouteRecordMatcher,
|
||||
parent: RouteRecordMatcher
|
||||
): boolean {
|
||||
return parent.children.some(
|
||||
child => child === record || isRecordChildOf(record, child)
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import {
|
||||
type RouterApi,
|
||||
type RouterConfig,
|
||||
type RouteLocation,
|
||||
useEvent,
|
||||
createEvent,
|
||||
type RawRouteLocation,
|
||||
type RawLocationOptions,
|
||||
} from '@alilc/lowcode-renderer-core';
|
||||
@ -14,10 +14,10 @@ import {
|
||||
type HistoryState,
|
||||
} from './history';
|
||||
import { createRouterMatcher } from './matcher';
|
||||
import { type PathParserOptions } from './utils/path-parser';
|
||||
import { type PathParserOptions, type PathParams } from './utils/path-parser';
|
||||
import { parseURL, stringifyURL } from './utils/url';
|
||||
import { isSameRouteLocation } from './utils/helper';
|
||||
import type { RouteParams, RouteRecord, RouteLocationNormalized } from './types';
|
||||
import type { RouteRecord, RouteLocationNormalized } from './types';
|
||||
import { type NavigationHookAfter, type NavigationGuard, guardToPromiseFn } from './guard';
|
||||
|
||||
export interface RouterOptions extends RouterConfig, PathParserOptions {
|
||||
@ -72,8 +72,8 @@ export function createRouter(options: RouterOptions = defaultRouterOptions): Rou
|
||||
? createMemoryHistory(baseName)
|
||||
: createBrowserHistory(baseName);
|
||||
|
||||
const beforeGuards = useEvent<NavigationGuard>();
|
||||
const afterGuards = useEvent<NavigationHookAfter>();
|
||||
const beforeGuards = createEvent<NavigationGuard>();
|
||||
const afterGuards = createEvent<NavigationHookAfter>();
|
||||
|
||||
let currentLocation: RouteLocationNormalized = START_LOCATION;
|
||||
let pendingLocation = currentLocation;
|
||||
@ -114,9 +114,9 @@ export function createRouter(options: RouterOptions = defaultRouterOptions): Rou
|
||||
|
||||
matcherLocation = {
|
||||
...rawLocation,
|
||||
params: rawLocation.params as RouteParams,
|
||||
params: rawLocation.params as PathParams,
|
||||
};
|
||||
currentLocation.params = currentLocation.params;
|
||||
currentLocation.params = matcherLocation.params;
|
||||
}
|
||||
|
||||
const matchedRoute = matcher.resolve(matcherLocation, currentLocation);
|
||||
@ -256,7 +256,7 @@ export function createRouter(options: RouterOptions = defaultRouterOptions): Rou
|
||||
to: RouteLocationNormalized,
|
||||
from: RouteLocationNormalized,
|
||||
): Promise<any> {
|
||||
let guards: ((...args: any[]) => Promise<any>)[] = [];
|
||||
const guards: ((...args: any[]) => Promise<any>)[] = [];
|
||||
|
||||
const canceledNavigationCheck = async (): Promise<any> => {
|
||||
if (pendingLocation !== to) {
|
||||
@ -265,19 +265,14 @@ export function createRouter(options: RouterOptions = defaultRouterOptions): Rou
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
try {
|
||||
guards = [];
|
||||
const beforeGuardsList = beforeGuards.list();
|
||||
const beforeGuardsList = beforeGuards.list();
|
||||
|
||||
for (const guard of beforeGuardsList) {
|
||||
guards.push(guardToPromiseFn(guard, to, from));
|
||||
}
|
||||
if (beforeGuardsList.length > 0) guards.push(canceledNavigationCheck);
|
||||
|
||||
return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve());
|
||||
} catch (err) {
|
||||
throw err;
|
||||
for (const guard of beforeGuardsList) {
|
||||
guards.push(guardToPromiseFn(guard, to, from));
|
||||
}
|
||||
if (beforeGuardsList.length > 0) guards.push(canceledNavigationCheck);
|
||||
|
||||
return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve());
|
||||
}
|
||||
|
||||
function finalizeNavigation(
|
||||
|
||||
@ -18,5 +18,3 @@ export interface RouteRecord extends RouterRecordSpec, PathParserOptions {
|
||||
export interface RouteLocationNormalized extends RouteLocation {
|
||||
matched: RouteRecord[];
|
||||
}
|
||||
|
||||
export type RouteParams = Record<string, string | string[]>;
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
import {
|
||||
pathToRegexp,
|
||||
match,
|
||||
compile,
|
||||
type Key,
|
||||
type TokensToRegexpOptions,
|
||||
} from 'path-to-regexp';
|
||||
import type { RouteParams } from '../types';
|
||||
|
||||
export interface PathParser {
|
||||
re: RegExp;
|
||||
/**
|
||||
* optional = token.modifier === "?" || token.modifier === "*";
|
||||
* repeat = token.modifier === "*" || token.modifier === "+";
|
||||
*/
|
||||
keys: Key[];
|
||||
/**
|
||||
* 解析路径中的参数
|
||||
*/
|
||||
parse: (path: string) => RouteParams | undefined;
|
||||
stringify: (params: RouteParams) => string;
|
||||
}
|
||||
|
||||
export type PathParserOptions = Pick<
|
||||
TokensToRegexpOptions,
|
||||
'end' | 'strict' | 'sensitive'
|
||||
>;
|
||||
|
||||
export function createPathParser(path: string, options: PathParserOptions) {
|
||||
if (!path.startsWith('/')) {
|
||||
throw new Error(
|
||||
`Route paths should start with a "/": "${path}" should be "/${path}".`
|
||||
);
|
||||
}
|
||||
|
||||
const keys: Key[] = [];
|
||||
const re = pathToRegexp(path, keys, options);
|
||||
const parse = match(path);
|
||||
const stringify = compile(path, { encode: encodeURIComponent });
|
||||
|
||||
return {
|
||||
re,
|
||||
keys,
|
||||
parse: (path: string) => {
|
||||
const parsed = parse(path);
|
||||
if (!parsed) return undefined;
|
||||
return parsed.params as RouteParams;
|
||||
},
|
||||
stringify: (params: RouteParams) => {
|
||||
return stringify(params);
|
||||
},
|
||||
};
|
||||
}
|
||||
9
packages/renderer-router/src/utils/path-parser/index.ts
Normal file
9
packages/renderer-router/src/utils/path-parser/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { type PathParserOptions, tokensToParser } from './parser-ranker';
|
||||
import { tokenizePath } from './path-tokenizer';
|
||||
|
||||
export function createPathParser(path: string, options?: PathParserOptions) {
|
||||
return tokensToParser(tokenizePath(path), options);
|
||||
}
|
||||
|
||||
export { comparePathParserScore } from './parser-ranker';
|
||||
export type { PathParser, PathParams, PathParserOptions } from './parser-ranker';
|
||||
370
packages/renderer-router/src/utils/path-parser/parser-ranker.ts
Normal file
370
packages/renderer-router/src/utils/path-parser/parser-ranker.ts
Normal file
@ -0,0 +1,370 @@
|
||||
// fork from https://github.com/vuejs/router/blob/main/packages/router/src/matcher/pathParserRanker.ts
|
||||
|
||||
import { Token, TokenType } from './path-tokenizer';
|
||||
|
||||
export type PathParams = Record<string, string | string[]>;
|
||||
|
||||
/**
|
||||
* A param in a url like `/users/:id`
|
||||
*/
|
||||
interface PathParserParamKey {
|
||||
name: string
|
||||
repeatable: boolean
|
||||
optional: boolean
|
||||
}
|
||||
|
||||
export interface PathParser {
|
||||
/**
|
||||
* The regexp used to match a url
|
||||
*/
|
||||
re: RegExp
|
||||
|
||||
/**
|
||||
* The score of the parser
|
||||
*/
|
||||
score: Array<number[]>
|
||||
|
||||
/**
|
||||
* Keys that appeared in the path
|
||||
*/
|
||||
keys: PathParserParamKey[]
|
||||
/**
|
||||
* Parses a url and returns the matched params or null if it doesn't match. An
|
||||
* optional param that isn't preset will be an empty string. A repeatable
|
||||
* param will be an array if there is at least one value.
|
||||
*
|
||||
* @param path - url to parse
|
||||
* @returns a Params object, empty if there are no params. `null` if there is
|
||||
* no match
|
||||
*/
|
||||
parse(path: string): PathParams | null
|
||||
|
||||
/**
|
||||
* Creates a string version of the url
|
||||
*
|
||||
* @param params - object of params
|
||||
* @returns a url
|
||||
*/
|
||||
stringify(params: PathParams): string
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface _PathParserOptions {
|
||||
/**
|
||||
* Makes the RegExp case-sensitive.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
sensitive?: boolean
|
||||
|
||||
/**
|
||||
* Whether to disallow a trailing slash or not.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
strict?: boolean
|
||||
|
||||
/**
|
||||
* Should the RegExp match from the beginning by prepending a `^` to it.
|
||||
* @internal
|
||||
*
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
start?: boolean
|
||||
|
||||
/**
|
||||
* Should the RegExp match until the end by appending a `$` to it.
|
||||
*
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
end?: boolean
|
||||
}
|
||||
|
||||
export type PathParserOptions = Pick<
|
||||
_PathParserOptions,
|
||||
'end' | 'sensitive' | 'strict'
|
||||
>;
|
||||
|
||||
// default pattern for a param: non-greedy everything but /
|
||||
const BASE_PARAM_PATTERN = '[^/]+?';
|
||||
|
||||
const BASE_PATH_PARSER_OPTIONS: Required<_PathParserOptions> = {
|
||||
sensitive: false,
|
||||
strict: false,
|
||||
start: true,
|
||||
end: true,
|
||||
};
|
||||
|
||||
// Scoring values used in tokensToParser
|
||||
const enum PathScore {
|
||||
_multiplier = 10,
|
||||
Root = 9 * _multiplier, // just /
|
||||
Segment = 4 * _multiplier, // /a-segment
|
||||
SubSegment = 3 * _multiplier, // /multiple-:things-in-one-:segment
|
||||
Static = 4 * _multiplier, // /static
|
||||
Dynamic = 2 * _multiplier, // /:someId
|
||||
BonusCustomRegExp = 1 * _multiplier, // /:someId(\\d+)
|
||||
BonusWildcard = -4 * _multiplier - BonusCustomRegExp, // /:namedWildcard(.*) we remove the bonus added by the custom regexp
|
||||
BonusRepeatable = -2 * _multiplier, // /:w+ or /:w*
|
||||
BonusOptional = -0.8 * _multiplier, // /:w? or /:w*
|
||||
// these two have to be under 0.1 so a strict /:page is still lower than /:a-:b
|
||||
BonusStrict = 0.07 * _multiplier, // when options strict: true is passed, as the regex omits \/?
|
||||
BonusCaseSensitive = 0.025 * _multiplier, // when options strict: true is passed, as the regex omits \/?
|
||||
}
|
||||
|
||||
// Special Regex characters that must be escaped in static tokens
|
||||
const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g;
|
||||
|
||||
/**
|
||||
* Creates a path parser from an array of Segments (a segment is an array of Tokens)
|
||||
*
|
||||
* @param segments - array of segments returned by tokenizePath
|
||||
* @param extraOptions - optional options for the regexp
|
||||
* @returns a PathParser
|
||||
*/
|
||||
export function tokensToParser(
|
||||
segments: Array<Token[]>,
|
||||
extraOptions?: _PathParserOptions
|
||||
): PathParser {
|
||||
const options = Object.assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions);
|
||||
|
||||
// the amount of scores is the same as the length of segments except for the root segment "/"
|
||||
const score: Array<number[]> = [];
|
||||
// the regexp as a string
|
||||
let pattern = options.start ? '^' : '';
|
||||
// extracted keys
|
||||
const keys: PathParserParamKey[] = [];
|
||||
|
||||
for (const segment of segments) {
|
||||
// the root segment needs special treatment
|
||||
const segmentScores: number[] = segment.length ? [] : [PathScore.Root];
|
||||
|
||||
// allow trailing slash
|
||||
if (options.strict && !segment.length) pattern += '/';
|
||||
for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {
|
||||
const token = segment[tokenIndex];
|
||||
// resets the score if we are inside a sub-segment /:a-other-:b
|
||||
let subSegmentScore: number =
|
||||
PathScore.Segment +
|
||||
(options.sensitive ? PathScore.BonusCaseSensitive : 0);
|
||||
|
||||
if (token.type === TokenType.Static) {
|
||||
// prepend the slash if we are starting a new segment
|
||||
if (!tokenIndex) pattern += '/';
|
||||
pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
|
||||
subSegmentScore += PathScore.Static;
|
||||
} else if (token.type === TokenType.Param) {
|
||||
const { value, repeatable, optional, regexp } = token;
|
||||
keys.push({
|
||||
name: value,
|
||||
repeatable,
|
||||
optional,
|
||||
});
|
||||
const re = regexp ? regexp : BASE_PARAM_PATTERN;
|
||||
// the user provided a custom regexp /:id(\\d+)
|
||||
if (re !== BASE_PARAM_PATTERN) {
|
||||
subSegmentScore += PathScore.BonusCustomRegExp;
|
||||
// make sure the regexp is valid before using it
|
||||
try {
|
||||
new RegExp(`(${re})`);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Invalid custom RegExp for param "${value}" (${re}): ` +
|
||||
(err as Error).message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// when we repeat we must take care of the repeating leading slash
|
||||
let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`;
|
||||
|
||||
// prepend the slash if we are starting a new segment
|
||||
if (!tokenIndex)
|
||||
subPattern =
|
||||
// avoid an optional / if there are more segments e.g. /:p?-static
|
||||
// or /:p?-:p2
|
||||
optional && segment.length < 2
|
||||
? `(?:/${subPattern})`
|
||||
: '/' + subPattern;
|
||||
if (optional) subPattern += '?';
|
||||
|
||||
pattern += subPattern;
|
||||
|
||||
subSegmentScore += PathScore.Dynamic;
|
||||
if (optional) subSegmentScore += PathScore.BonusOptional;
|
||||
if (repeatable) subSegmentScore += PathScore.BonusRepeatable;
|
||||
if (re === '.*') subSegmentScore += PathScore.BonusWildcard;
|
||||
}
|
||||
|
||||
segmentScores.push(subSegmentScore);
|
||||
}
|
||||
|
||||
// an empty array like /home/ -> [[{home}], []]
|
||||
// if (!segment.length) pattern += '/'
|
||||
|
||||
score.push(segmentScores);
|
||||
}
|
||||
|
||||
// only apply the strict bonus to the last score
|
||||
if (options.strict && options.end) {
|
||||
const i = score.length - 1;
|
||||
score[i][score[i].length - 1] += PathScore.BonusStrict;
|
||||
}
|
||||
|
||||
// TODO: dev only warn double trailing slash
|
||||
if (!options.strict) pattern += '/?';
|
||||
|
||||
if (options.end) pattern += '$';
|
||||
// allow paths like /dynamic to only match dynamic or dynamic/... but not dynamic_something_else
|
||||
else if (options.strict) pattern += '(?:/|$)';
|
||||
|
||||
const re = new RegExp(pattern, options.sensitive ? '' : 'i');
|
||||
|
||||
function parse(path: string): PathParams | null {
|
||||
const match = path.match(re);
|
||||
const params: PathParams = {};
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
for (let i = 1; i < match.length; i++) {
|
||||
const value: string = match[i] || '';
|
||||
const key = keys[i - 1];
|
||||
params[key.name] = value && key.repeatable ? value.split('/') : value;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function stringify(params: PathParams): string {
|
||||
let path = '';
|
||||
// for optional parameters to allow to be empty
|
||||
let avoidDuplicatedSlash: boolean = false;
|
||||
for (const segment of segments) {
|
||||
if (!avoidDuplicatedSlash || !path.endsWith('/')) path += '/';
|
||||
avoidDuplicatedSlash = false;
|
||||
|
||||
for (const token of segment) {
|
||||
if (token.type === TokenType.Static) {
|
||||
path += token.value;
|
||||
} else if (token.type === TokenType.Param) {
|
||||
const { value, repeatable, optional } = token;
|
||||
const param: string | readonly string[] =
|
||||
value in params ? params[value] : '';
|
||||
|
||||
if (Array.isArray(param) && !repeatable) {
|
||||
throw new Error(
|
||||
`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`
|
||||
);
|
||||
}
|
||||
|
||||
const text: string = Array.isArray(param)
|
||||
? (param as string[]).join('/')
|
||||
: (param as string);
|
||||
if (!text) {
|
||||
if (optional) {
|
||||
// if we have more than one optional param like /:a?-static we don't need to care about the optional param
|
||||
if (segment.length < 2) {
|
||||
// remove the last slash as we could be at the end
|
||||
if (path.endsWith('/')) path = path.slice(0, -1);
|
||||
// do not append a slash on the next iteration
|
||||
else avoidDuplicatedSlash = true;
|
||||
}
|
||||
} else throw new Error(`Missing required param "${value}"`);
|
||||
}
|
||||
path += text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// avoid empty path when we have multiple optional params
|
||||
return path || '/';
|
||||
}
|
||||
|
||||
return {
|
||||
re,
|
||||
score,
|
||||
keys,
|
||||
parse,
|
||||
stringify,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares an array of numbers as used in PathParser.score and returns a
|
||||
* number. This function can be used to `sort` an array
|
||||
*
|
||||
* @param a - first array of numbers
|
||||
* @param b - second array of numbers
|
||||
* @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
|
||||
* should be sorted first
|
||||
*/
|
||||
function compareScoreArray(a: number[], b: number[]): number {
|
||||
let i = 0;
|
||||
while (i < a.length && i < b.length) {
|
||||
const diff = b[i] - a[i];
|
||||
// only keep going if diff === 0
|
||||
if (diff) return diff;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// if the last subsegment was Static, the shorter segments should be sorted first
|
||||
// otherwise sort the longest segment first
|
||||
if (a.length < b.length) {
|
||||
return a.length === 1 && a[0] === PathScore.Static + PathScore.Segment
|
||||
? -1
|
||||
: 1;
|
||||
} else if (a.length > b.length) {
|
||||
return b.length === 1 && b[0] === PathScore.Static + PathScore.Segment
|
||||
? 1
|
||||
: -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare function that can be used with `sort` to sort an array of PathParser
|
||||
*
|
||||
* @param a - first PathParser
|
||||
* @param b - second PathParser
|
||||
* @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
|
||||
*/
|
||||
export function comparePathParserScore(a: PathParser, b: PathParser): number {
|
||||
let i = 0;
|
||||
const aScore = a.score;
|
||||
const bScore = b.score;
|
||||
while (i < aScore.length && i < bScore.length) {
|
||||
const comp = compareScoreArray(aScore[i], bScore[i]);
|
||||
// do not return if both are equal
|
||||
if (comp) return comp;
|
||||
|
||||
i++;
|
||||
}
|
||||
if (Math.abs(bScore.length - aScore.length) === 1) {
|
||||
if (isLastScoreNegative(aScore)) return 1;
|
||||
if (isLastScoreNegative(bScore)) return -1;
|
||||
}
|
||||
|
||||
// if a and b share the same score entries but b has more, sort b first
|
||||
return bScore.length - aScore.length;
|
||||
// this is the ternary version
|
||||
// return aScore.length < bScore.length
|
||||
// ? 1
|
||||
// : aScore.length > bScore.length
|
||||
// ? -1
|
||||
// : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* This allows detecting splats at the end of a path: /home/:id(.*)*
|
||||
*
|
||||
* @param score - score to check
|
||||
* @returns true if the last entry is negative
|
||||
*/
|
||||
function isLastScoreNegative(score: PathParser['score']): boolean {
|
||||
const last = score[score.length - 1];
|
||||
return score.length > 0 && last[last.length - 1] < 0;
|
||||
}
|
||||
200
packages/renderer-router/src/utils/path-parser/path-tokenizer.ts
Normal file
200
packages/renderer-router/src/utils/path-parser/path-tokenizer.ts
Normal file
@ -0,0 +1,200 @@
|
||||
// fork from https://github.com/vuejs/router/blob/main/packages/router/src/matcher/pathTokenizer.ts
|
||||
|
||||
export const enum TokenType {
|
||||
Static,
|
||||
Param,
|
||||
Group,
|
||||
}
|
||||
|
||||
const enum TokenizerState {
|
||||
Static,
|
||||
Param,
|
||||
ParamRegExp, // custom re for a param
|
||||
ParamRegExpEnd, // check if there is any ? + *
|
||||
EscapeNext,
|
||||
}
|
||||
|
||||
interface TokenStatic {
|
||||
type: TokenType.Static
|
||||
value: string
|
||||
}
|
||||
|
||||
interface TokenParam {
|
||||
type: TokenType.Param
|
||||
regexp?: string
|
||||
value: string
|
||||
optional: boolean
|
||||
repeatable: boolean
|
||||
}
|
||||
|
||||
interface TokenGroup {
|
||||
type: TokenType.Group
|
||||
value: Exclude<Token, TokenGroup>[]
|
||||
}
|
||||
|
||||
export type Token = TokenStatic | TokenParam | TokenGroup;
|
||||
|
||||
const ROOT_TOKEN: Token = {
|
||||
type: TokenType.Static,
|
||||
value: '',
|
||||
};
|
||||
|
||||
const VALID_PARAM_RE = /[a-zA-Z0-9_]/;
|
||||
// After some profiling, the cache seems to be unnecessary because tokenizePath
|
||||
// (the slowest part of adding a route) is very fast
|
||||
|
||||
// const tokenCache = new Map<string, Token[][]>()
|
||||
|
||||
export function tokenizePath(path: string): Array<Token[]> {
|
||||
if (!path) return [[]];
|
||||
if (path === '/') return [[ROOT_TOKEN]];
|
||||
if (!path.startsWith('/')) {
|
||||
throw new Error(
|
||||
`Route paths should start with a "/": "${path}" should be "/${path}".`
|
||||
);
|
||||
}
|
||||
|
||||
// if (tokenCache.has(path)) return tokenCache.get(path)!
|
||||
|
||||
function crash(message: string) {
|
||||
throw new Error(`ERR (${state})/"${buffer}": ${message}`);
|
||||
}
|
||||
|
||||
let state: TokenizerState = TokenizerState.Static;
|
||||
let previousState: TokenizerState = state;
|
||||
const tokens: Array<Token[]> = [];
|
||||
// the segment will always be valid because we get into the initial state
|
||||
// with the leading /
|
||||
let segment!: Token[];
|
||||
|
||||
function finalizeSegment() {
|
||||
if (segment) tokens.push(segment);
|
||||
segment = [];
|
||||
}
|
||||
|
||||
// index on the path
|
||||
let i = 0;
|
||||
// char at index
|
||||
let char: string;
|
||||
// buffer of the value read
|
||||
let buffer: string = '';
|
||||
// custom regexp for a param
|
||||
let customRe: string = '';
|
||||
|
||||
function consumeBuffer() {
|
||||
if (!buffer) return;
|
||||
|
||||
if (state === TokenizerState.Static) {
|
||||
segment.push({
|
||||
type: TokenType.Static,
|
||||
value: buffer,
|
||||
});
|
||||
} else if (
|
||||
state === TokenizerState.Param ||
|
||||
state === TokenizerState.ParamRegExp ||
|
||||
state === TokenizerState.ParamRegExpEnd
|
||||
) {
|
||||
if (segment.length > 1 && (char === '*' || char === '+'))
|
||||
crash(
|
||||
`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`
|
||||
);
|
||||
segment.push({
|
||||
type: TokenType.Param,
|
||||
value: buffer,
|
||||
regexp: customRe,
|
||||
repeatable: char === '*' || char === '+',
|
||||
optional: char === '*' || char === '?',
|
||||
});
|
||||
} else {
|
||||
crash('Invalid state to consume buffer');
|
||||
}
|
||||
buffer = '';
|
||||
}
|
||||
|
||||
function addCharToBuffer() {
|
||||
buffer += char;
|
||||
}
|
||||
|
||||
while (i < path.length) {
|
||||
char = path[i++];
|
||||
|
||||
if (char === '\\' && state !== TokenizerState.ParamRegExp) {
|
||||
previousState = state;
|
||||
state = TokenizerState.EscapeNext;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case TokenizerState.Static:
|
||||
if (char === '/') {
|
||||
if (buffer) {
|
||||
consumeBuffer();
|
||||
}
|
||||
finalizeSegment();
|
||||
} else if (char === ':') {
|
||||
consumeBuffer();
|
||||
state = TokenizerState.Param;
|
||||
} else {
|
||||
addCharToBuffer();
|
||||
}
|
||||
break;
|
||||
|
||||
case TokenizerState.EscapeNext:
|
||||
addCharToBuffer();
|
||||
state = previousState;
|
||||
break;
|
||||
|
||||
case TokenizerState.Param:
|
||||
if (char === '(') {
|
||||
state = TokenizerState.ParamRegExp;
|
||||
} else if (VALID_PARAM_RE.test(char)) {
|
||||
addCharToBuffer();
|
||||
} else {
|
||||
consumeBuffer();
|
||||
state = TokenizerState.Static;
|
||||
// go back one character if we were not modifying
|
||||
if (char !== '*' && char !== '?' && char !== '+') i--;
|
||||
}
|
||||
break;
|
||||
|
||||
case TokenizerState.ParamRegExp:
|
||||
// TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix)
|
||||
// it already works by escaping the closing )
|
||||
// https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB#
|
||||
// is this really something people need since you can also write
|
||||
// /prefix_:p()_suffix
|
||||
if (char === ')') {
|
||||
// handle the escaped )
|
||||
if (customRe[customRe.length - 1] == '\\')
|
||||
customRe = customRe.slice(0, -1) + char;
|
||||
else state = TokenizerState.ParamRegExpEnd;
|
||||
} else {
|
||||
customRe += char;
|
||||
}
|
||||
break;
|
||||
|
||||
case TokenizerState.ParamRegExpEnd:
|
||||
// same as finalizing a param
|
||||
consumeBuffer();
|
||||
state = TokenizerState.Static;
|
||||
// go back one character if we were not modifying
|
||||
if (char !== '*' && char !== '?' && char !== '+') i--;
|
||||
customRe = '';
|
||||
break;
|
||||
|
||||
default:
|
||||
crash('Unknown state');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (state === TokenizerState.ParamRegExp)
|
||||
crash(`Unfinished custom RegExp for param "${buffer}"`);
|
||||
|
||||
consumeBuffer();
|
||||
finalizeSegment();
|
||||
|
||||
// tokenCache.set(path, tokens)
|
||||
|
||||
return tokens;
|
||||
}
|
||||
@ -4,5 +4,6 @@ import baseConfigFn from '../../vite.base.config'
|
||||
export default defineConfig(async () => {
|
||||
return baseConfigFn({
|
||||
name: 'LowCodeRuntimeRouter',
|
||||
defaultFormats: ['es', 'cjs']
|
||||
})
|
||||
});
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import { defineProject } from 'vitest/config'
|
||||
|
||||
export default defineProject({
|
||||
test: {}
|
||||
})
|
||||
@ -30,9 +30,9 @@ export interface IPublicApiCommonUtils {
|
||||
* @returns {(IPublicTypeNodeSchema | undefined)}
|
||||
*/
|
||||
getNodeSchemaById(
|
||||
schema: IPublicTypeNodeSchema,
|
||||
nodeId: string,
|
||||
): IPublicTypeNodeSchema | undefined;
|
||||
schema: IPublicTypeNodeSchema,
|
||||
nodeId: string,
|
||||
): IPublicTypeNodeSchema | undefined;
|
||||
|
||||
// TODO: add comments
|
||||
getConvertedExtraKey(key: string): string;
|
||||
|
||||
@ -18,10 +18,10 @@ export interface IPublicApiHotkey {
|
||||
* @param action
|
||||
*/
|
||||
bind(
|
||||
combos: string[] | string,
|
||||
callback: IPublicTypeHotkeyCallback,
|
||||
action?: string,
|
||||
): IPublicTypeDisposable;
|
||||
combos: string[] | string,
|
||||
callback: IPublicTypeHotkeyCallback,
|
||||
action?: string,
|
||||
): IPublicTypeDisposable;
|
||||
|
||||
/**
|
||||
* 给指定窗口绑定快捷键
|
||||
|
||||
@ -112,9 +112,9 @@ export interface IPublicApiMaterial {
|
||||
* @param handle
|
||||
*/
|
||||
modifyBuiltinComponentAction(
|
||||
actionName: string,
|
||||
handle: (action: IPublicTypeComponentAction) => void,
|
||||
): void;
|
||||
actionName: string,
|
||||
handle: (action: IPublicTypeComponentAction) => void,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 监听 assets 变化的事件
|
||||
|
||||
@ -30,8 +30,8 @@ export interface IPublicApiPlugins {
|
||||
* use this to get preference config for this plugin when engine.init() called
|
||||
*/
|
||||
getPluginPreference(
|
||||
pluginName: string,
|
||||
): Record<string, IPublicTypePreferenceValueType> | null | undefined;
|
||||
pluginName: string,
|
||||
): Record<string, IPublicTypePreferenceValueType> | null | undefined;
|
||||
|
||||
/**
|
||||
* 获取指定插件
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user