mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 03:12:15 +00:00
perf(editor): Virtualize SchemaView (#11694)
This commit is contained in:
committed by
GitHub
parent
3a5bd12945
commit
9c6def9197
@@ -1,15 +1,18 @@
|
||||
import { ref } from 'vue';
|
||||
import type { Optional, Primitives, Schema, INodeUi } from '@/Interface';
|
||||
import {
|
||||
type ITaskDataConnections,
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeTypeDescription,
|
||||
NodeConnectionType,
|
||||
} from 'n8n-workflow';
|
||||
import { merge } from 'lodash-es';
|
||||
import { generatePath } from '@/utils/mappingUtils';
|
||||
import { generatePath, getMappedExpression } from '@/utils/mappingUtils';
|
||||
import { isObj } from '@/utils/typeGuards';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { isPresent } from '@/utils/typesUtils';
|
||||
import { isPresent, shorten } from '@/utils/typesUtils';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
|
||||
export function useDataSchema() {
|
||||
function getSchema(
|
||||
@@ -166,3 +169,208 @@ export function useDataSchema() {
|
||||
filterSchema,
|
||||
};
|
||||
}
|
||||
|
||||
export type SchemaNode = {
|
||||
node: INodeUi;
|
||||
nodeType: INodeTypeDescription;
|
||||
depth: number;
|
||||
connectedOutputIndexes: number[];
|
||||
itemsCount: number;
|
||||
schema: Schema;
|
||||
};
|
||||
|
||||
export type RenderItem = {
|
||||
title?: string;
|
||||
path?: string;
|
||||
level?: number;
|
||||
depth?: number;
|
||||
expression?: string;
|
||||
value?: string;
|
||||
id: string;
|
||||
icon: string;
|
||||
collapsable?: boolean;
|
||||
nodeType?: INodeUi['type'];
|
||||
type: 'item';
|
||||
};
|
||||
|
||||
export type RenderHeader = {
|
||||
id: string;
|
||||
title: string;
|
||||
info?: string;
|
||||
collapsable: boolean;
|
||||
nodeType: INodeTypeDescription;
|
||||
itemCount: number | null;
|
||||
type: 'header';
|
||||
};
|
||||
|
||||
type Renders = RenderHeader | RenderItem;
|
||||
|
||||
const icons = {
|
||||
object: 'cube',
|
||||
array: 'list',
|
||||
['string']: 'font',
|
||||
null: 'font',
|
||||
['number']: 'hashtag',
|
||||
['boolean']: 'check-square',
|
||||
function: 'code',
|
||||
bigint: 'calculator',
|
||||
symbol: 'sun',
|
||||
['undefined']: 'ban',
|
||||
} as const;
|
||||
|
||||
const getIconBySchemaType = (type: Schema['type']): string => icons[type];
|
||||
|
||||
const emptyItem = (): RenderItem => ({
|
||||
id: `empty-${window.crypto.randomUUID()}`,
|
||||
icon: '',
|
||||
value: useI18n().baseText('dataMapping.schemaView.emptyData'),
|
||||
type: 'item',
|
||||
});
|
||||
|
||||
const isDataEmpty = (schema: Schema) => {
|
||||
// Utilize the generated schema instead of looping over the entire data again
|
||||
// The schema for empty data is { type: 'object', value: [] }
|
||||
const isObjectOrArray = schema.type === 'object';
|
||||
const isEmpty = Array.isArray(schema.value) && schema.value.length === 0;
|
||||
|
||||
return isObjectOrArray && isEmpty;
|
||||
};
|
||||
|
||||
const prefixTitle = (title: string, prefix?: string) => (prefix ? `${prefix}[${title}]` : title);
|
||||
|
||||
export const useFlattenSchema = () => {
|
||||
const closedNodes = ref<Set<string>>(new Set());
|
||||
const headerIds = ref<Set<string>>(new Set());
|
||||
const toggleLeaf = (id: string) => {
|
||||
if (closedNodes.value.has(id)) {
|
||||
closedNodes.value.delete(id);
|
||||
} else {
|
||||
closedNodes.value.add(id);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleNode = (id: string) => {
|
||||
if (closedNodes.value.has(id)) {
|
||||
closedNodes.value = new Set(headerIds.value);
|
||||
closedNodes.value.delete(id);
|
||||
} else {
|
||||
closedNodes.value.add(id);
|
||||
}
|
||||
};
|
||||
|
||||
const flattenSchema = ({
|
||||
schema,
|
||||
node = { name: '', type: '' },
|
||||
depth = 0,
|
||||
prefix = '',
|
||||
level = 0,
|
||||
}: {
|
||||
schema: Schema;
|
||||
node?: { name: string; type: string };
|
||||
depth?: number;
|
||||
prefix?: string;
|
||||
level?: number;
|
||||
}): RenderItem[] => {
|
||||
// only show empty item for the first level
|
||||
if (isDataEmpty(schema) && depth <= 0) {
|
||||
return [emptyItem()];
|
||||
}
|
||||
|
||||
const expression = getMappedExpression({
|
||||
nodeName: node.name,
|
||||
distanceFromActive: depth,
|
||||
path: schema.path,
|
||||
});
|
||||
|
||||
if (Array.isArray(schema.value)) {
|
||||
const items: RenderItem[] = [];
|
||||
|
||||
if (schema.key) {
|
||||
items.push({
|
||||
title: prefixTitle(schema.key, prefix),
|
||||
path: schema.path,
|
||||
expression,
|
||||
depth,
|
||||
level,
|
||||
icon: getIconBySchemaType(schema.type),
|
||||
id: expression,
|
||||
collapsable: true,
|
||||
nodeType: node.type,
|
||||
type: 'item',
|
||||
});
|
||||
}
|
||||
|
||||
if (closedNodes.value.has(expression)) {
|
||||
return items;
|
||||
}
|
||||
|
||||
return items.concat(
|
||||
schema.value
|
||||
.map((item) => {
|
||||
const itemPrefix = schema.type === 'array' ? schema.key : '';
|
||||
return flattenSchema({
|
||||
schema: item,
|
||||
node,
|
||||
depth,
|
||||
prefix: itemPrefix,
|
||||
level: level + 1,
|
||||
});
|
||||
})
|
||||
.flat(),
|
||||
);
|
||||
} else if (schema.key) {
|
||||
return [
|
||||
{
|
||||
title: prefixTitle(schema.key, prefix),
|
||||
path: schema.path,
|
||||
expression,
|
||||
level,
|
||||
depth,
|
||||
value: shorten(schema.value, 600, 0),
|
||||
id: expression,
|
||||
icon: getIconBySchemaType(schema.type),
|
||||
collapsable: false,
|
||||
nodeType: node.type,
|
||||
type: 'item',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const flattenMultipleSchemas = (
|
||||
nodes: SchemaNode[],
|
||||
additionalInfo: (node: INodeUi) => string,
|
||||
) => {
|
||||
headerIds.value.clear();
|
||||
|
||||
return nodes.reduce<Renders[]>((acc, item) => {
|
||||
acc.push({
|
||||
title: item.node.name,
|
||||
id: item.node.name,
|
||||
collapsable: true,
|
||||
nodeType: item.nodeType,
|
||||
itemCount: item.itemsCount,
|
||||
info: additionalInfo(item.node),
|
||||
type: 'header',
|
||||
});
|
||||
|
||||
headerIds.value.add(item.node.name);
|
||||
|
||||
if (closedNodes.value.has(item.node.name)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (isDataEmpty(item.schema)) {
|
||||
acc.push(emptyItem());
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.push(...flattenSchema(item));
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
|
||||
return { closedNodes, toggleLeaf, toggleNode, flattenSchema, flattenMultipleSchemas };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user