mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 21:07:28 -08:00
Schema view variables initial implementation
This commit is contained in:
parent
a91abeeff5
commit
c40fc8bcb7
|
@ -1179,7 +1179,8 @@ export type SchemaType =
|
||||||
| 'object'
|
| 'object'
|
||||||
| 'function'
|
| 'function'
|
||||||
| 'null'
|
| 'null'
|
||||||
| 'undefined';
|
| 'undefined'
|
||||||
|
| 'notice';
|
||||||
|
|
||||||
export interface ILdapSyncData {
|
export interface ILdapSyncData {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -2,11 +2,13 @@ import { isObject } from 'lodash-es';
|
||||||
import type { AssignmentValue, IDataObject } from 'n8n-workflow';
|
import type { AssignmentValue, IDataObject } from 'n8n-workflow';
|
||||||
import { resolveParameter } from '@/composables/useWorkflowHelpers';
|
import { resolveParameter } from '@/composables/useWorkflowHelpers';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
export function inferAssignmentType(value: unknown): string {
|
export function inferAssignmentType(value: unknown): string {
|
||||||
if (typeof value === 'boolean') return 'boolean';
|
if (typeof value === 'boolean') return 'boolean';
|
||||||
if (typeof value === 'number') return 'number';
|
if (typeof value === 'number') return 'number';
|
||||||
if (typeof value === 'string') return 'string';
|
if (typeof value === 'string' || DateTime.isDateTime(value) || value instanceof Date)
|
||||||
|
return 'string';
|
||||||
if (Array.isArray(value)) return 'array';
|
if (Array.isArray(value)) return 'array';
|
||||||
if (isObject(value)) return 'object';
|
if (isObject(value)) return 'object';
|
||||||
return 'string';
|
return 'string';
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, watch } from 'vue';
|
|
||||||
import { snakeCase } from 'lodash-es';
|
|
||||||
import type { INodeUi, Schema } from '@/Interface';
|
import type { INodeUi, Schema } from '@/Interface';
|
||||||
import RunDataSchemaItem from '@/components/RunDataSchemaItem.vue';
|
import RunDataSchemaItem from '@/components/RunDataSchemaItem.vue';
|
||||||
import NodeIcon from '@/components/NodeIcon.vue';
|
import { useDataSchema } from '@/composables/useDataSchema';
|
||||||
import Draggable from '@/components/Draggable.vue';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
import { resolveParameter } from '@/composables/useWorkflowHelpers';
|
||||||
|
import { i18n } from '@/plugins/i18n';
|
||||||
|
import useEnvironmentsStore from '@/stores/environments.ee.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { telemetry } from '@/plugins/telemetry';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
import { escapeMappingString, generatePath } from '@/utils/mappingUtils';
|
||||||
|
import { executionDataToJson } from '@/utils/nodeTypesUtils';
|
||||||
import {
|
import {
|
||||||
|
type ITelemetryTrackProperties,
|
||||||
NodeConnectionType,
|
NodeConnectionType,
|
||||||
type IConnectedNode,
|
type IConnectedNode,
|
||||||
type IDataObject,
|
type IDataObject,
|
||||||
type INodeTypeDescription,
|
type INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { i18n } from '@/plugins/i18n';
|
|
||||||
import MappingPill from './MappingPill.vue';
|
|
||||||
import { useDataSchema } from '@/composables/useDataSchema';
|
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
|
||||||
import { executionDataToJson } from '@/utils/nodeTypesUtils';
|
|
||||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
|
||||||
import { useDebounce } from '@/composables/useDebounce';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
nodes?: IConnectedNode[];
|
nodes?: IConnectedNode[];
|
||||||
|
@ -39,6 +39,8 @@ type Props = {
|
||||||
|
|
||||||
type SchemaNode = {
|
type SchemaNode = {
|
||||||
node: INodeUi;
|
node: INodeUi;
|
||||||
|
subtitle: string;
|
||||||
|
baseExpression: string;
|
||||||
nodeType: INodeTypeDescription;
|
nodeType: INodeTypeDescription;
|
||||||
depth: number;
|
depth: number;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
@ -62,7 +64,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
context: 'ndv',
|
context: 'ndv',
|
||||||
});
|
});
|
||||||
|
|
||||||
const draggingPath = ref<string>('');
|
|
||||||
const nodesOpen = ref<Partial<Record<string, boolean>>>({});
|
const nodesOpen = ref<Partial<Record<string, boolean>>>({});
|
||||||
const nodesData = ref<Partial<Record<string, { schema: Schema; itemsCount: number }>>>({});
|
const nodesData = ref<Partial<Record<string, { schema: Schema; itemsCount: number }>>>({});
|
||||||
const nodesLoading = ref<Partial<Record<string, boolean>>>({});
|
const nodesLoading = ref<Partial<Record<string, boolean>>>({});
|
||||||
|
@ -71,9 +72,12 @@ const disableScrollInView = ref(false);
|
||||||
const ndvStore = useNDVStore();
|
const ndvStore = useNDVStore();
|
||||||
const nodeTypesStore = useNodeTypesStore();
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
const { getSchemaForExecutionData, filterSchema } = useDataSchema();
|
const settingsStore = useSettingsStore();
|
||||||
|
const environmentsStore = useEnvironmentsStore();
|
||||||
|
|
||||||
|
const { getSchemaForExecutionData, getSchema, filterSchema, isSchemaEmpty } = useDataSchema();
|
||||||
const { getNodeInputData } = useNodeHelpers();
|
const { getNodeInputData } = useNodeHelpers();
|
||||||
const { debounce } = useDebounce();
|
const telemetry = useTelemetry();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'clear:search': [];
|
'clear:search': [];
|
||||||
|
@ -97,6 +101,11 @@ const nodes = computed(() => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node: fullNode,
|
node: fullNode,
|
||||||
|
subtitle: nodeAdditionalInfo(fullNode),
|
||||||
|
baseExpression:
|
||||||
|
node.depth === 1
|
||||||
|
? '$json'
|
||||||
|
: generatePath(`$('${escapeMappingString(node.name)}')`, ['item', 'json']),
|
||||||
connectedOutputIndexes: node.indicies.length > 0 ? node.indicies : [0],
|
connectedOutputIndexes: node.indicies.length > 0 ? node.indicies : [0],
|
||||||
depth: node.depth,
|
depth: node.depth,
|
||||||
itemsCount,
|
itemsCount,
|
||||||
|
@ -110,7 +119,7 @@ const nodes = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredNodes = computed(() =>
|
const filteredNodes = computed(() =>
|
||||||
nodes.value.filter((node) => !props.search || !isDataEmpty(node.schema)),
|
nodes.value.filter((node) => !props.search || !isSchemaEmpty(node.schema)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const nodeAdditionalInfo = (node: INodeUi) => {
|
const nodeAdditionalInfo = (node: INodeUi) => {
|
||||||
|
@ -131,19 +140,20 @@ const nodeAdditionalInfo = (node: INodeUi) => {
|
||||||
return returnData.length ? `(${returnData.join(' | ')})` : '';
|
return returnData.length ? `(${returnData.join(' | ')})` : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDataEmpty = (schema: Schema | null) => {
|
|
||||||
if (!schema) return true;
|
|
||||||
// Utilize the generated schema instead of looping over the entire data again
|
|
||||||
// The schema for empty data is { type: 'object' | 'array', value: [] }
|
|
||||||
const isObjectOrArray = schema.type === 'object' || schema.type === 'array';
|
|
||||||
const isEmpty = Array.isArray(schema.value) && schema.value.length === 0;
|
|
||||||
|
|
||||||
return isObjectOrArray && isEmpty;
|
|
||||||
};
|
|
||||||
|
|
||||||
const highlight = computed(() => ndvStore.highlightDraggables);
|
const highlight = computed(() => ndvStore.highlightDraggables);
|
||||||
const allNodesOpen = computed(() => nodes.value.every((node) => node.open));
|
const allNodesOpen = computed(() => nodes.value.every((node) => node.open));
|
||||||
const noNodesOpen = computed(() => nodes.value.every((node) => !node.open));
|
const noNodesOpen = computed(() => nodes.value.every((node) => !node.open));
|
||||||
|
const variablesSchema = computed<Schema>(() => {
|
||||||
|
const schema = getSchema({
|
||||||
|
$now: resolveParameter('={{$now.toISO()}}'),
|
||||||
|
$today: resolveParameter('={{$today.toISO()}}'),
|
||||||
|
$vars: environmentsStore.variablesAsObject,
|
||||||
|
$execution: resolveParameter('={{$execution}}'),
|
||||||
|
$workflow: resolveParameter('={{$workflow}}'),
|
||||||
|
});
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
});
|
||||||
|
|
||||||
const loadNodeData = async ({ node, connectedOutputIndexes }: SchemaNode) => {
|
const loadNodeData = async ({ node, connectedOutputIndexes }: SchemaNode) => {
|
||||||
const pinData = workflowsStore.pinDataByNodeName(node.name);
|
const pinData = workflowsStore.pinDataByNodeName(node.name);
|
||||||
|
@ -190,23 +200,15 @@ const openAllNodes = async () => {
|
||||||
nodesOpen.value = Object.fromEntries(nodes.value.map(({ node }) => [node.name, true]));
|
nodesOpen.value = Object.fromEntries(nodes.value.map(({ node }) => [node.name, true]));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragStart = (el: HTMLElement) => {
|
const onDragStart = () => {
|
||||||
if (el?.dataset?.path) {
|
|
||||||
draggingPath.value = el.dataset.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
ndvStore.resetMappingTelemetry();
|
ndvStore.resetMappingTelemetry();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragEnd = (el: HTMLElement, node: INodeUi, depth: number) => {
|
const onDragEnd = (el: HTMLElement, node?: SchemaNode) => {
|
||||||
draggingPath.value = '';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const mappingTelemetry = ndvStore.mappingTelemetry;
|
const mappingTelemetry = ndvStore.mappingTelemetry;
|
||||||
const telemetryPayload = {
|
const telemetryPayload: ITelemetryTrackProperties = {
|
||||||
src_node_type: node.type,
|
|
||||||
src_field_name: el.dataset.name ?? '',
|
src_field_name: el.dataset.name ?? '',
|
||||||
src_nodes_back: depth,
|
|
||||||
src_run_index: props.runIndex,
|
src_run_index: props.runIndex,
|
||||||
src_runs_total: props.totalRuns,
|
src_runs_total: props.totalRuns,
|
||||||
src_field_nest_level: el.dataset.depth ?? 0,
|
src_field_nest_level: el.dataset.depth ?? 0,
|
||||||
|
@ -216,29 +218,17 @@ const onDragEnd = (el: HTMLElement, node: INodeUi, depth: number) => {
|
||||||
...mappingTelemetry,
|
...mappingTelemetry,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
telemetryPayload.src_node_type = node.node.type;
|
||||||
|
telemetryPayload.src_nodes_back = node.depth;
|
||||||
|
}
|
||||||
|
|
||||||
void useExternalHooks().run('runDataJson.onDragEnd', telemetryPayload);
|
void useExternalHooks().run('runDataJson.onDragEnd', telemetryPayload);
|
||||||
|
|
||||||
telemetry.track('User dragged data for mapping', telemetryPayload, { withPostHog: true });
|
telemetry.track('User dragged data for mapping', telemetryPayload, { withPostHog: true });
|
||||||
}, 1000); // ensure dest data gets set if drop
|
}, 1000); // ensure dest data gets set if drop
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTransitionStart = debounce(
|
|
||||||
(event: TransitionEvent, nodeName: string) => {
|
|
||||||
if (
|
|
||||||
nodesOpen.value[nodeName] &&
|
|
||||||
event.target instanceof HTMLElement &&
|
|
||||||
!disableScrollInView.value
|
|
||||||
) {
|
|
||||||
event.target.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'nearest',
|
|
||||||
inline: 'nearest',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ debounceTime: 100, trailing: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.nodes,
|
() => props.nodes,
|
||||||
() => {
|
() => {
|
||||||
|
@ -285,120 +275,46 @@ watch(
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<RunDataSchemaNode
|
||||||
v-for="currentNode in filteredNodes"
|
v-for="currentNode in filteredNodes"
|
||||||
:key="currentNode.node.id"
|
:key="currentNode.node.id"
|
||||||
data-test-id="run-data-schema-node"
|
|
||||||
:class="[$style.node, { [$style.open]: currentNode.open }]"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
$style.header,
|
|
||||||
{
|
|
||||||
[$style.trigger]: currentNode.nodeType.group.includes('trigger'),
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
data-test-id="run-data-schema-node-header"
|
|
||||||
>
|
|
||||||
<div :class="$style.expand" @click="toggleOpenNode(currentNode)">
|
|
||||||
<font-awesome-icon icon="angle-right" :class="$style.expandIcon" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
:class="$style.titleContainer"
|
|
||||||
data-test-id="run-data-schema-node-name"
|
|
||||||
@click="toggleOpenNode(currentNode, true)"
|
|
||||||
>
|
|
||||||
<div :class="$style.nodeIcon">
|
|
||||||
<NodeIcon :node-type="currentNode.nodeType" :size="12" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :class="$style.title">
|
|
||||||
{{ currentNode.node.name }}
|
|
||||||
<span v-if="nodeAdditionalInfo(currentNode.node)" :class="$style.subtitle">{{
|
|
||||||
nodeAdditionalInfo(currentNode.node)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<font-awesome-icon
|
|
||||||
v-if="currentNode.nodeType.group.includes('trigger')"
|
|
||||||
:class="$style.triggerIcon"
|
|
||||||
icon="bolt"
|
|
||||||
size="xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Transition name="items">
|
|
||||||
<div
|
|
||||||
v-if="currentNode.itemsCount && currentNode.open"
|
|
||||||
:class="$style.items"
|
|
||||||
data-test-id="run-data-schema-node-item-count"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
i18n.baseText('ndv.output.items', {
|
|
||||||
interpolate: { count: currentNode.itemsCount },
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Draggable
|
|
||||||
type="mapping"
|
|
||||||
target-data-key="mappable"
|
|
||||||
:disabled="!mappingEnabled"
|
|
||||||
@dragstart="onDragStart"
|
|
||||||
@dragend="(el: HTMLElement) => onDragEnd(el, currentNode.node, currentNode.depth)"
|
|
||||||
>
|
|
||||||
<template #preview="{ canDrop, el }">
|
|
||||||
<MappingPill v-if="el" :html="el.outerHTML" :can-drop="canDrop" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<Transition name="schema">
|
|
||||||
<div
|
|
||||||
v-if="currentNode.schema || search"
|
|
||||||
:class="[$style.schema, $style.animated]"
|
|
||||||
data-test-id="run-data-schema-node-schema"
|
|
||||||
@transitionstart="(event) => onTransitionStart(event, currentNode.node.name)"
|
|
||||||
>
|
|
||||||
<div :class="$style.innerSchema" @transitionstart.stop>
|
|
||||||
<div
|
|
||||||
v-if="currentNode.node.disabled"
|
|
||||||
:class="$style.notice"
|
|
||||||
data-test-id="run-data-schema-disabled"
|
|
||||||
>
|
|
||||||
{{ i18n.baseText('dataMapping.schemaView.disabled') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-else-if="isDataEmpty(currentNode.schema)"
|
|
||||||
:class="$style.notice"
|
|
||||||
data-test-id="run-data-schema-empty"
|
|
||||||
>
|
|
||||||
{{ i18n.baseText('dataMapping.schemaView.emptyData') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<RunDataSchemaItem
|
|
||||||
v-else-if="currentNode.schema"
|
|
||||||
:schema="currentNode.schema"
|
:schema="currentNode.schema"
|
||||||
:level="0"
|
:title="currentNode.node.name"
|
||||||
:parent="null"
|
:subtitle="currentNode.subtitle"
|
||||||
:pane-type="paneType"
|
:items-count="currentNode.itemsCount"
|
||||||
:sub-key="`${props.context}_${snakeCase(currentNode.node.name)}`"
|
:base-expression="currentNode.baseExpression"
|
||||||
:mapping-enabled="mappingEnabled"
|
:mapping-enabled="mappingEnabled"
|
||||||
:dragging-path="draggingPath"
|
:open="currentNode.open"
|
||||||
:distance-from-active="currentNode.depth"
|
:context="context"
|
||||||
:node="currentNode.node"
|
|
||||||
:search="search"
|
:search="search"
|
||||||
/>
|
:disabled="currentNode.node.disabled"
|
||||||
</div>
|
:is-trigger="currentNode.nodeType.group.includes('trigger')"
|
||||||
</div>
|
:disable-scroll-in-view="disableScrollInView"
|
||||||
</Transition>
|
@drag-start="onDragStart"
|
||||||
</Draggable>
|
@drag-end="(el) => onDragEnd(el, currentNode)"
|
||||||
</div>
|
@toggle-open="(exclusive) => toggleOpenNode(currentNode, exclusive)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<NodeIcon :node-type="currentNode.nodeType" :size="12" />
|
||||||
|
</template>
|
||||||
|
</RunDataSchemaNode>
|
||||||
|
|
||||||
|
<RunDataSchemaNode
|
||||||
|
v-if="filteredNodes.length > 0 && !search"
|
||||||
|
:schema="variablesSchema"
|
||||||
|
:title="i18n.baseText('dataMapping.schemaView.variables')"
|
||||||
|
:mapping-enabled="mappingEnabled"
|
||||||
|
:context="context"
|
||||||
|
:search="search"
|
||||||
|
:disable-scroll-in-view="disableScrollInView"
|
||||||
|
@drag-start="onDragStart"
|
||||||
|
@drag-end="onDragEnd"
|
||||||
|
>
|
||||||
|
</RunDataSchemaNode>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else :class="[$style.schemaWrapper, { highlightSchema: highlight }]">
|
<div v-else :class="[$style.schemaWrapper, { highlightSchema: highlight }]">
|
||||||
<div v-if="isDataEmpty(nodeSchema) && search" :class="$style.noMatch">
|
<div v-if="isSchemaEmpty(nodeSchema) && search" :class="$style.noMatch">
|
||||||
<n8n-text tag="h3" size="large">{{
|
<n8n-text tag="h3" size="large">{{
|
||||||
$locale.baseText('ndv.search.noNodeMatch.title')
|
$locale.baseText('ndv.search.noNodeMatch.title')
|
||||||
}}</n8n-text>
|
}}</n8n-text>
|
||||||
|
@ -416,7 +332,7 @@ watch(
|
||||||
|
|
||||||
<div v-else :class="$style.schema" data-test-id="run-data-schema-node-schema">
|
<div v-else :class="$style.schema" data-test-id="run-data-schema-node-schema">
|
||||||
<n8n-info-tip
|
<n8n-info-tip
|
||||||
v-if="isDataEmpty(nodeSchema)"
|
v-if="isSchemaEmpty(nodeSchema)"
|
||||||
:class="$style.tip"
|
:class="$style.tip"
|
||||||
data-test-id="run-data-schema-empty"
|
data-test-id="run-data-schema-empty"
|
||||||
>
|
>
|
||||||
|
@ -431,7 +347,6 @@ watch(
|
||||||
:pane-type="paneType"
|
:pane-type="paneType"
|
||||||
:sub-key="`${props.context}_output_${nodeSchema.type}-0-0`"
|
:sub-key="`${props.context}_output_${nodeSchema.type}-0-0`"
|
||||||
:mapping-enabled="mappingEnabled"
|
:mapping-enabled="mappingEnabled"
|
||||||
:dragging-path="draggingPath"
|
|
||||||
:node="node"
|
:node="node"
|
||||||
:search="search"
|
:search="search"
|
||||||
/>
|
/>
|
||||||
|
@ -440,8 +355,6 @@ watch(
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
@import '@/styles/variables';
|
|
||||||
|
|
||||||
.schemaWrapper {
|
.schemaWrapper {
|
||||||
--header-height: 38px;
|
--header-height: 38px;
|
||||||
--title-spacing-left: 38px;
|
--title-spacing-left: 38px;
|
||||||
|
@ -454,194 +367,4 @@ watch(
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.node {
|
|
||||||
.schema {
|
|
||||||
padding-left: var(--title-spacing-left);
|
|
||||||
scroll-margin-top: var(--header-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice {
|
|
||||||
padding-left: var(--spacing-l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.schema {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
|
|
||||||
&.animated {
|
|
||||||
grid-template-rows: 0fr;
|
|
||||||
transform: translateX(-8px);
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
transition:
|
|
||||||
grid-template-rows 0.2s $ease-out-expo,
|
|
||||||
opacity 0.2s $ease-out-expo 0s,
|
|
||||||
transform 0.2s $ease-out-expo 0s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice {
|
|
||||||
font-size: var(--font-size-2xs);
|
|
||||||
color: var(--color-text-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.innerSchema {
|
|
||||||
min-height: 0;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
margin-bottom: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.titleContainer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-2xs);
|
|
||||||
flex-basis: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
margin-left: auto;
|
|
||||||
padding-left: var(--spacing-2xs);
|
|
||||||
color: var(--color-text-light);
|
|
||||||
font-weight: var(--font-weight-regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1;
|
|
||||||
padding-bottom: var(--spacing-2xs);
|
|
||||||
background: var(--color-run-data-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand {
|
|
||||||
--expand-toggle-size: 30px;
|
|
||||||
width: var(--expand-toggle-size);
|
|
||||||
height: var(--expand-toggle-size);
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:active {
|
|
||||||
color: var(--color-text-dark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.expandIcon {
|
|
||||||
transition: transform 0.2s $ease-out-expo;
|
|
||||||
}
|
|
||||||
|
|
||||||
.open {
|
|
||||||
.expandIcon {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.schema {
|
|
||||||
transition:
|
|
||||||
grid-template-rows 0.2s $ease-out-expo,
|
|
||||||
opacity 0.2s $ease-out-expo,
|
|
||||||
transform 0.2s $ease-out-expo;
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nodeIcon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: var(--spacing-3xs);
|
|
||||||
border: 1px solid var(--color-foreground-light);
|
|
||||||
border-radius: var(--border-radius-base);
|
|
||||||
background-color: var(--color-background-xlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.noMatch {
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: var(--spacing-s) var(--spacing-s) var(--spacing-xl) var(--spacing-s);
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
max-width: 316px;
|
|
||||||
margin-bottom: var(--spacing-2xs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: var(--font-size-2xs);
|
|
||||||
color: var(--color-text-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.items {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: var(--font-size-2xs);
|
|
||||||
color: var(--color-text-light);
|
|
||||||
margin-left: var(--spacing-2xs);
|
|
||||||
|
|
||||||
transition:
|
|
||||||
opacity 0.2s $ease-out-expo,
|
|
||||||
transform 0.2s $ease-out-expo;
|
|
||||||
}
|
|
||||||
|
|
||||||
.triggerIcon {
|
|
||||||
margin-left: var(--spacing-2xs);
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger {
|
|
||||||
.nodeIcon {
|
|
||||||
border-radius: 16px 4px 4px 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@container schema (max-width: 24em) {
|
|
||||||
.depth {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '@/styles/variables';
|
|
||||||
|
|
||||||
.items-enter-from,
|
|
||||||
.items-leave-to {
|
|
||||||
transform: translateX(-4px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-enter-to,
|
|
||||||
.items-leave-from {
|
|
||||||
transform: translateX(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schema-enter-from,
|
|
||||||
.schema-leave-to {
|
|
||||||
grid-template-rows: 0fr;
|
|
||||||
transform: translateX(-8px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schema-enter-to,
|
|
||||||
.schema-leave-from {
|
|
||||||
transform: translateX(0);
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import type { Schema } from '@/Interface';
|
||||||
import type { INodeUi, Schema } from '@/Interface';
|
|
||||||
import { checkExhaustive } from '@/utils/typeGuards';
|
import { checkExhaustive } from '@/utils/typeGuards';
|
||||||
import { shorten } from '@/utils/typesUtils';
|
import { shorten } from '@/utils/typesUtils';
|
||||||
import { getMappedExpression } from '@/utils/mappingUtils';
|
|
||||||
import TextWithHighlights from './TextWithHighlights.vue';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import TextWithHighlights from './TextWithHighlights.vue';
|
||||||
|
import { generatePath } from '@/utils/mappingUtils';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
schema: Schema;
|
schema: Schema;
|
||||||
level: number;
|
level: number;
|
||||||
parent: Schema | null;
|
parent: Schema | null;
|
||||||
subKey: string;
|
subKey: string;
|
||||||
paneType: 'input' | 'output';
|
baseExpression?: string;
|
||||||
mappingEnabled: boolean;
|
mappingEnabled?: boolean;
|
||||||
draggingPath: string;
|
draggingPath?: string;
|
||||||
distanceFromActive?: number;
|
search?: string;
|
||||||
node: INodeUi | null;
|
|
||||||
search: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = withDefaults(defineProps<Props>(), { baseExpression: '' });
|
||||||
|
|
||||||
const isSchemaValueArray = computed(() => Array.isArray(props.schema.value));
|
const isSchemaValueArray = computed(() => Array.isArray(props.schema.value));
|
||||||
const schemaArray = computed(
|
const schemaArray = computed(
|
||||||
|
@ -42,11 +40,8 @@ const text = computed(() =>
|
||||||
const dragged = computed(() => props.draggingPath === props.schema.path);
|
const dragged = computed(() => props.draggingPath === props.schema.path);
|
||||||
|
|
||||||
const getJsonParameterPath = (path: string): string =>
|
const getJsonParameterPath = (path: string): string =>
|
||||||
getMappedExpression({
|
console.log(path.split('.').filter(Boolean)) ||
|
||||||
nodeName: props.node!.name,
|
`{{ ${generatePath(props.baseExpression, path.split('.').filter(Boolean))} }}`;
|
||||||
distanceFromActive: props.distanceFromActive ?? 1,
|
|
||||||
path,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getIconBySchemaType = (type: Schema['type']): string => {
|
const getIconBySchemaType = (type: Schema['type']): string => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -132,12 +127,10 @@ const getIconBySchemaType = (type: Schema['type']): string => {
|
||||||
:schema="s"
|
:schema="s"
|
||||||
:level="level + 1"
|
:level="level + 1"
|
||||||
:parent="schema"
|
:parent="schema"
|
||||||
:pane-type="paneType"
|
|
||||||
:sub-key="`${subKey}-${s.key ?? s.type}`"
|
:sub-key="`${subKey}-${s.key ?? s.type}`"
|
||||||
:mapping-enabled="mappingEnabled"
|
:mapping-enabled="mappingEnabled"
|
||||||
:dragging-path="draggingPath"
|
:dragging-path="draggingPath"
|
||||||
:distance-from-active="distanceFromActive"
|
:base-expression="baseExpression"
|
||||||
:node="node"
|
|
||||||
:search="search"
|
:search="search"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
367
packages/editor-ui/src/components/RunDataSchemaNode.vue
Normal file
367
packages/editor-ui/src/components/RunDataSchemaNode.vue
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useDataSchema } from '@/composables/useDataSchema';
|
||||||
|
import { useDebounce } from '@/composables/useDebounce';
|
||||||
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import type { Schema } from '@/Interface';
|
||||||
|
import { snakeCase } from 'lodash-es';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string;
|
||||||
|
schema: Schema | null;
|
||||||
|
baseExpression?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
itemsCount?: number | null;
|
||||||
|
search?: string;
|
||||||
|
context?: 'ndv' | 'modal';
|
||||||
|
open?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
isTrigger?: boolean;
|
||||||
|
mappingEnabled?: boolean;
|
||||||
|
disableScrollInView?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
context: 'ndv',
|
||||||
|
open: undefined,
|
||||||
|
disabled: false,
|
||||||
|
isTrigger: false,
|
||||||
|
mappingEnabled: false,
|
||||||
|
disableScrollInView: false,
|
||||||
|
baseExpression: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
toggleOpen: [exclusive?: boolean];
|
||||||
|
dragStart: [el: HTMLElement];
|
||||||
|
dragEnd: [el: HTMLElement];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const i18n = useI18n();
|
||||||
|
const { debounce } = useDebounce();
|
||||||
|
const { isSchemaEmpty } = useDataSchema();
|
||||||
|
|
||||||
|
const draggingPath = ref('');
|
||||||
|
const localOpen = ref(false);
|
||||||
|
|
||||||
|
const isOpen = computed(() => props.open ?? localOpen.value);
|
||||||
|
|
||||||
|
function toggleOpen(exclusive = false) {
|
||||||
|
console.log(localOpen.value, isOpen.value, props.open);
|
||||||
|
localOpen.value = !localOpen.value;
|
||||||
|
emit('toggleOpen', exclusive);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragStart(el: HTMLElement) {
|
||||||
|
draggingPath.value = el.dataset.value as string;
|
||||||
|
emit('dragStart', el);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragEnd(el: HTMLElement) {
|
||||||
|
draggingPath.value = '';
|
||||||
|
emit('dragEnd', el);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTransitionStart = debounce(
|
||||||
|
(event: TransitionEvent) => {
|
||||||
|
if (isOpen.value && event.target instanceof HTMLElement && !props.disableScrollInView) {
|
||||||
|
event.target.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'nearest',
|
||||||
|
inline: 'nearest',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ debounceTime: 100, trailing: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
defineSlots<{ icon?(): never }>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div data-test-id="run-data-schema-node" :class="[$style.node, { [$style.open]: isOpen }]">
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
$style.header,
|
||||||
|
{
|
||||||
|
[$style.trigger]: isTrigger,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
data-test-id="run-data-schema-node-header"
|
||||||
|
>
|
||||||
|
<div :class="$style.expand" @click="toggleOpen()">
|
||||||
|
<font-awesome-icon icon="angle-right" :class="$style.expandIcon" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
:class="$style.titleContainer"
|
||||||
|
data-test-id="run-data-schema-node-name"
|
||||||
|
@click="toggleOpen(true)"
|
||||||
|
>
|
||||||
|
<div v-if="$slots.icon" :class="$style.nodeIcon">
|
||||||
|
<slot name="icon" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="$style.title">
|
||||||
|
{{ title }}
|
||||||
|
<span v-if="subtitle" :class="$style.subtitle">{{ subtitle }}</span>
|
||||||
|
</div>
|
||||||
|
<font-awesome-icon v-if="isTrigger" :class="$style.triggerIcon" icon="bolt" size="xs" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Transition name="items">
|
||||||
|
<div
|
||||||
|
v-if="itemsCount && isOpen"
|
||||||
|
:class="$style.items"
|
||||||
|
data-test-id="run-data-schema-node-item-count"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
i18n.baseText('ndv.output.items', {
|
||||||
|
interpolate: { count: itemsCount },
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Draggable
|
||||||
|
type="mapping"
|
||||||
|
target-data-key="mappable"
|
||||||
|
:disabled="!mappingEnabled"
|
||||||
|
@dragstart="onDragStart"
|
||||||
|
@dragend="onDragEnd"
|
||||||
|
>
|
||||||
|
<template #preview="{ canDrop, el }">
|
||||||
|
<MappingPill v-if="el" :html="el.outerHTML" :can-drop="canDrop" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<Transition name="schema">
|
||||||
|
<div
|
||||||
|
v-if="schema || search"
|
||||||
|
:class="[$style.schema, $style.animated]"
|
||||||
|
data-test-id="run-data-schema-node-schema"
|
||||||
|
@transitionstart="onTransitionStart"
|
||||||
|
>
|
||||||
|
<div :class="$style.innerSchema" @transitionstart.stop>
|
||||||
|
<div v-if="disabled" :class="$style.notice" data-test-id="run-data-schema-disabled">
|
||||||
|
{{ i18n.baseText('dataMapping.schemaView.disabled') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="isSchemaEmpty(schema)"
|
||||||
|
:class="$style.notice"
|
||||||
|
data-test-id="run-data-schema-empty"
|
||||||
|
>
|
||||||
|
{{ i18n.baseText('dataMapping.schemaView.emptyData') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RunDataSchemaItem
|
||||||
|
v-else-if="schema"
|
||||||
|
:schema="schema"
|
||||||
|
:level="0"
|
||||||
|
:parent="null"
|
||||||
|
:sub-key="`${context}_${snakeCase(title)}`"
|
||||||
|
:mapping-enabled="mappingEnabled"
|
||||||
|
:dragging-path="draggingPath"
|
||||||
|
:base-expression="baseExpression"
|
||||||
|
:search="search"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Draggable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" module>
|
||||||
|
@import '@/styles/variables';
|
||||||
|
|
||||||
|
.node {
|
||||||
|
.schema {
|
||||||
|
padding-left: var(--title-spacing-left);
|
||||||
|
scroll-margin-top: var(--header-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
padding-left: var(--spacing-l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.schema {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
|
||||||
|
&.animated {
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
transform: translateX(-8px);
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
transition:
|
||||||
|
grid-template-rows 0.2s $ease-out-expo,
|
||||||
|
opacity 0.2s $ease-out-expo 0s,
|
||||||
|
transform 0.2s $ease-out-expo 0s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
font-size: var(--font-size-2xs);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.innerSchema {
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-2xs);
|
||||||
|
flex-basis: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin-left: auto;
|
||||||
|
padding-left: var(--spacing-2xs);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
padding-bottom: var(--spacing-2xs);
|
||||||
|
background: var(--color-run-data-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand {
|
||||||
|
--expand-toggle-size: 30px;
|
||||||
|
width: var(--expand-toggle-size);
|
||||||
|
height: var(--expand-toggle-size);
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandIcon {
|
||||||
|
transition: transform 0.2s $ease-out-expo;
|
||||||
|
}
|
||||||
|
|
||||||
|
.open {
|
||||||
|
.expandIcon {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.schema {
|
||||||
|
transition:
|
||||||
|
grid-template-rows 0.2s $ease-out-expo,
|
||||||
|
opacity 0.2s $ease-out-expo,
|
||||||
|
transform 0.2s $ease-out-expo;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodeIcon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-3xs);
|
||||||
|
border: 1px solid var(--color-foreground-light);
|
||||||
|
border-radius: var(--border-radius-base);
|
||||||
|
background-color: var(--color-background-xlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.noMatch {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-s) var(--spacing-s) var(--spacing-xl) var(--spacing-s);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
max-width: 316px;
|
||||||
|
margin-bottom: var(--spacing-2xs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: var(--font-size-2xs);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.items {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: var(--font-size-2xs);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
margin-left: var(--spacing-2xs);
|
||||||
|
|
||||||
|
transition:
|
||||||
|
opacity 0.2s $ease-out-expo,
|
||||||
|
transform 0.2s $ease-out-expo;
|
||||||
|
}
|
||||||
|
|
||||||
|
.triggerIcon {
|
||||||
|
margin-left: var(--spacing-2xs);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
.nodeIcon {
|
||||||
|
border-radius: 16px 4px 4px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container schema (max-width: 24em) {
|
||||||
|
.depth {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/styles/variables';
|
||||||
|
|
||||||
|
.items-enter-from,
|
||||||
|
.items-leave-to {
|
||||||
|
transform: translateX(-4px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-enter-to,
|
||||||
|
.items-leave-from {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schema-enter-from,
|
||||||
|
.schema-leave-to {
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
transform: translateX(-8px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schema-enter-to,
|
||||||
|
.schema-leave-from {
|
||||||
|
transform: translateX(0);
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -156,11 +156,22 @@ export function useDataSchema() {
|
||||||
return schemaMatches(schema, search) ? schema : null;
|
return schemaMatches(schema, search) ? schema : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSchemaEmpty(schema: Schema | null) {
|
||||||
|
if (!schema) return true;
|
||||||
|
// Utilize the generated schema instead of looping over the entire data again
|
||||||
|
// The schema for empty data is { type: 'object' | 'array', value: [] }
|
||||||
|
const isObjectOrArray = schema.type === 'object' || schema.type === 'array';
|
||||||
|
const isEmpty = Array.isArray(schema.value) && schema.value.length === 0;
|
||||||
|
|
||||||
|
return isObjectOrArray && isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getSchema,
|
getSchema,
|
||||||
getSchemaForExecutionData,
|
getSchemaForExecutionData,
|
||||||
getNodeInputData,
|
getNodeInputData,
|
||||||
getInputDataWithPinned,
|
getInputDataWithPinned,
|
||||||
filterSchema,
|
filterSchema,
|
||||||
|
isSchemaEmpty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -641,6 +641,7 @@
|
||||||
"dataMapping.schemaView.emptyData": "No fields - item(s) exist, but they're empty",
|
"dataMapping.schemaView.emptyData": "No fields - item(s) exist, but they're empty",
|
||||||
"dataMapping.schemaView.disabled": "This node is disabled and will just pass data through",
|
"dataMapping.schemaView.disabled": "This node is disabled and will just pass data through",
|
||||||
"dataMapping.schemaView.noMatches": "No results for '{search}'",
|
"dataMapping.schemaView.noMatches": "No results for '{search}'",
|
||||||
|
"dataMapping.schemaView.variables": "Variables and context",
|
||||||
"displayWithChange.cancelEdit": "Cancel Edit",
|
"displayWithChange.cancelEdit": "Cancel Edit",
|
||||||
"displayWithChange.clickToChange": "Click to Change",
|
"displayWithChange.clickToChange": "Click to Change",
|
||||||
"displayWithChange.setValue": "Set Value",
|
"displayWithChange.setValue": "Set Value",
|
||||||
|
|
|
@ -18,7 +18,7 @@ export function generatePath(root: string, path: Array<string | number>): string
|
||||||
return `${accu}['${escapeMappingString(part)}']`;
|
return `${accu}['${escapeMappingString(part)}']`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${accu}.${part}`;
|
return accu ? `${accu}.${part}` : part;
|
||||||
}, root);
|
}, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue