SYNC: Merge pull request #3 from dbgate/feature/zip

This commit is contained in:
Jan Prochazka
2025-04-23 13:17:54 +02:00
committed by Diflow
parent 54c53f0b56
commit 8f4118a6b8
82 changed files with 3981 additions and 2814 deletions

View File

@@ -7,6 +7,7 @@
props?: any;
formatter?: any;
slot?: number;
slotKey?: string;
isHighlighted?: Function;
sortable?: boolean;
filterable?: boolean;
@@ -25,12 +26,15 @@
import { evalFilterBehaviour } from 'dbgate-tools';
import { evaluateCondition } from 'dbgate-sqltree';
import { compileCompoudEvalCondition } from 'dbgate-filterparser';
import { chevronExpandIcon } from '../icons/expandIcons';
export let columns: (TableControlColumn | false)[];
export let rows;
export let rows = null;
export let groupedRows = null;
export let focusOnCreate = false;
export let selectable = false;
export let selectedIndex = 0;
export let selectedKey = null;
export let clickable = false;
export let disableFocusOutline = false;
export let emptyMessage = null;
@@ -41,9 +45,12 @@
export let checkedKeys = null;
export let onSetCheckedKeys = null;
export let extractCheckedKey = x => x.id;
export let extractTableItemKey = x => x.id;
export let itemSupportsCheckbox = x => true;
export let filters = null;
export let selectionMode: 'index' | 'key' = 'index';
const dispatch = createEventDispatcher();
$: columnList = _.compact(_.flatten(columns));
@@ -53,37 +60,120 @@
});
const handleKeyDown = event => {
if (event.keyCode == keycodes.downArrow) {
selectedIndex = Math.min(selectedIndex + 1, sortedRows.length - 1);
const oldSelectedIndex =
selectionMode == 'index' ? selectedIndex : _.findIndex(flatRowsShown, x => extractTableItemKey(x) == selectedKey);
let newIndex = oldSelectedIndex;
switch (event.keyCode) {
case keycodes.downArrow:
newIndex = Math.min(newIndex + 1, flatRowsShown.length - 1);
break;
case keycodes.upArrow:
newIndex = Math.max(0, newIndex - 1);
break;
case keycodes.home:
newIndex = 0;
break;
case keycodes.end:
newIndex = rows.length - 1;
break;
case keycodes.pageUp:
newIndex -= 10;
break;
case keycodes.pageDown:
newIndex += 10;
break;
}
if (event.keyCode == keycodes.upArrow) {
selectedIndex = Math.max(0, selectedIndex - 1);
if (newIndex < 0) {
newIndex = 0;
}
if (newIndex >= flatRowsShown.length) {
newIndex = flatRowsShown.length - 1;
}
if (clickable && oldSelectedIndex != newIndex) {
event.preventDefault();
domRows[newIndex]?.scrollIntoView();
if (clickable) {
dispatch('clickrow', flatRowsShown[newIndex]);
}
if (selectionMode == 'index') {
selectedIndex = newIndex;
} else {
selectedKey = extractTableItemKey(flatRowsShown[newIndex]);
}
}
};
function filterRows(rows, filters) {
function filterRows(grows, filters) {
const condition = compileCompoudEvalCondition(filters);
if (!condition) return rows;
if (!condition) return grows;
return rows.filter(row => {
const newrow = { ...row };
for (const col of columnList) {
if (col.filteredExpression) {
newrow[col.fieldName] = col.filteredExpression(row);
}
}
return evaluateCondition(condition, newrow);
});
return grows
.map(gitem => {
return {
group: gitem.group,
rows: gitem.rows.filter(row => {
const newrow = { ...row };
for (const col of columnList) {
if (col.filteredExpression) {
newrow[col.fieldName] = col.filteredExpression(row);
}
}
return evaluateCondition(condition, newrow);
}),
};
})
.filter(gitem => gitem.rows.length > 0);
}
// function computeGroupedRows(array) {
// if (!extractGroupName) {
// return [{ label: null, rows: array }];
// }
// const res = [];
// let lastGroupName = null;
// let buildArray = [];
// for (const item of array) {
// const groupName = extractGroupName(item);
// if (lastGroupName != groupName) {
// if (buildArray.length > 0) {
// res.push({ label: lastGroupName, rows: buildArray });
// }
// lastGroupName = groupName;
// buildArray = [];
// }
// buildArray.push(item);
// }
// if (buildArray.length > 0) {
// res.push({ label: lastGroupName, rows: buildArray });
// }
// }
let sortedByField = null;
let sortOrderIsDesc = false;
let collapsedGroupIndexes = [];
let domRows = {};
$: filteredRows = filters ? filterRows(rows, $filters) : rows;
$: rowsSource = groupedRows ? groupedRows : [{ group: null, rows }];
$: sortedRowsTmp = sortedByField ? _.sortBy(filteredRows || [], sortedByField) : filteredRows;
$: sortedRows = sortOrderIsDesc ? [...sortedRowsTmp].reverse() : sortedRowsTmp;
$: filteredRows = filters ? filterRows(rowsSource, $filters) : rowsSource;
$: sortedRows = sortedByField
? filteredRows.map(gitem => {
let res = _.sortBy(gitem.rows, sortedByField);
if (sortOrderIsDesc) res = [...res].reverse();
return { group: gitem.group, rows: res };
})
: filteredRows;
// $: console.log('sortedRows', sortedRows);
$: flatRowsShown = sortedRows.map(gitem => gitem.rows).flat();
$: checkableFlatRowsShown = flatRowsShown.filter(x => itemSupportsCheckbox(x));
// $: groupedRows = computeGroupedRows(sortedRows);
</script>
<table
@@ -98,7 +188,16 @@
<thead class:stickyHeader>
<tr>
{#if checkedKeys}
<th></th>
<th>
<input
type="checkbox"
checked={checkableFlatRowsShown.every(r => checkedKeys.includes(extractTableItemKey(r)))}
on:change={e => {
if (e.target['checked']) onSetCheckedKeys(checkableFlatRowsShown.map(r => extractTableItemKey(r)));
else onSetCheckedKeys([]);
}}
/>
</th>
{/if}
{#each columnList as col}
<th
@@ -129,7 +228,7 @@
{#if filters}
<tr>
{#if checkedKeys}
<td></td>
<td class="empty-cell"></td>
{/if}
{#each columnList as col}
<td class="filter-cell" class:empty-cell={!col.filterable}>
@@ -147,58 +246,91 @@
{/if}
</thead>
<tbody>
{#each sortedRows as row, index}
<tr
class:selected={selectable && selectedIndex == index}
class:clickable
on:click={() => {
if (selectable) {
selectedIndex = index;
domTable.focus();
}
if (clickable) {
dispatch('clickrow', row);
}
}}
>
{#if checkedKeys}
<td>
<input
type="checkbox"
checked={checkedKeys.includes(extractCheckedKey(row))}
on:change={e => {
if (e.target['checked']) onSetCheckedKeys(_.uniq([...checkedKeys, extractCheckedKey(row)]));
else onSetCheckedKeys(checkedKeys.filter(x => x != extractCheckedKey(row)));
}}
/>
{#each sortedRows as gitem, groupIndex}
{#if gitem.group}
<tr class="group-row">
<td
colspan={columnList.length + (checkedKeys ? 1 : 0)}
class="groupcell"
on:click={() => {
if (collapsedGroupIndexes.includes(groupIndex)) {
collapsedGroupIndexes = collapsedGroupIndexes.filter(x => x != groupIndex);
} else {
collapsedGroupIndexes = [...collapsedGroupIndexes, groupIndex];
}
}}
>
<FontIcon icon={chevronExpandIcon(!collapsedGroupIndexes.includes(groupIndex))} padRight />
<strong>{gitem.group} ({gitem.rows.length})</strong>
</td>
{/if}
{#each columnList as col}
{@const rowProps = { ...col.props, ...(col.getProps ? col.getProps(row) : null) }}
<td class:isHighlighted={col.isHighlighted && col.isHighlighted(row)} class:noCellPadding>
{#if col.component}
<svelte:component this={col.component} {...rowProps} />
{:else if col.formatter}
{col.formatter(row)}
{:else if col.slot != null}
{#if col.slot == -1}<slot name="-1" {row} {col} {index} />
{:else if col.slot == 0}<slot name="0" {row} {col} {index} {...rowProps} />
{:else if col.slot == 1}<slot name="1" {row} {col} {index} {...rowProps} />
{:else if col.slot == 2}<slot name="2" {row} {col} {index} {...rowProps} />
{:else if col.slot == 3}<slot name="3" {row} {col} {index} {...rowProps} />
{:else if col.slot == 4}<slot name="4" {row} {col} {index} {...rowProps} />
{:else if col.slot == 5}<slot name="5" {row} {col} {index} {...rowProps} />
{:else if col.slot == 6}<slot name="6" {row} {col} {index} {...rowProps} />
{:else if col.slot == 7}<slot name="7" {row} {col} {index} {...rowProps} />
{:else if col.slot == 8}<slot name="8" {row} {col} {index} {...rowProps} />
{:else if col.slot == 9}<slot name="9" {row} {col} {index} {...rowProps} />
{/if}
{:else}
{row[col.fieldName] || ''}
</tr>
{/if}
{#if !collapsedGroupIndexes.includes(groupIndex)}
{#each gitem.rows as row}
{@const index = _.indexOf(flatRowsShown, row)}
<tr
class:selected={selectable &&
(selectionMode == 'index' ? selectedIndex == index : selectedKey == extractTableItemKey(row))}
class:clickable
bind:this={domRows[index]}
on:click={() => {
if (selectable) {
if (selectionMode == 'index') {
selectedIndex = index;
} else {
selectedKey = extractTableItemKey(row);
}
domTable.focus();
}
if (clickable) {
dispatch('clickrow', row);
}
}}
>
{#if checkedKeys}
<td>
{#if itemSupportsCheckbox(row)}
<input
type="checkbox"
checked={checkedKeys.includes(extractTableItemKey(row))}
on:change={e => {
if (e.target['checked']) onSetCheckedKeys(_.uniq([...checkedKeys, extractTableItemKey(row)]));
else onSetCheckedKeys(checkedKeys.filter(x => x != extractTableItemKey(row)));
}}
/>
{/if}
</td>
{/if}
</td>
{#each columnList as col}
{@const rowProps = { ...col.props, ...(col.getProps ? col.getProps(row) : null) }}
<td class:isHighlighted={col.isHighlighted && col.isHighlighted(row)} class:noCellPadding>
{#if col.component}
<svelte:component this={col.component} {...rowProps} />
{:else if col.formatter}
{col.formatter(row)}
{:else if col.slot != null}
{#key row[col.slotKey] || 'key'}
{#if col.slot == -1}<slot name="-1" {row} {col} {index} />
{:else if col.slot == 0}<slot name="0" {row} {col} {index} {...rowProps} />
{:else if col.slot == 1}<slot name="1" {row} {col} {index} {...rowProps} />
{:else if col.slot == 2}<slot name="2" {row} {col} {index} {...rowProps} />
{:else if col.slot == 3}<slot name="3" {row} {col} {index} {...rowProps} />
{:else if col.slot == 4}<slot name="4" {row} {col} {index} {...rowProps} />
{:else if col.slot == 5}<slot name="5" {row} {col} {index} {...rowProps} />
{:else if col.slot == 6}<slot name="6" {row} {col} {index} {...rowProps} />
{:else if col.slot == 7}<slot name="7" {row} {col} {index} {...rowProps} />
{:else if col.slot == 8}<slot name="8" {row} {col} {index} {...rowProps} />
{:else if col.slot == 9}<slot name="9" {row} {col} {index} {...rowProps} />
{/if}
{/key}
{:else}
{row[col.fieldName] || ''}
{/if}
</td>
{/each}
</tr>
{/each}
</tr>
{/if}
{/each}
{#if emptyMessage && sortedRows.length == 0}
<tr>
@@ -223,6 +355,9 @@
background: var(--theme-bg-0);
}
tbody tr.selected {
background: var(--theme-bg-3);
}
table:focus tbody tr.selected {
background: var(--theme-bg-selected);
}
tbody tr.clickable:hover {
@@ -287,4 +422,9 @@
.empty-cell {
background-color: var(--theme-bg-1);
}
.groupcell {
background-color: var(--theme-bg-1);
cursor: pointer;
}
</style>