Files
windmill/frontend/src/lib/components/wizards/DBExplorerWizard.svelte
T

387 lines
11 KiB
Svelte

<script lang="ts">
import { Badge, Popup, type AlertType } from '../common'
import Toggle from '$lib/components/Toggle.svelte'
import SimpleEditor from '../SimpleEditor.svelte'
import Label from '../Label.svelte'
import Section from '../Section.svelte'
import Tooltip from '../Tooltip.svelte'
import Button from '../common/button/Button.svelte'
import { twMerge } from 'tailwind-merge'
import { ColumnIdentity, type ColumnDef } from '../apps/components/display/dbtable/utils'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
export let value: ColumnDef | undefined
import Alert from '../common/alert/Alert.svelte'
const presets = [
{
label: 'None',
value: null
},
{
label: 'Currency CHF',
value: 'value + " CHF"'
},
{
label: 'Currency USD',
value: '"$ " + value'
},
{
label: 'Date',
value: 'new Date(value).toLocaleDateString()'
},
{
label: 'Percentage',
value: 'value + " %"'
},
{
label: 'Currency GBP',
value: 'value + " £"'
},
{
label: 'Currency EUR',
value: 'value + " €"'
},
{
label: 'Currency JPY',
value: 'value + " ¥"'
},
{
label: 'Decimal places (2)',
value: 'parseFloat(value).toFixed(2)'
},
{
label: 'Uppercase',
value: 'value.toUpperCase()'
},
{
label: 'Lowercase',
value: 'value.toLowerCase()'
},
{
label: 'Boolean (True/False)',
value: 'value ? "True" : "False"'
},
{
label: 'Object',
value: 'JSON.stringify(value, null, 2)'
}
]
let renderCount = 0
function computeWarning(columnMetadata, value) {
if (columnMetadata?.isnullable === 'NO' && !columnMetadata?.defaultvalue) {
if ([ColumnIdentity.Always, ColumnIdentity.ByDefault].includes(columnMetadata?.isidentity)) {
return {
type: 'info' as AlertType,
title: 'Value will be generated',
message: 'The column is an identity column. The value will be generated by the database.'
}
}
if (value?.hideInsert) {
return {
type: 'warning' as AlertType,
title: 'No default value',
message:
"The column is not nullable and doesn't have a default value. A default value is required."
}
}
}
if (columnMetadata?.defaultvalue !== null) {
return {
type: 'info' as AlertType,
title: 'Default value',
message: `${
value?.hideInsert ? '' : 'You may want to hide this field from insert. '
}The column has a default value defined in the database. The default value is: ${
value?.defaultvalue
}`
}
}
if (columnMetadata?.isnullable === 'YES') {
return {
type: 'info' as AlertType,
title: 'Default value',
message: `${
value?.hideInsert ? '' : 'You may want to hide this field from insert. '
}The column can be null. If no value is provided, the default value will be null.`
}
}
return null
}
$: warning = computeWarning(value, value)
</script>
<Popup
floatingConfig={{
strategy: 'fixed',
placement: 'left-start',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4 max-h-[512px] overflow-y-auto"
>
<svelte:fragment slot="button">
<slot name="trigger" />
</svelte:fragment>
{#if value}
<div class="flex flex-col w-96 p-2 gap-4">
<Section label="Column settings">
<svelte:fragment slot="header">
<Badge color="blue">
{value.field}
</Badge>
</svelte:fragment>
<Label label="Skip for select and update">
<svelte:fragment slot="header">
<Tooltip>
By default, all columns are included in the select and update queries. If you want to
exclude a column from the select and update queries, you can set this property to
true.
</Tooltip>
</svelte:fragment>
<svelte:fragment slot="action">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
bind:checked={value.ignored}
size="xs"
disabled={value?.isprimarykey}
/>
</svelte:fragment>
{#if value?.isprimarykey}
<Alert type="warning" size="xs" title="Primary key" class="my-1">
You cannot skip a primary key.
</Alert>
{/if}
</Label>
<Label label="Hide from insert">
<svelte:fragment slot="header">
<Tooltip>
By default, all columns are used to generate the submit form. If you want to exclude a
column from the submit form, you can set this property to true. If the column is not
nullable or doesn't have a default value, a default value will be required.
</Tooltip>
</svelte:fragment>
<svelte:fragment slot="action">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
bind:checked={value.hideInsert}
size="xs"
/>
</svelte:fragment>
</Label>
{#if warning}
<Alert type={warning.type} size="xs" title={warning.title} class="my-2">
{warning.message}
</Alert>
{/if}
{#if value?.defaultvalue !== null && value?.hideInsert}
<Toggle
bind:checked={value.overrideDefaultValue}
size="xs"
options={{
right: `Override default value: ${value?.defaultvalue}`
}}
on:change={() => {
if (!value || !value.overrideDefaultValue) {
if (value) {
value.defaultValueNull = false
value.defaultUserValue = undefined
}
}
}}
/>
{/if}
<Label label="Default input">
<svelte:fragment slot="header">
<Tooltip>
By default, all columns are used to generate the submit form. If you want to exclude a
column from the submit form, you can set this property to true. If the column is not
nullable or doesn't have a default value, a default value will be required.
</Tooltip>
</svelte:fragment>
{#if value?.datatype}
{@const type = value?.datatype}
<div class="flex flex-row items-center gap-2">
<Badge color="dark-gray">
Type:
{type}
</Badge>
{#if value?.isnullable == 'YES' && value.hideInsert}
<Toggle
bind:checked={value.defaultValueNull}
size="xs"
options={{
right: 'Set to null'
}}
disabled={value.hideInsert &&
value?.defaultvalue !== null &&
!value?.overrideDefaultValue}
on:change={() => {
if (value?.defaultValueNull && value) {
value.defaultUserValue = null
}
}}
/>
{/if}
</div>
<input
type="text"
placeholder="Default value"
class="mt-2"
bind:value={value.defaultUserValue}
disabled={value.defaultValueNull ||
(value.hideInsert && value?.defaultvalue !== null && !value?.overrideDefaultValue)}
/>
{/if}
</Label>
</Section>
<Section label="AG Grid configuration">
<div
class={twMerge('flex flex-col gap-4', value.ignored ? 'opacity-50 cursor-none ' : '')}
on:pointerdown={(e) => {
if (value?.ignored) {
e?.stopPropagation()
}
}}
>
<Label label="Header name">
<input type="text" placeholder="Header name" bind:value={value.headerName} />
</Label>
<Label label="Editable value">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
options={{ right: 'Editable' }}
bind:checked={value.editable}
size="xs"
/>
</Label>
<Label label="Min width (px)">
<input type="number" placeholder="width" bind:value={value.minWidth} />
</Label>
<Label label="Flex">
<svelte:fragment slot="header">
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/column-sizing/#column-flex"
>
It's often required that one or more columns fill the entire available space in the
grid. For this scenario, it is possible to use the flex config. Some columns could
be set with a regular width config, while other columns would have a flex config.
Flex sizing works by dividing the remaining space in the grid among all flex columns
in proportion to their flex value. For example, suppose the grid has a total width
of 450px and it has three columns: the first with width: 150; the second with flex:
1; and third with flex: 2. The first column will be 150px wide, leaving 300px
remaining. The column with flex: 2 has twice the size with flex: 1. So final sizes
will be: 150px, 100px, 200px.
</Tooltip>
</svelte:fragment>
<input type="range" step="1" bind:value={value.flex} min={1} max={12} />
<div class="text-xs">{value.flex}</div>
</Label>
<Label label="Hide">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
options={{ right: 'Hide' }}
bind:checked={value.hide}
size="xs"
/>
</Label>
<Label label="Value formatter">
<svelte:fragment slot="header">
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/value-formatters/"
>
Value formatters allow you to format values for display. This is useful when data is
one type (e.g. numeric) but needs to be converted for human reading (e.g. putting in
currency symbols and number formatting).
</Tooltip>
</svelte:fragment>
<svelte:fragment slot="action">
<Button
size="xs"
color="light"
variant="border"
on:click={() => {
// @ts-ignore
value.valueFormatter = null
renderCount++
}}
>
Clear
</Button>
</svelte:fragment>
</Label>
<div>
{#key renderCount}
<div class="flex flex-col gap-4">
<div class="relative">
{#if !presets.find((preset) => preset.value === value?.valueFormatter)}
<div
class="z-50 absolute bg-opacity-50 bg-surface top-0 left-0 bottom-0 right-0"
/>
{/if}
<div class="text-xs font-semibold">Presets</div>
<select
bind:value={value.valueFormatter}
on:change={() => {
renderCount++
}}
placeholder="Code"
>
{#each presets as preset}
<option value={preset.value}>{preset.label}</option>
{/each}
</select>
</div>
<SimpleEditor
extraLib={'declare const value: any'}
autoHeight
lang="javascript"
bind:code={value.valueFormatter}
/>
<div class="text-xs text-secondary -mt-4">Use `value` in the formatter</div>
</div>
{/key}
</div>
<Label label="Sort">
<select bind:value={value.sort}>
<option value={null}>None</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</Label>
</div>
</Section>
</div>
{/if}
</Popup>