forked from tsai/budibase
21 changed files with 1031 additions and 74 deletions
@ -0,0 +1,178 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
Button, |
||||
|
Icon, |
||||
|
DrawerContent, |
||||
|
Layout, |
||||
|
Input, |
||||
|
Select, |
||||
|
Label, |
||||
|
Body, |
||||
|
} from "@budibase/bbui" |
||||
|
import { flip } from "svelte/animate" |
||||
|
import { dndzone } from "svelte-dnd-action" |
||||
|
import { generate } from "shortid" |
||||
|
|
||||
|
export let columns = [] |
||||
|
export let options = [] |
||||
|
|
||||
|
const flipDurationMs = 150 |
||||
|
let dragDisabled = true |
||||
|
|
||||
|
$: unselectedColumns = getUnselectedColumns(options, columns) |
||||
|
$: columns.forEach(column => { |
||||
|
if (!column.id) { |
||||
|
column.id = generate() |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
const getUnselectedColumns = (allColumns, selectedColumns) => { |
||||
|
let optionsObj = {} |
||||
|
allColumns.forEach(option => { |
||||
|
optionsObj[option] = true |
||||
|
}) |
||||
|
selectedColumns?.forEach(column => { |
||||
|
delete optionsObj[column.name] |
||||
|
}) |
||||
|
return Object.keys(optionsObj) |
||||
|
} |
||||
|
|
||||
|
const getRemainingColumnOptions = selectedColumn => { |
||||
|
if (!selectedColumn || unselectedColumns.includes(selectedColumn)) { |
||||
|
return unselectedColumns |
||||
|
} |
||||
|
return [selectedColumn, ...unselectedColumns] |
||||
|
} |
||||
|
|
||||
|
const addColumn = () => { |
||||
|
columns = [...columns, {}] |
||||
|
} |
||||
|
|
||||
|
const removeColumn = id => { |
||||
|
columns = columns.filter(column => column.id !== id) |
||||
|
} |
||||
|
|
||||
|
const updateColumnOrder = e => { |
||||
|
columns = e.detail.items |
||||
|
} |
||||
|
|
||||
|
const handleFinalize = e => { |
||||
|
updateColumnOrder(e) |
||||
|
dragDisabled = true |
||||
|
} |
||||
|
|
||||
|
const reset = () => { |
||||
|
columns = options.map(col => ({ |
||||
|
name: col, |
||||
|
displayName: col, |
||||
|
})) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<DrawerContent> |
||||
|
<div class="container"> |
||||
|
<Layout noPadding gap="S"> |
||||
|
{#if columns?.length} |
||||
|
<Layout noPadding gap="XS"> |
||||
|
<div class="column"> |
||||
|
<div /> |
||||
|
<Label size="L">Column</Label> |
||||
|
<Label size="L">Label</Label> |
||||
|
<div /> |
||||
|
</div> |
||||
|
<div |
||||
|
class="columns" |
||||
|
use:dndzone={{ |
||||
|
items: columns, |
||||
|
flipDurationMs, |
||||
|
dropTargetStyle: { outline: "none" }, |
||||
|
dragDisabled, |
||||
|
}} |
||||
|
on:finalize={handleFinalize} |
||||
|
on:consider={updateColumnOrder} |
||||
|
> |
||||
|
{#each columns as column (column.id)} |
||||
|
<div class="column" animate:flip={{ duration: flipDurationMs }}> |
||||
|
<div |
||||
|
class="handle" |
||||
|
aria-label="drag-handle" |
||||
|
style={dragDisabled ? "cursor: grab" : "cursor: grabbing"} |
||||
|
on:mousedown={() => (dragDisabled = false)} |
||||
|
> |
||||
|
<Icon name="DragHandle" size="XL" /> |
||||
|
</div> |
||||
|
<Select |
||||
|
bind:value={column.name} |
||||
|
placeholder="Column" |
||||
|
options={getRemainingColumnOptions(column.name)} |
||||
|
on:change={e => (column.displayName = e.detail)} |
||||
|
/> |
||||
|
<Input bind:value={column.displayName} placeholder="Label" /> |
||||
|
<Icon |
||||
|
name="Close" |
||||
|
hoverable |
||||
|
size="S" |
||||
|
on:click={() => removeColumn(column.id)} |
||||
|
disabled={columns.length === 1} |
||||
|
/> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</Layout> |
||||
|
{:else} |
||||
|
<div class="column"> |
||||
|
<div /> |
||||
|
<Body size="S">Add the first column to your table.</Body> |
||||
|
</div> |
||||
|
{/if} |
||||
|
<div class="columns"> |
||||
|
<div class="column"> |
||||
|
<div /> |
||||
|
<div class="buttons"> |
||||
|
<Button secondary icon="Add" on:click={addColumn}> |
||||
|
Add column |
||||
|
</Button> |
||||
|
<Button secondary quiet on:click={reset}>Reset columns</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Layout> |
||||
|
</div> |
||||
|
</DrawerContent> |
||||
|
|
||||
|
<style> |
||||
|
.container { |
||||
|
width: 100%; |
||||
|
max-width: 600px; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
.columns { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: flex-start; |
||||
|
align-items: stretch; |
||||
|
gap: var(--spacing-m); |
||||
|
} |
||||
|
.column { |
||||
|
gap: var(--spacing-l); |
||||
|
display: grid; |
||||
|
grid-template-columns: 20px 1fr 1fr 20px; |
||||
|
align-items: center; |
||||
|
border-radius: var(--border-radius-s); |
||||
|
transition: background-color ease-in-out 130ms; |
||||
|
} |
||||
|
.column:hover { |
||||
|
background-color: var(--spectrum-global-color-gray-100); |
||||
|
} |
||||
|
.handle { |
||||
|
display: grid; |
||||
|
place-items: center; |
||||
|
} |
||||
|
.buttons { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: flex-start; |
||||
|
align-items: center; |
||||
|
gap: var(--spacing-m); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,64 @@ |
|||||
|
<script> |
||||
|
import { Button, ActionButton, Drawer } from "@budibase/bbui" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
import ColumnDrawer from "./ColumnDrawer.svelte" |
||||
|
import { cloneDeep } from "lodash/fp" |
||||
|
import { |
||||
|
getDatasourceForProvider, |
||||
|
getSchemaForDatasource, |
||||
|
} from "builderStore/dataBinding" |
||||
|
import { currentAsset } from "builderStore" |
||||
|
|
||||
|
export let componentInstance |
||||
|
export let value = [] |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
|
||||
|
let drawer |
||||
|
let boundValue |
||||
|
|
||||
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance) |
||||
|
$: schema = getSchemaForDatasource($currentAsset, datasource).schema |
||||
|
$: options = Object.keys(schema || {}) |
||||
|
$: sanitisedValue = getValidColumns(value, options) |
||||
|
$: updateBoundValue(sanitisedValue) |
||||
|
|
||||
|
const updateBoundValue = value => { |
||||
|
boundValue = cloneDeep(value) |
||||
|
} |
||||
|
|
||||
|
const getValidColumns = (columns, options) => { |
||||
|
// If no columns then default to all columns |
||||
|
if (!Array.isArray(columns) || !columns.length) { |
||||
|
return options.map(col => ({ |
||||
|
name: col, |
||||
|
displayName: col, |
||||
|
})) |
||||
|
} |
||||
|
// We need to account for legacy configs which would just be an array |
||||
|
// of strings |
||||
|
if (typeof columns[0] === "string") { |
||||
|
columns = columns.map(col => ({ |
||||
|
name: col, |
||||
|
displayName: col, |
||||
|
})) |
||||
|
} |
||||
|
return columns.filter(column => { |
||||
|
return options.includes(column.name) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const save = () => { |
||||
|
dispatch("change", getValidColumns(boundValue, options)) |
||||
|
drawer.hide() |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<ActionButton on:click={drawer.show}>Configure columns</ActionButton> |
||||
|
<Drawer bind:this={drawer} title="Table Columns"> |
||||
|
<svelte:fragment slot="description"> |
||||
|
Configure the columns in your table. |
||||
|
</svelte:fragment> |
||||
|
<Button cta slot="buttons" on:click={save}>Save</Button> |
||||
|
<ColumnDrawer slot="body" bind:columns={boundValue} {options} /> |
||||
|
</Drawer> |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue