feat(editor): Collapse button on table view (#16993)

This commit is contained in:
Suguru Inoue
2025-07-07 10:54:59 +02:00
committed by GitHub
parent bd8b7b468c
commit d3330b6bcc
13 changed files with 276 additions and 10 deletions

View File

@@ -7,7 +7,7 @@ import { getMappedExpression } from '@/utils/mappingUtils';
import { getPairedItemId } from '@/utils/pairedItemUtils';
import { shorten } from '@/utils/typesUtils';
import type { GenericValue, IDataObject, INodeExecutionData } from 'n8n-workflow';
import { computed, onMounted, ref, watch } from 'vue';
import { useTemplateRef, computed, onMounted, ref, watch } from 'vue';
import Draggable from '@/components/Draggable.vue';
import MappingPill from './MappingPill.vue';
import TextWithHighlights from './TextWithHighlights.vue';
@@ -35,6 +35,7 @@ type Props = {
headerBgColor?: 'base' | 'light';
compact?: boolean;
disableHoverHighlight?: boolean;
collapsingColumnName: string | null;
};
const props = withDefaults(defineProps<Props>(), {
@@ -52,9 +53,13 @@ const emit = defineEmits<{
activeRowChanged: [row: number | null];
displayModeChange: [mode: IRunDataDisplayMode];
mounted: [data: { avgRowHeight: number }];
collapsingColumnChanged: [columnName: string | null];
}>();
const externalHooks = useExternalHooks();
const tableRef = useTemplateRef('tableRef');
const activeColumn = ref(-1);
const forceShowGrip = ref(false);
const draggedColumn = ref(false);
@@ -64,6 +69,7 @@ const activeRow = ref<number | null>(null);
const columnLimit = ref(MAX_COLUMNS_LIMIT);
const columnLimitExceeded = ref(false);
const draggableRef = ref<DraggableRef>();
const fixedColumnWidths = ref<number[] | undefined>();
const ndvStore = useNDVStore();
const workflowsStore = useWorkflowsStore();
@@ -82,6 +88,13 @@ const canDraggableDrop = computed(() => ndvStore.canDraggableDrop);
const draggableStickyPosition = computed(() => ndvStore.draggableStickyPos);
const pairedItemMappings = computed(() => workflowsStore.workflowExecutionPairedItemMappings);
const tableData = computed(() => convertToTable(props.inputData));
const collapsingColumnIndex = computed(() => {
if (!props.collapsingColumnName) {
return -1;
}
return tableData.value.columns.indexOf(props.collapsingColumnName);
});
onMounted(() => {
if (tableData.value?.columns && draggableRef.value) {
@@ -419,6 +432,15 @@ function switchToJsonView() {
emit('displayModeChange', 'json');
}
function handleSetCollapsingColumn(columnIndex: number) {
emit(
'collapsingColumnChanged',
collapsingColumnIndex.value === columnIndex
? null
: (tableData.value.columns[columnIndex] ?? null),
);
}
watch(focusedMappableInput, (curr) => {
setTimeout(
() => {
@@ -427,6 +449,27 @@ watch(focusedMappableInput, (curr) => {
curr ? 300 : 150,
);
});
watch(
[collapsingColumnIndex, tableRef],
([index, table]) => {
if (index === -1) {
fixedColumnWidths.value = undefined;
return;
}
if (table === null) {
return;
}
fixedColumnWidths.value = [...table.querySelectorAll('thead tr th')].map((el) =>
el instanceof HTMLElement
? el.getBoundingClientRect().width // using getBoundingClientRect for decimal accuracy
: 0,
);
},
{ immediate: true, flush: 'post' },
);
</script>
<template>
@@ -437,6 +480,7 @@ watch(focusedMappableInput, (curr) => {
[$style.highlight]: highlight,
[$style.lightHeader]: headerBgColor === 'light',
[$style.compact]: props.compact,
[$style.hasCollapsingColumn]: fixedColumnWidths !== undefined,
},
]"
>
@@ -500,13 +544,20 @@ watch(focusedMappableInput, (curr) => {
</tr>
</tbody>
</table>
<table v-else :class="$style.table">
<table v-else ref="tableRef" :class="$style.table">
<colgroup v-if="fixedColumnWidths">
<col v-for="(width, i) in fixedColumnWidths" :key="i" :width="width" />
</colgroup>
<thead>
<tr>
<th v-if="tableData.metadata.hasExecutionIds" :class="$style.executionLinkRowHeader">
<!-- column for execution link -->
</th>
<th v-for="(column, i) in tableData.columns || []" :key="column">
<th
v-for="(column, i) in tableData.columns || []"
:key="column"
:class="collapsingColumnIndex === i ? $style.isCollapsingColumn : ''"
>
<N8nTooltip placement="bottom-start" :disabled="!mappingEnabled" :show-after="1000">
<template #content>
<div>
@@ -540,7 +591,23 @@ watch(focusedMappableInput, (curr) => {
:content="getValueToRender(column || '')"
:search="search"
/>
<div :class="$style.dragButton">
<N8nTooltip
:content="i18n.baseText('dataMapping.tableView.columnCollapsing.tooltip')"
:disabled="mappingEnabled || collapsingColumnIndex === i"
>
<N8nIconButton
:class="$style.collapseColumnButton"
type="tertiary"
size="xmini"
text
:icon="
collapsingColumnIndex === i ? 'chevrons-up-down' : 'chevrons-down-up'
"
:aria-label="i18n.baseText('dataMapping.tableView.columnCollapsing')"
@click="handleSetCollapsingColumn(i)"
/>
</N8nTooltip>
<div v-if="mappingEnabled" :class="$style.dragButton">
<n8n-icon icon="grip-vertical" />
</div>
</div>
@@ -629,7 +696,10 @@ watch(focusedMappableInput, (curr) => {
:key="index2"
:data-row="index1"
:data-col="index2"
:class="hasJsonInColumn(index2) ? $style.minColWidth : $style.limitColWidth"
:class="[
hasJsonInColumn(index2) ? $style.minColWidth : $style.limitColWidth,
collapsingColumnIndex === index2 ? $style.isCollapsingColumn : '',
]"
@mouseenter="onMouseEnterCell"
@mouseleave="onMouseLeaveCell"
>
@@ -757,6 +827,41 @@ watch(focusedMappableInput, (curr) => {
td:last-child {
border-right: var(--border-base);
}
.hasCollapsingColumn & {
table-layout: fixed;
td:not(.isCollapsingColumn) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
& :global(.n8n-tree) {
height: 1.5em;
overflow: hidden;
}
}
}
}
th.isCollapsingColumn {
border-top-color: var(--color-foreground-xdark);
border-left-color: var(--color-foreground-xdark);
border-right-color: var(--color-foreground-xdark);
}
td.isCollapsingColumn {
border-left-color: var(--color-foreground-xdark);
border-right-color: var(--color-foreground-xdark);
tr:last-child & {
border-bottom-color: var(--color-foreground-xdark);
}
}
td.isCollapsingColumn + td,
th.isCollapsingColumn + th {
border-left-color: var(--color-foreground-xdark);
}
.nodeClass {
@@ -808,7 +913,10 @@ watch(focusedMappableInput, (curr) => {
.dragButton {
opacity: 0;
margin-left: var(--spacing-2xs);
& > svg {
vertical-align: middle;
}
}
.dataKey {
@@ -885,4 +993,18 @@ watch(focusedMappableInput, (curr) => {
.executionLinkRowHeader {
width: var(--spacing-m);
}
.collapseColumnButton {
span {
flex-shrink: 0;
}
visibility: hidden;
margin-block: calc(-2 * var(--spacing-2xs));
.isCollapsingColumn &,
th:hover & {
visibility: visible;
}
}
</style>