mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
refactor(editor): Move editor-ui and design-system to frontend dir (no-changelog) (#13564)
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
<script setup lang="ts">
|
||||
import { useDebounce } from '@/composables/useDebounce';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import type {
|
||||
AssignmentCollectionValue,
|
||||
AssignmentValue,
|
||||
INode,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import DropArea from '../DropArea/DropArea.vue';
|
||||
import ParameterOptions from '../ParameterOptions.vue';
|
||||
import Assignment from './Assignment.vue';
|
||||
import { inputDataToAssignments, typeFromExpression } from './utils';
|
||||
import { propertyNameFromExpression } from '@/utils/mappingUtils';
|
||||
import Draggable from 'vuedraggable';
|
||||
|
||||
interface Props {
|
||||
parameter: INodeProperties;
|
||||
value: AssignmentCollectionValue;
|
||||
path: string;
|
||||
node: INode | null;
|
||||
isReadOnly?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), { isReadOnly: false });
|
||||
|
||||
const emit = defineEmits<{
|
||||
valueChanged: [value: { name: string; node: string; value: AssignmentCollectionValue }];
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const state = reactive<{ paramValue: AssignmentCollectionValue }>({
|
||||
paramValue: {
|
||||
assignments:
|
||||
props.value.assignments?.map((assignment) => {
|
||||
if (!assignment.id) assignment.id = crypto.randomUUID();
|
||||
return assignment;
|
||||
}) ?? [],
|
||||
},
|
||||
});
|
||||
|
||||
const ndvStore = useNDVStore();
|
||||
const { callDebounced } = useDebounce();
|
||||
|
||||
const issues = computed(() => {
|
||||
if (!ndvStore.activeNode) return {};
|
||||
return ndvStore.activeNode?.issues?.parameters ?? {};
|
||||
});
|
||||
|
||||
const empty = computed(() => state.paramValue.assignments.length === 0);
|
||||
const activeDragField = computed(() => propertyNameFromExpression(ndvStore.draggableData));
|
||||
const inputData = computed(() => ndvStore.ndvInputData?.[0]?.json);
|
||||
const actions = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: i18n.baseText('assignment.addAll'),
|
||||
value: 'addAll',
|
||||
disabled: !inputData.value,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('assignment.clearAll'),
|
||||
value: 'clearAll',
|
||||
disabled: state.paramValue.assignments.length === 0,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
watch(state.paramValue, (value) => {
|
||||
void callDebounced(
|
||||
() => {
|
||||
emit('valueChanged', { name: props.path, value, node: props.node?.name as string });
|
||||
},
|
||||
{ debounceTime: 1000 },
|
||||
);
|
||||
});
|
||||
|
||||
function addAssignment(): void {
|
||||
state.paramValue.assignments.push({
|
||||
id: crypto.randomUUID(),
|
||||
name: '',
|
||||
value: '',
|
||||
type: 'string',
|
||||
});
|
||||
}
|
||||
|
||||
function dropAssignment(expression: string): void {
|
||||
state.paramValue.assignments.push({
|
||||
id: crypto.randomUUID(),
|
||||
name: propertyNameFromExpression(expression),
|
||||
value: `=${expression}`,
|
||||
type: typeFromExpression(expression),
|
||||
});
|
||||
}
|
||||
|
||||
function onAssignmentUpdate(index: number, value: AssignmentValue): void {
|
||||
state.paramValue.assignments[index] = value;
|
||||
}
|
||||
|
||||
function onAssignmentRemove(index: number): void {
|
||||
state.paramValue.assignments.splice(index, 1);
|
||||
}
|
||||
|
||||
function getIssues(index: number): string[] {
|
||||
return issues.value[`${props.parameter.name}.${index}`] ?? [];
|
||||
}
|
||||
|
||||
function optionSelected(action: string) {
|
||||
if (action === 'clearAll') {
|
||||
state.paramValue.assignments = [];
|
||||
} else if (action === 'addAll' && inputData.value) {
|
||||
const newAssignments = inputDataToAssignments(inputData.value);
|
||||
state.paramValue.assignments = state.paramValue.assignments.concat(newAssignments);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{ [$style.assignmentCollection]: true, [$style.empty]: empty }"
|
||||
:data-test-id="`assignment-collection-${parameter.name}`"
|
||||
>
|
||||
<n8n-input-label
|
||||
:label="parameter.displayName"
|
||||
:show-expression-selector="false"
|
||||
size="small"
|
||||
underline
|
||||
color="text-dark"
|
||||
>
|
||||
<template #options>
|
||||
<ParameterOptions
|
||||
:parameter="parameter"
|
||||
:value="value"
|
||||
:custom-actions="actions"
|
||||
:is-read-only="isReadOnly"
|
||||
:show-expression-selector="false"
|
||||
@update:model-value="optionSelected"
|
||||
/>
|
||||
</template>
|
||||
</n8n-input-label>
|
||||
<div :class="$style.content">
|
||||
<div :class="$style.assignments">
|
||||
<Draggable
|
||||
v-model="state.paramValue.assignments"
|
||||
item-key="id"
|
||||
handle=".drag-handle"
|
||||
:drag-class="$style.dragging"
|
||||
:ghost-class="$style.ghost"
|
||||
>
|
||||
<template #item="{ index, element: assignment }">
|
||||
<Assignment
|
||||
:model-value="assignment"
|
||||
:index="index"
|
||||
:path="`${path}.assignments.${index}`"
|
||||
:issues="getIssues(index)"
|
||||
:class="$style.assignment"
|
||||
:is-read-only="isReadOnly"
|
||||
@update:model-value="(value) => onAssignmentUpdate(index, value)"
|
||||
@remove="() => onAssignmentRemove(index)"
|
||||
>
|
||||
</Assignment>
|
||||
</template>
|
||||
</Draggable>
|
||||
</div>
|
||||
<div
|
||||
v-if="!isReadOnly"
|
||||
:class="$style.dropAreaWrapper"
|
||||
data-test-id="assignment-collection-drop-area"
|
||||
@click="addAssignment"
|
||||
>
|
||||
<DropArea :sticky-offset="empty ? [-4, 32] : [92, 0]" @drop="dropAssignment">
|
||||
<template #default="{ active, droppable }">
|
||||
<div :class="{ [$style.active]: active, [$style.droppable]: droppable }">
|
||||
<div v-if="droppable" :class="$style.dropArea">
|
||||
<span>{{ i18n.baseText('assignment.dropField') }}</span>
|
||||
<span :class="$style.activeField">{{ activeDragField }}</span>
|
||||
</div>
|
||||
<div v-else :class="$style.dropArea">
|
||||
<span>{{ i18n.baseText('assignment.dragFields') }}</span>
|
||||
<span :class="$style.or">{{ i18n.baseText('assignment.or') }}</span>
|
||||
<span :class="$style.add">{{ i18n.baseText('assignment.add') }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</DropArea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.assignmentCollection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: var(--spacing-xs) 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
gap: var(--spacing-l);
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.assignments {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4xs);
|
||||
}
|
||||
|
||||
.assignment {
|
||||
padding-left: var(--spacing-l);
|
||||
}
|
||||
|
||||
.dropAreaWrapper {
|
||||
cursor: pointer;
|
||||
|
||||
&:not(.empty .dropAreaWrapper) {
|
||||
padding-left: var(--spacing-l);
|
||||
}
|
||||
|
||||
&:hover .add {
|
||||
color: var(--color-primary-shade-1);
|
||||
}
|
||||
}
|
||||
|
||||
.dropArea {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-dark);
|
||||
gap: 1ch;
|
||||
min-height: 24px;
|
||||
|
||||
> span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.or {
|
||||
color: var(--color-text-light);
|
||||
font-size: var(--font-size-2xs);
|
||||
}
|
||||
|
||||
.add {
|
||||
color: var(--color-primary);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.activeField {
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-ndv-droppable-parameter);
|
||||
}
|
||||
|
||||
.active {
|
||||
.activeField {
|
||||
color: var(--color-success);
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
.dropArea {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-3xs);
|
||||
min-height: 20vh;
|
||||
}
|
||||
|
||||
.droppable .dropArea {
|
||||
flex-direction: row;
|
||||
gap: 1ch;
|
||||
}
|
||||
|
||||
.content {
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
.ghost,
|
||||
.dragging {
|
||||
border-radius: var(--border-radius-base);
|
||||
padding-right: var(--spacing-xs);
|
||||
padding-bottom: var(--spacing-xs);
|
||||
}
|
||||
.ghost {
|
||||
background-color: var(--color-background-base);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.dragging {
|
||||
background-color: var(--color-background-xlight);
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user