Files
dbgate/packages/web/src/modals/ColumnMapModal.svelte
2025-12-01 10:25:43 +01:00

186 lines
5.9 KiB
Svelte

<script lang="ts">
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import ColumnMapColumnDropdown from '../elements/ColumnMapColumnDropdown.svelte';
import Link from '../elements/Link.svelte';
import TableControl from '../elements/TableControl.svelte';
import CheckboxField from '../forms/CheckboxField.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import FormSubmit from '../forms/FormSubmit.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import _ from 'lodash';
import { _t } from '../translations';
export let header = _t('columnMapModal.configureColumns', { defaultMessage: 'Configure columns' });
export let onConfirm;
export let sourceTableInfo;
export let targetTableInfo;
export let initialValue;
function getResetValue() {
if (sourceTableInfo && !targetTableInfo) {
return sourceTableInfo.columns.map(x => ({
src: x.columnName,
dst: x.columnName,
skip: false,
}));
}
if (targetTableInfo && !sourceTableInfo) {
return targetTableInfo.columns.map(x => ({
src: x.columnName,
dst: x.columnName,
skip: false,
}));
}
if (sourceTableInfo && targetTableInfo) {
return sourceTableInfo.columns
.map(x => ({
src: x.columnName,
dst: targetTableInfo.columns.find(y => y.columnName == x.columnName)?.columnName,
skip: false,
}))
.filter(x => x.dst != null);
}
return [];
}
const resetValue = getResetValue();
function equalValues(v1, v2) {
if (!v1 || !v2) return false;
if (v1.length != v2.length) return false;
for (let i = 0; i < v1.length; i++) {
if (v1[i].src != v2[i].src || v1[i].dst != v2[i].dst || !!v1[i].skip != !!v2[i].skip) return false;
}
return true;
}
$: differentFromReset = !equalValues(value, resetValue);
let value = initialValue?.length > 0 ? initialValue : resetValue;
let validationError;
function validate() {
validationError = null;
if (!value) return;
if (value.length == 0) return;
if (value.some(x => !x.src || !x.dst)) {
validationError = _t('columnMapModal.sourceAndTargetColumnsMustBeDefined', { defaultMessage: 'Source and target columns must be defined' });
return;
}
const duplicates = _.chain(value.map(x => x.dst))
.countBy()
.pickBy(count => count > 1)
.keys()
.value();
if (duplicates.length > 0) {
validationError = _t('columnMapModal.targetColumnsMustBeUnique', { defaultMessage: 'Target columns must be unique, duplicates found: ' }) + duplicates.join(', ');
return;
}
}
$: {
value;
validate();
}
</script>
<FormProvider>
<ModalBase {...$$restProps}>
<div slot="header">{header}</div>
{#if resetValue.length == 0}
<div class="m-3">
{_t('columnMapModal.noColumnsDefined', { defaultMessage: 'When no columns are defined in this mapping, source row is copied to target without any modifications' })}
</div>
{/if}
<TableControl
columns={[
{ fieldName: 'use', header: _t('columnMapModal.use', { defaultMessage: 'Use' }), slot: 4 },
{ fieldName: 'src', header: _t('columnMapModal.sourceColumn', { defaultMessage: 'Source column' }), slot: 1 },
{ fieldName: 'dst', header: _t('columnMapModal.targetColumn', { defaultMessage: 'Target column' }), slot: 2 },
{ fieldName: 'actions', header: '', slot: 3 },
]}
rows={value || []}
emptyMessage={_t('columnMapModal.noTransformDefined', { defaultMessage: 'No transform defined' })}
>
<svelte:fragment slot="4" let:row let:index>
<CheckboxField
checked={!row['skip']}
on:change={e =>
(value = (value || []).map((x, i) => (i == index ? { ...x, skip: !e.target.checked, ignore: false } : x)))}
/>
</svelte:fragment>
<svelte:fragment slot="1" let:row let:index>
<ColumnMapColumnDropdown
value={row['src']}
onChange={column =>
(value = (value || []).map((x, i) => (i == index ? { ...x, src: column, ignore: false } : x)))}
tableInfo={sourceTableInfo}
/>
</svelte:fragment>
<svelte:fragment slot="2" let:row let:index>
<ColumnMapColumnDropdown
value={row['dst']}
onChange={column =>
(value = (value || []).map((x, i) => (i == index ? { ...x, dst: column, ignore: false } : x)))}
tableInfo={targetTableInfo}
/>
</svelte:fragment>
<svelte:fragment slot="3" let:index>
<Link
onClick={() => {
value = value.filter((x, i) => i != index);
}}>{_t('common.Remove', { defaultMessage: 'Remove' })}</Link
>
</svelte:fragment>
</TableControl>
{#if validationError}
<div class="error-result">
<FontIcon icon="img error" />
{validationError}
</div>
{/if}
<svelte:fragment slot="footer">
<FormSubmit
value="OK"
disabled={!!validationError}
on:click={() => {
closeCurrentModal();
onConfirm(!value || value.length == 0 || !differentFromReset ? null : value);
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton
type="button"
value={_t('columnMapModal.addColumn', { defaultMessage: 'Add column' })}
on:click={() => {
value = [...(value || []), {}];
}}
/>
<FormStyledButton
type="button"
value={_t('columnMapModal.reset', { defaultMessage: 'Reset' })}
disabled={!differentFromReset}
on:click={() => {
value = resetValue;
}}
/>
</svelte:fragment>
</ModalBase>
</FormProvider>
<style>
.error-result {
margin: 10px;
}
</style>