Merge branch 'develop' into plugin-improvements
This commit is contained in:
commit
b74fc5292c
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.8.29-alpha.3",
|
||||
"version": "2.8.29-alpha.6",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -85,7 +85,8 @@
|
|||
"dayjs": "^1.10.4",
|
||||
"easymde": "^2.16.1",
|
||||
"svelte-flatpickr": "3.2.3",
|
||||
"svelte-portal": "^1.0.0"
|
||||
"svelte-portal": "^1.0.0",
|
||||
"svelte-dnd-action": "^0.9.8"
|
||||
},
|
||||
"resolutions": {
|
||||
"loader-utils": "1.4.1"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
//import { createEventDispatcher } from "svelte"
|
||||
import "@spectrum-css/popover/dist/index-vars.css"
|
||||
import clickOutside from "../Actions/click_outside"
|
||||
import { fly } from "svelte/transition"
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
<script>
|
||||
import { flip } from "svelte/animate"
|
||||
import { dndzone } from "svelte-dnd-action"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
import Popover from "../Popover/Popover.svelte"
|
||||
import { onMount } from "svelte"
|
||||
const flipDurationMs = 150
|
||||
|
||||
export let constraints
|
||||
export let optionColors = {}
|
||||
let options = []
|
||||
|
||||
let colorPopovers = []
|
||||
let anchors = []
|
||||
|
||||
let colorsArray = [
|
||||
"hsla(0, 90%, 75%, 0.3)",
|
||||
"hsla(50, 80%, 75%, 0.3)",
|
||||
"hsla(120, 90%, 75%, 0.3)",
|
||||
"hsla(200, 90%, 75%, 0.3)",
|
||||
"hsla(240, 90%, 75%, 0.3)",
|
||||
"hsla(320, 90%, 75%, 0.3)",
|
||||
]
|
||||
$: {
|
||||
if (constraints.inclusion.length) {
|
||||
options = constraints.inclusion.map(value => ({
|
||||
name: value,
|
||||
id: Math.random(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
const removeInput = idx => {
|
||||
delete optionColors[options[idx].name]
|
||||
constraints.inclusion = constraints.inclusion.filter((e, i) => i !== idx)
|
||||
options = options.filter((e, i) => i !== idx)
|
||||
colorPopovers.pop(undefined)
|
||||
anchors.pop(undefined)
|
||||
}
|
||||
|
||||
const addNewInput = () => {
|
||||
options = [
|
||||
...options,
|
||||
{ name: `Option ${constraints.inclusion.length + 1}`, id: Math.random() },
|
||||
]
|
||||
constraints.inclusion = [
|
||||
...constraints.inclusion,
|
||||
`Option ${constraints.inclusion.length + 1}`,
|
||||
]
|
||||
|
||||
colorPopovers.push(undefined)
|
||||
anchors.push(undefined)
|
||||
}
|
||||
|
||||
const handleDndConsider = e => {
|
||||
options = e.detail.items
|
||||
}
|
||||
const handleDndFinalize = e => {
|
||||
options = e.detail.items
|
||||
constraints.inclusion = options.map(option => option.name)
|
||||
}
|
||||
|
||||
const handleColorChange = (optionName, color, idx) => {
|
||||
optionColors[optionName] = color
|
||||
colorPopovers[idx].hide()
|
||||
}
|
||||
|
||||
const handleNameChange = (optionName, idx, value) => {
|
||||
constraints.inclusion[idx] = value
|
||||
options[idx].name = value
|
||||
optionColors[value] = optionColors[optionName]
|
||||
delete optionColors[optionName]
|
||||
}
|
||||
|
||||
const openColorPickerPopover = (optionIdx, target) => {
|
||||
colorPopovers[optionIdx].show()
|
||||
anchors[optionIdx] = target
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Initialize anchor arrays on mount, assuming 'options' is already populated
|
||||
colorPopovers = constraints.inclusion.map(() => undefined)
|
||||
anchors = constraints.inclusion.map(() => undefined)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="actions"
|
||||
use:dndzone={{
|
||||
items: options,
|
||||
flipDurationMs,
|
||||
dropTargetStyle: { outline: "none" },
|
||||
}}
|
||||
on:consider={handleDndConsider}
|
||||
on:finalize={handleDndFinalize}
|
||||
>
|
||||
{#each options as option, idx (option.id)}
|
||||
<div
|
||||
class="no-border action-container"
|
||||
animate:flip={{ duration: flipDurationMs }}
|
||||
>
|
||||
<div class="child drag-handle-spacing">
|
||||
<Icon name="DragHandle" size="L" />
|
||||
</div>
|
||||
<div class="child color-picker">
|
||||
<div
|
||||
id="color-picker"
|
||||
bind:this={anchors[idx]}
|
||||
style="--color:{optionColors?.[option.name] ||
|
||||
'hsla(0, 1%, 50%, 0.3)'}"
|
||||
class="circle"
|
||||
on:click={e => openColorPickerPopover(idx, e.target)}
|
||||
>
|
||||
<Popover
|
||||
bind:this={colorPopovers[idx]}
|
||||
anchor={anchors[idx]}
|
||||
align="left"
|
||||
offset={0}
|
||||
style=""
|
||||
popoverTarget={document.getElementById(`color-picker`)}
|
||||
animate={false}
|
||||
>
|
||||
<div class="colors">
|
||||
{#each colorsArray as color}
|
||||
<div
|
||||
on:click={() => handleColorChange(option.name, color, idx)}
|
||||
style="--color:{color};"
|
||||
class="circle circle-hover"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<div class="child">
|
||||
<input
|
||||
class="input-field"
|
||||
type="text"
|
||||
on:change={e => handleNameChange(option.name, idx, e.target.value)}
|
||||
value={option.name}
|
||||
placeholder="Option name"
|
||||
/>
|
||||
</div>
|
||||
<div class="child">
|
||||
<Icon name="Close" hoverable size="S" on:click={removeInput(idx)} />
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div on:click={addNewInput} class="add-option">
|
||||
<Icon hoverable name="Add" />
|
||||
<div>Add option</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.action-container {
|
||||
background-color: var(--spectrum-alias-background-color-primary);
|
||||
border-radius: 0px;
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
transition: background-color 130ms ease-in-out, color 130ms ease-in-out,
|
||||
border-color 130ms ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.no-border {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.action-container:last-child {
|
||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300) !important;
|
||||
}
|
||||
|
||||
.child {
|
||||
height: 30px;
|
||||
}
|
||||
.child:hover,
|
||||
.child:focus {
|
||||
background: var(--spectrum-global-color-gray-200);
|
||||
}
|
||||
.add-option {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: var(--spacing-m);
|
||||
gap: var(--spacing-m);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
width: 100%;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.child input[type="text"] {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.input-field:hover,
|
||||
.input-field:focus {
|
||||
background: var(--spectrum-global-color-gray-200);
|
||||
}
|
||||
|
||||
.action-container > :nth-child(1) {
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.action-container > :nth-child(2) {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-container > :nth-child(3) {
|
||||
flex-grow: 4;
|
||||
display: flex;
|
||||
}
|
||||
.action-container > :nth-child(4) {
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.circle {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-color: var(--color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.circle-hover:hover {
|
||||
border: 1px solid var(--spectrum-global-color-blue-400);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.colors {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: var(--spacing-xl);
|
||||
justify-items: center;
|
||||
margin: var(--spacing-m);
|
||||
}
|
||||
</style>
|
|
@ -21,6 +21,7 @@
|
|||
export let offset = 5
|
||||
export let customHeight
|
||||
export let animate = true
|
||||
export let customZindex
|
||||
|
||||
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
||||
|
||||
|
@ -77,8 +78,9 @@
|
|||
}}
|
||||
on:keydown={handleEscape}
|
||||
class="spectrum-Popover is-open"
|
||||
class:customZindex
|
||||
role="presentation"
|
||||
style="height: {customHeight}"
|
||||
style="height: {customHeight}; --customZindex: {customZindex};"
|
||||
transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }}
|
||||
>
|
||||
<slot />
|
||||
|
@ -92,4 +94,8 @@
|
|||
border-color: var(--spectrum-global-color-gray-300);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.customZindex {
|
||||
z-index: var(--customZindex) !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -84,7 +84,7 @@ export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte
|
|||
export { default as Slider } from "./Form/Slider.svelte"
|
||||
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
||||
export { default as File } from "./Form/File.svelte"
|
||||
|
||||
export { default as OptionSelectDnD } from "./OptionSelectDnD/OptionSelectDnD.svelte"
|
||||
// Renderers
|
||||
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
||||
export { default as CodeRenderer } from "./Table/CodeRenderer.svelte"
|
||||
|
|
|
@ -64,6 +64,13 @@
|
|||
<svelte:fragment slot="filter">
|
||||
<GridFilterButton />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="edit-column">
|
||||
<GridEditColumnModal />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="add-column">
|
||||
<GridAddColumnModal />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="controls">
|
||||
{#if isInternal}
|
||||
<GridCreateViewButton />
|
||||
|
@ -77,9 +84,8 @@
|
|||
{:else}
|
||||
<GridImportButton />
|
||||
{/if}
|
||||
|
||||
<GridExportButton />
|
||||
<GridAddColumnModal />
|
||||
<GridEditColumnModal />
|
||||
{#if isUsersTable}
|
||||
<GridEditUserModal />
|
||||
{:else}
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
Toggle,
|
||||
RadioGroup,
|
||||
DatePicker,
|
||||
ModalContent,
|
||||
Context,
|
||||
Modal,
|
||||
notifications,
|
||||
OptionSelectDnD,
|
||||
Layout,
|
||||
} from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { createEventDispatcher, getContext } from "svelte"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { tables, datasources } from "stores/backend"
|
||||
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
|
||||
|
@ -26,12 +26,10 @@
|
|||
SWITCHABLE_TYPES,
|
||||
} from "constants/backend"
|
||||
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
||||
import ValuesList from "components/common/ValuesList.svelte"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { truncate } from "lodash"
|
||||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
||||
import { getBindings } from "components/backend/DataTable/formula"
|
||||
import { getContext } from "svelte"
|
||||
import JSONSchemaModal from "./JSONSchemaModal.svelte"
|
||||
import { ValidColumnNameRegex } from "@budibase/shared-core"
|
||||
|
||||
|
@ -45,11 +43,11 @@
|
|||
|
||||
const dispatch = createEventDispatcher()
|
||||
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
|
||||
const { hide } = getContext(Context.Modal)
|
||||
let fieldDefinitions = cloneDeep(FIELDS)
|
||||
const { dispatch: gridDispatch } = getContext("grid")
|
||||
|
||||
export let field
|
||||
|
||||
let fieldDefinitions = cloneDeep(FIELDS)
|
||||
let originalName
|
||||
let linkEditDisabled
|
||||
let primaryDisplay
|
||||
|
@ -61,11 +59,10 @@
|
|||
let savingColumn
|
||||
let deleteColName
|
||||
let jsonSchemaModal
|
||||
|
||||
let allowedTypes = []
|
||||
let editableColumn = {
|
||||
type: "string",
|
||||
constraints: fieldDefinitions.STRING.constraints,
|
||||
|
||||
// Initial value for column name in other table for linked records
|
||||
fieldName: $tables.selected.name,
|
||||
}
|
||||
|
@ -83,7 +80,23 @@
|
|||
primaryDisplay =
|
||||
$tables.selected.primaryDisplay == null ||
|
||||
$tables.selected.primaryDisplay === editableColumn.name
|
||||
} else if (!savingColumn) {
|
||||
let highestNumber = 0
|
||||
Object.keys(table.schema).forEach(columnName => {
|
||||
const columnNumber = extractColumnNumber(columnName)
|
||||
if (columnNumber > highestNumber) {
|
||||
highestNumber = columnNumber
|
||||
}
|
||||
return highestNumber
|
||||
})
|
||||
|
||||
if (highestNumber >= 1) {
|
||||
editableColumn.name = `Column 0${highestNumber + 1}`
|
||||
} else {
|
||||
editableColumn.name = "Column 01"
|
||||
}
|
||||
}
|
||||
allowedTypes = getAllowedTypes()
|
||||
}
|
||||
|
||||
$: initialiseField(field, savingColumn)
|
||||
|
@ -182,6 +195,8 @@
|
|||
indexes,
|
||||
})
|
||||
dispatch("updatecolumns")
|
||||
gridDispatch("close-edit-column")
|
||||
|
||||
if (
|
||||
saveColumn.type === LINK_TYPE &&
|
||||
saveColumn.relationshipType === RelationshipType.MANY_TO_MANY
|
||||
|
@ -203,6 +218,7 @@
|
|||
|
||||
function cancelEdit() {
|
||||
editableColumn.name = originalName
|
||||
gridDispatch("close-edit-column")
|
||||
}
|
||||
|
||||
async function deleteColumn() {
|
||||
|
@ -214,8 +230,8 @@
|
|||
await tables.deleteField(editableColumn)
|
||||
notifications.success(`Column ${editableColumn.name} deleted`)
|
||||
confirmDeleteDialog.hide()
|
||||
hide()
|
||||
dispatch("updatecolumns")
|
||||
gridDispatch("close-edit-column")
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error(`Error deleting column: ${error.message}`)
|
||||
|
@ -251,14 +267,6 @@
|
|||
required = req
|
||||
}
|
||||
|
||||
function onChangePrimaryDisplay(e) {
|
||||
const isPrimary = e.detail
|
||||
// primary display is always required
|
||||
if (isPrimary) {
|
||||
editableColumn.constraints.presence = { allowEmpty: false }
|
||||
}
|
||||
}
|
||||
|
||||
function openJsonSchemaEditor() {
|
||||
jsonSchemaModal.show()
|
||||
}
|
||||
|
@ -272,6 +280,11 @@
|
|||
deleteColName = ""
|
||||
}
|
||||
|
||||
function extractColumnNumber(columnName) {
|
||||
const match = columnName.match(/Column (\d+)/)
|
||||
return match ? parseInt(match[1]) : 0
|
||||
}
|
||||
|
||||
function getRelationshipOptions(field) {
|
||||
if (!field || !field.tableId) {
|
||||
return null
|
||||
|
@ -402,15 +415,8 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
title={originalName ? "Edit Column" : "Create Column"}
|
||||
confirmText="Save Column"
|
||||
onConfirm={saveColumn}
|
||||
onCancel={cancelEdit}
|
||||
disabled={invalid}
|
||||
>
|
||||
<Layout noPadding gap="S">
|
||||
<Input
|
||||
label="Name"
|
||||
bind:value={editableColumn.name}
|
||||
disabled={uneditable ||
|
||||
(linkEditDisabled && editableColumn.type === LINK_TYPE)}
|
||||
|
@ -419,12 +425,12 @@
|
|||
|
||||
<Select
|
||||
disabled={!typeEnabled}
|
||||
label="Type"
|
||||
bind:value={editableColumn.type}
|
||||
on:change={handleTypeChange}
|
||||
options={getAllowedTypes()}
|
||||
options={allowedTypes}
|
||||
getOptionLabel={field => field.name}
|
||||
getOptionValue={field => field.type}
|
||||
getOptionIcon={field => field.icon}
|
||||
isOptionEnabled={option => {
|
||||
if (option.type == AUTO_TYPE) {
|
||||
return availableAutoColumnKeys?.length > 0
|
||||
|
@ -433,28 +439,6 @@
|
|||
}}
|
||||
/>
|
||||
|
||||
{#if canBeRequired || canBeDisplay}
|
||||
<div>
|
||||
{#if canBeRequired}
|
||||
<Toggle
|
||||
value={required}
|
||||
on:change={onChangeRequired}
|
||||
disabled={primaryDisplay}
|
||||
thin
|
||||
text="Required"
|
||||
/>
|
||||
{/if}
|
||||
{#if canBeDisplay}
|
||||
<Toggle
|
||||
bind:value={primaryDisplay}
|
||||
on:change={onChangePrimaryDisplay}
|
||||
thin
|
||||
text="Use as table display column"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if editableColumn.type === "string"}
|
||||
<Input
|
||||
type="number"
|
||||
|
@ -462,9 +446,9 @@
|
|||
bind:value={editableColumn.constraints.length.maximum}
|
||||
/>
|
||||
{:else if editableColumn.type === "options"}
|
||||
<ValuesList
|
||||
label="Options (one per line)"
|
||||
bind:values={editableColumn.constraints.inclusion}
|
||||
<OptionSelectDnD
|
||||
bind:constraints={editableColumn.constraints}
|
||||
bind:optionColors={editableColumn.optionColors}
|
||||
/>
|
||||
{:else if editableColumn.type === "longform"}
|
||||
<div>
|
||||
|
@ -480,19 +464,28 @@
|
|||
/>
|
||||
</div>
|
||||
{:else if editableColumn.type === "array"}
|
||||
<ValuesList
|
||||
label="Options (one per line)"
|
||||
bind:values={editableColumn.constraints.inclusion}
|
||||
<OptionSelectDnD
|
||||
bind:constraints={editableColumn.constraints}
|
||||
bind:optionColors={editableColumn.optionColors}
|
||||
/>
|
||||
{:else if editableColumn.type === "datetime" && !editableColumn.autocolumn}
|
||||
<DatePicker
|
||||
label="Earliest"
|
||||
bind:value={editableColumn.constraints.datetime.earliest}
|
||||
/>
|
||||
<DatePicker
|
||||
label="Latest"
|
||||
bind:value={editableColumn.constraints.datetime.latest}
|
||||
/>
|
||||
<div class="split-label">
|
||||
<div class="label-length">
|
||||
<Label size="M">Earliest</Label>
|
||||
</div>
|
||||
<div class="input-length">
|
||||
<DatePicker bind:value={editableColumn.constraints.datetime.earliest} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="split-label">
|
||||
<div class="label-length">
|
||||
<Label size="M">Latest</Label>
|
||||
</div>
|
||||
<div class="input-length">
|
||||
<DatePicker bind:value={editableColumn.constraints.datetime.latest} />
|
||||
</div>
|
||||
</div>
|
||||
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
|
||||
<div>
|
||||
<Label
|
||||
|
@ -509,16 +502,30 @@
|
|||
</div>
|
||||
{/if}
|
||||
{:else if editableColumn.type === "number" && !editableColumn.autocolumn}
|
||||
<Input
|
||||
type="number"
|
||||
label="Min Value"
|
||||
bind:value={editableColumn.constraints.numericality.greaterThanOrEqualTo}
|
||||
/>
|
||||
<Input
|
||||
type="number"
|
||||
label="Max Value"
|
||||
bind:value={editableColumn.constraints.numericality.lessThanOrEqualTo}
|
||||
/>
|
||||
<div class="split-label">
|
||||
<div class="label-length">
|
||||
<Label size="M">Max Value</Label>
|
||||
</div>
|
||||
<div class="input-length">
|
||||
<Input
|
||||
type="number"
|
||||
bind:value={editableColumn.constraints.numericality
|
||||
.greaterThanOrEqualTo}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="split-label">
|
||||
<div class="label-length">
|
||||
<Label size="M">Max Value</Label>
|
||||
</div>
|
||||
<div class="input-length">
|
||||
<Input
|
||||
type="number"
|
||||
bind:value={editableColumn.constraints.numericality.lessThanOrEqualTo}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if editableColumn.type === "link"}
|
||||
<Select
|
||||
label="Table"
|
||||
|
@ -547,32 +554,44 @@
|
|||
/>
|
||||
{:else if editableColumn.type === FORMULA_TYPE}
|
||||
{#if !table.sql}
|
||||
<Select
|
||||
label="Formula type"
|
||||
bind:value={editableColumn.formulaType}
|
||||
options={[
|
||||
{ label: "Dynamic", value: "dynamic" },
|
||||
{ label: "Static", value: "static" },
|
||||
]}
|
||||
getOptionLabel={option => option.label}
|
||||
getOptionValue={option => option.value}
|
||||
tooltip="Dynamic formula are calculated when retrieved, but cannot be filtered or sorted by,
|
||||
<div class="split-label">
|
||||
<div class="label-length">
|
||||
<Label size="M">Formula Type</Label>
|
||||
</div>
|
||||
<div class="input-length">
|
||||
<Select
|
||||
bind:value={editableColumn.formulaType}
|
||||
options={[
|
||||
{ label: "Dynamic", value: "dynamic" },
|
||||
{ label: "Static", value: "static" },
|
||||
]}
|
||||
getOptionLabel={option => option.label}
|
||||
getOptionValue={option => option.value}
|
||||
tooltip="Dynamic formula are calculated when retrieved, but cannot be filtered or sorted by,
|
||||
while static formula are calculated when the row is saved."
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<ModalBindableInput
|
||||
title="Formula"
|
||||
label="Formula"
|
||||
value={editableColumn.formula}
|
||||
on:change={e => {
|
||||
editableColumn = {
|
||||
...editableColumn,
|
||||
formula: e.detail,
|
||||
}
|
||||
}}
|
||||
bindings={getBindings({ table })}
|
||||
allowJS
|
||||
/>
|
||||
<div class="split-label">
|
||||
<div class="label-length">
|
||||
<Label size="M">Formula</Label>
|
||||
</div>
|
||||
<div class="input-length">
|
||||
<ModalBindableInput
|
||||
title="Formula"
|
||||
value={editableColumn.formula}
|
||||
on:change={e => {
|
||||
editableColumn = {
|
||||
...editableColumn,
|
||||
formula: e.detail,
|
||||
}
|
||||
}}
|
||||
bindings={getBindings({ table })}
|
||||
allowJS
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if editableColumn.type === JSON_TYPE}
|
||||
<Button primary text on:click={openJsonSchemaEditor}
|
||||
>Open schema editor</Button
|
||||
|
@ -591,12 +610,28 @@
|
|||
/>
|
||||
{/if}
|
||||
|
||||
<div slot="footer">
|
||||
{#if !uneditable && originalName != null}
|
||||
<Button warning text on:click={confirmDelete}>Delete</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</ModalContent>
|
||||
{#if canBeRequired || canBeDisplay}
|
||||
<div>
|
||||
{#if canBeRequired}
|
||||
<Toggle
|
||||
value={required}
|
||||
on:change={onChangeRequired}
|
||||
disabled={primaryDisplay}
|
||||
thin
|
||||
text="Required"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Layout>
|
||||
|
||||
<div class="action-buttons">
|
||||
{#if !uneditable && originalName != null}
|
||||
<Button quiet warning text on:click={confirmDelete}>Delete</Button>
|
||||
{/if}
|
||||
<Button secondary newStyles on:click={cancelEdit}>Cancel</Button>
|
||||
<Button disabled={invalid} newStyles cta on:click={saveColumn}>Save</Button>
|
||||
</div>
|
||||
<Modal bind:this={jsonSchemaModal}>
|
||||
<JSONSchemaModal
|
||||
schema={editableColumn.schema}
|
||||
|
@ -607,6 +642,7 @@
|
|||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Column"
|
||||
|
@ -622,3 +658,24 @@
|
|||
</p>
|
||||
<Input bind:value={deleteColName} placeholder={originalName} />
|
||||
</ConfirmDialog>
|
||||
|
||||
<style>
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--spacing-s);
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
.split-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label-length {
|
||||
flex-basis: 40%;
|
||||
}
|
||||
|
||||
.input-length {
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
<script>
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { Modal } from "@budibase/bbui"
|
||||
import { getContext } from "svelte"
|
||||
import CreateEditColumn from "components/backend/DataTable/modals/CreateEditColumn.svelte"
|
||||
|
||||
const { rows, subscribe } = getContext("grid")
|
||||
|
||||
let modal
|
||||
|
||||
onMount(() => subscribe("add-column", modal.show))
|
||||
const { rows } = getContext("grid")
|
||||
</script>
|
||||
|
||||
<Modal bind:this={modal}>
|
||||
<CreateEditColumn on:updatecolumns={rows.actions.refreshTableDefinition} />
|
||||
</Modal>
|
||||
<CreateEditColumn on:updatecolumns={rows.actions.refreshTableDefinition} />
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
<script>
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { Modal } from "@budibase/bbui"
|
||||
import CreateEditColumn from "../CreateEditColumn.svelte"
|
||||
|
||||
const { rows, subscribe } = getContext("grid")
|
||||
|
||||
let editableColumn
|
||||
let editColumnModal
|
||||
|
||||
const editColumn = column => {
|
||||
editableColumn = column
|
||||
editColumnModal.show()
|
||||
}
|
||||
|
||||
onMount(() => subscribe("edit-column", editColumn))
|
||||
</script>
|
||||
|
||||
<Modal bind:this={editColumnModal}>
|
||||
<CreateEditColumn
|
||||
field={editableColumn}
|
||||
on:updatecolumns={rows.actions.refreshData}
|
||||
/>
|
||||
</Modal>
|
||||
<CreateEditColumn
|
||||
field={editableColumn}
|
||||
on:updatecolumns={rows.actions.refreshData}
|
||||
/>
|
||||
|
|
|
@ -2,6 +2,7 @@ export const FIELDS = {
|
|||
STRING: {
|
||||
name: "Text",
|
||||
type: "string",
|
||||
icon: "Text",
|
||||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
|
@ -11,6 +12,7 @@ export const FIELDS = {
|
|||
BARCODEQR: {
|
||||
name: "Barcode/QR",
|
||||
type: "barcodeqr",
|
||||
icon: "Camera",
|
||||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
|
@ -20,6 +22,7 @@ export const FIELDS = {
|
|||
LONGFORM: {
|
||||
name: "Long Form Text",
|
||||
type: "longform",
|
||||
icon: "TextAlignLeft",
|
||||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
|
@ -29,6 +32,7 @@ export const FIELDS = {
|
|||
OPTIONS: {
|
||||
name: "Options",
|
||||
type: "options",
|
||||
icon: "Dropdown",
|
||||
constraints: {
|
||||
type: "string",
|
||||
presence: false,
|
||||
|
@ -38,6 +42,7 @@ export const FIELDS = {
|
|||
ARRAY: {
|
||||
name: "Multi-select",
|
||||
type: "array",
|
||||
icon: "Duplicate",
|
||||
constraints: {
|
||||
type: "array",
|
||||
presence: false,
|
||||
|
@ -47,6 +52,7 @@ export const FIELDS = {
|
|||
NUMBER: {
|
||||
name: "Number",
|
||||
type: "number",
|
||||
icon: "123",
|
||||
constraints: {
|
||||
type: "number",
|
||||
presence: false,
|
||||
|
@ -56,10 +62,12 @@ export const FIELDS = {
|
|||
BIGINT: {
|
||||
name: "BigInt",
|
||||
type: "bigint",
|
||||
icon: "TagBold",
|
||||
},
|
||||
BOOLEAN: {
|
||||
name: "Boolean",
|
||||
type: "boolean",
|
||||
icon: "Boolean",
|
||||
constraints: {
|
||||
type: "boolean",
|
||||
presence: false,
|
||||
|
@ -68,6 +76,7 @@ export const FIELDS = {
|
|||
DATETIME: {
|
||||
name: "Date/Time",
|
||||
type: "datetime",
|
||||
icon: "Calendar",
|
||||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
|
@ -81,6 +90,7 @@ export const FIELDS = {
|
|||
ATTACHMENT: {
|
||||
name: "Attachment",
|
||||
type: "attachment",
|
||||
icon: "Folder",
|
||||
constraints: {
|
||||
type: "array",
|
||||
presence: false,
|
||||
|
@ -89,6 +99,7 @@ export const FIELDS = {
|
|||
LINK: {
|
||||
name: "Relationship",
|
||||
type: "link",
|
||||
icon: "Link",
|
||||
constraints: {
|
||||
type: "array",
|
||||
presence: false,
|
||||
|
@ -97,11 +108,13 @@ export const FIELDS = {
|
|||
FORMULA: {
|
||||
name: "Formula",
|
||||
type: "formula",
|
||||
icon: "Calculator",
|
||||
constraints: {},
|
||||
},
|
||||
JSON: {
|
||||
name: "JSON",
|
||||
type: "json",
|
||||
icon: "Brackets",
|
||||
constraints: {
|
||||
type: "object",
|
||||
presence: false,
|
||||
|
|
|
@ -75,6 +75,14 @@
|
|||
{
|
||||
"name": "Chart",
|
||||
"icon": "GraphBarVertical",
|
||||
"children": ["bar", "line", "area", "candlestick", "pie", "donut"]
|
||||
"children": [
|
||||
"bar",
|
||||
"line",
|
||||
"area",
|
||||
"candlestick",
|
||||
"pie",
|
||||
"donut",
|
||||
"histogram"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -2212,6 +2212,147 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"histogram": {
|
||||
"name": "Histogram Chart",
|
||||
"description": "Histogram chart",
|
||||
"icon": "Histogram",
|
||||
"size": {
|
||||
"width": 600,
|
||||
"height": 400
|
||||
},
|
||||
"requiredAncestors": ["dataprovider"],
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Title",
|
||||
"key": "title"
|
||||
},
|
||||
{
|
||||
"type": "dataProvider",
|
||||
"label": "Provider",
|
||||
"key": "dataProvider",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"label": "Data column",
|
||||
"key": "valueColumn",
|
||||
"dependsOn": "dataProvider",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Y axis label",
|
||||
"key": "yAxisLabel",
|
||||
"defaultValue": "Frequency"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "X axis label",
|
||||
"key": "xAxisLabel"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"label": "Bucket count",
|
||||
"key": "bucketCount",
|
||||
"defaultValue": 10,
|
||||
"min": 2
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Data labels",
|
||||
"key": "dataLabels",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Width",
|
||||
"key": "width"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Height",
|
||||
"key": "height",
|
||||
"defaultValue": "400"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Colors",
|
||||
"key": "palette",
|
||||
"defaultValue": "Palette 1",
|
||||
"options": [
|
||||
"Custom",
|
||||
"Palette 1",
|
||||
"Palette 2",
|
||||
"Palette 3",
|
||||
"Palette 4",
|
||||
"Palette 5",
|
||||
"Palette 6",
|
||||
"Palette 7",
|
||||
"Palette 8",
|
||||
"Palette 9",
|
||||
"Palette 10"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"label": "C1",
|
||||
"key": "c1",
|
||||
"dependsOn": {
|
||||
"setting": "palette",
|
||||
"value": "Custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"label": "C2",
|
||||
"key": "c2",
|
||||
"dependsOn": {
|
||||
"setting": "palette",
|
||||
"value": "Custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"label": "C3",
|
||||
"key": "c3",
|
||||
"dependsOn": {
|
||||
"setting": "palette",
|
||||
"value": "Custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"label": "C4",
|
||||
"key": "c4",
|
||||
"dependsOn": {
|
||||
"setting": "palette",
|
||||
"value": "Custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"label": "C5",
|
||||
"key": "c5",
|
||||
"dependsOn": {
|
||||
"setting": "palette",
|
||||
"value": "Custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Animate",
|
||||
"key": "animate",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Horizontal",
|
||||
"key": "horizontal",
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"form": {
|
||||
"name": "Form",
|
||||
"icon": "Form",
|
||||
|
@ -3965,6 +4106,10 @@
|
|||
"label": "Bar",
|
||||
"value": "bar"
|
||||
},
|
||||
{
|
||||
"label": "Histogram",
|
||||
"value": "histogram"
|
||||
},
|
||||
{
|
||||
"label": "Line",
|
||||
"value": "line"
|
||||
|
@ -4215,6 +4360,47 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": true,
|
||||
"name": "Histogram Chart",
|
||||
"icon": "Histogram",
|
||||
"dependsOn": {
|
||||
"setting": "chartType",
|
||||
"value": "histogram"
|
||||
},
|
||||
"settings": [
|
||||
{
|
||||
"type": "field",
|
||||
"label": "Value column",
|
||||
"key": "valueColumn",
|
||||
"dependsOn": "dataSource",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Y axis label",
|
||||
"key": "yAxisLabel"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "X axis label",
|
||||
"key": "xAxisLabel"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Horizontal",
|
||||
"key": "horizontal",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"label": "Bucket count",
|
||||
"key": "bucketCount",
|
||||
"defaultValue": 10,
|
||||
"min": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": true,
|
||||
"name": "Line Chart",
|
||||
|
@ -5234,11 +5420,7 @@
|
|||
"type": "boolean",
|
||||
"label": "Hide notifications",
|
||||
"key": "notificationOverride",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "showSaveButton",
|
||||
"value": true
|
||||
}
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -46,6 +46,9 @@
|
|||
export let lowColumn
|
||||
export let dateColumn
|
||||
|
||||
// Histogram
|
||||
export let bucketCount
|
||||
|
||||
let dataProviderId
|
||||
|
||||
$: colors = c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null
|
||||
|
@ -92,6 +95,7 @@
|
|||
highColumn,
|
||||
lowColumn,
|
||||
dateColumn,
|
||||
bucketCount,
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
tableId: dataSource?.tableId,
|
||||
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
|
||||
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
|
||||
notificationOverride,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
<script>
|
||||
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||
import ApexChart from "./ApexChart.svelte"
|
||||
|
||||
export let title
|
||||
export let dataProvider
|
||||
export let valueColumn
|
||||
export let xAxisLabel
|
||||
export let yAxisLabel
|
||||
export let height
|
||||
export let width
|
||||
export let dataLabels
|
||||
export let animate
|
||||
export let palette
|
||||
export let c1, c2, c3, c4, c5
|
||||
export let horizontal
|
||||
export let bucketCount = 10
|
||||
|
||||
$: options = setUpChart(
|
||||
title,
|
||||
dataProvider,
|
||||
valueColumn,
|
||||
xAxisLabel || valueColumn,
|
||||
yAxisLabel,
|
||||
height,
|
||||
width,
|
||||
dataLabels,
|
||||
animate,
|
||||
palette,
|
||||
horizontal,
|
||||
c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null,
|
||||
customColor,
|
||||
bucketCount
|
||||
)
|
||||
|
||||
$: customColor = palette === "Custom"
|
||||
|
||||
const setUpChart = (
|
||||
title,
|
||||
dataProvider,
|
||||
valueColumn,
|
||||
xAxisLabel, //freqAxisLabel
|
||||
yAxisLabel, //valueAxisLabel
|
||||
height,
|
||||
width,
|
||||
dataLabels,
|
||||
animate,
|
||||
palette,
|
||||
horizontal,
|
||||
colors,
|
||||
customColor,
|
||||
bucketCount
|
||||
) => {
|
||||
const allCols = [valueColumn]
|
||||
if (
|
||||
!dataProvider ||
|
||||
!dataProvider.rows?.length ||
|
||||
allCols.find(x => x == null)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Fetch data
|
||||
const { schema, rows } = dataProvider
|
||||
const reducer = row => (valid, column) => valid && row[column] != null
|
||||
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||
const data = rows.filter(row => hasAllColumns(row)).slice(0, 100)
|
||||
if (!schema || !data.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Initialise default chart
|
||||
let builder = new ApexOptionsBuilder()
|
||||
.type("bar")
|
||||
.title(title)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.xLabel(horizontal ? yAxisLabel : xAxisLabel)
|
||||
.yLabel(horizontal ? xAxisLabel : yAxisLabel)
|
||||
.dataLabels(dataLabels)
|
||||
.animate(animate)
|
||||
.palette(palette)
|
||||
.horizontal(horizontal)
|
||||
.colors(customColor ? colors : null)
|
||||
|
||||
if (horizontal) {
|
||||
builder = builder.setOption(["plotOptions", "bar", "barHeight"], "90%")
|
||||
} else {
|
||||
builder = builder.setOption(["plotOptions", "bar", "columnWidth"], "99%")
|
||||
}
|
||||
|
||||
// Pull occurences of the value.
|
||||
let flatlist = data.map(row => {
|
||||
return row[valueColumn]
|
||||
})
|
||||
|
||||
// Build range buckets
|
||||
let interval = Math.max(...flatlist) / bucketCount
|
||||
let counts = Array(bucketCount).fill(0)
|
||||
|
||||
// Assign row data to a bucket
|
||||
let buckets = flatlist.reduce((acc, val) => {
|
||||
let dest = Math.min(Math.floor(val / interval), bucketCount - 1)
|
||||
acc[dest] = acc[dest] + 1
|
||||
return acc
|
||||
}, counts)
|
||||
|
||||
const rangeLabel = bucketIdx => {
|
||||
return `${Math.floor(interval * bucketIdx)} - ${Math.floor(
|
||||
interval * (bucketIdx + 1)
|
||||
)}`
|
||||
}
|
||||
|
||||
const series = [
|
||||
{
|
||||
name: yAxisLabel,
|
||||
data: Array.from({ length: buckets.length }, (_, i) => ({
|
||||
x: rangeLabel(i),
|
||||
y: buckets[i],
|
||||
})),
|
||||
},
|
||||
]
|
||||
|
||||
builder = builder.setOption(["xaxis", "labels"], {
|
||||
formatter: x => {
|
||||
return x + ""
|
||||
},
|
||||
})
|
||||
|
||||
builder = builder.series(series)
|
||||
|
||||
return builder.getOptions()
|
||||
}
|
||||
</script>
|
||||
|
||||
<ApexChart {options} />
|
|
@ -4,3 +4,4 @@ export { default as pie } from "./PieChart.svelte"
|
|||
export { default as donut } from "./DonutChart.svelte"
|
||||
export { default as area } from "./AreaChart.svelte"
|
||||
export { default as candlestick } from "./CandleStickChart.svelte"
|
||||
export { default as histogram } from "./HistogramChart.svelte"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import { getContext, onMount, tick } from "svelte"
|
||||
import GridCell from "./GridCell.svelte"
|
||||
import { Icon, Popover, Menu, MenuItem } from "@budibase/bbui"
|
||||
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
|
||||
import { getColumnIcon } from "../lib/utils"
|
||||
|
||||
export let column
|
||||
|
@ -16,6 +16,7 @@
|
|||
sort,
|
||||
renderedColumns,
|
||||
dispatch,
|
||||
subscribe,
|
||||
config,
|
||||
ui,
|
||||
columns,
|
||||
|
@ -32,7 +33,9 @@
|
|||
|
||||
let anchor
|
||||
let open = false
|
||||
let editIsOpen = false
|
||||
let timeout
|
||||
let popover
|
||||
|
||||
$: sortedBy = column.name === $sort.column
|
||||
$: canMoveLeft = orderable && idx > 0
|
||||
|
@ -44,11 +47,16 @@
|
|||
? "high-low"
|
||||
: "Z-A"
|
||||
|
||||
const editColumn = () => {
|
||||
const editColumn = async () => {
|
||||
editIsOpen = true
|
||||
await tick()
|
||||
dispatch("edit-column", column.schema)
|
||||
open = false
|
||||
}
|
||||
|
||||
const cancelEdit = () => {
|
||||
popover.hide()
|
||||
editIsOpen = false
|
||||
}
|
||||
const onMouseDown = e => {
|
||||
if (e.button === 0 && orderable) {
|
||||
timeout = setTimeout(() => {
|
||||
|
@ -109,6 +117,7 @@
|
|||
columns.actions.saveChanges()
|
||||
open = false
|
||||
}
|
||||
onMount(() => subscribe("close-edit-column", cancelEdit))
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
@ -157,57 +166,74 @@
|
|||
|
||||
<Popover
|
||||
bind:open
|
||||
bind:this={popover}
|
||||
{anchor}
|
||||
align="right"
|
||||
offset={0}
|
||||
popoverTarget={document.getElementById(`grid-${rand}`)}
|
||||
animate={false}
|
||||
customZindex={100}
|
||||
>
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon="Edit"
|
||||
on:click={editColumn}
|
||||
disabled={!$config.allowSchemaChanges || column.schema.disabled}
|
||||
{#if editIsOpen}
|
||||
<div
|
||||
use:clickOutside={() => {
|
||||
editIsOpen = false
|
||||
}}
|
||||
class="content"
|
||||
>
|
||||
Edit column
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="Label"
|
||||
on:click={makeDisplayColumn}
|
||||
disabled={idx === "sticky" ||
|
||||
!$config.allowSchemaChanges ||
|
||||
bannedDisplayColumnTypes.includes(column.schema.type)}
|
||||
>
|
||||
Use as display column
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="SortOrderUp"
|
||||
on:click={sortAscending}
|
||||
disabled={column.name === $sort.column && $sort.order === "ascending"}
|
||||
>
|
||||
Sort {ascendingLabel}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="SortOrderDown"
|
||||
on:click={sortDescending}
|
||||
disabled={column.name === $sort.column && $sort.order === "descending"}
|
||||
>
|
||||
Sort {descendingLabel}
|
||||
</MenuItem>
|
||||
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
|
||||
Move left
|
||||
</MenuItem>
|
||||
<MenuItem disabled={!canMoveRight} icon="ChevronRight" on:click={moveRight}>
|
||||
Move right
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
disabled={idx === "sticky" || !$config.showControls}
|
||||
icon="VisibilityOff"
|
||||
on:click={hideColumn}
|
||||
>
|
||||
Hide column
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<slot />
|
||||
</div>
|
||||
{:else}
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon="Edit"
|
||||
on:click={editColumn}
|
||||
disabled={!$config.allowSchemaChanges || column.schema.disabled}
|
||||
>
|
||||
Edit column
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="Label"
|
||||
on:click={makeDisplayColumn}
|
||||
disabled={idx === "sticky" ||
|
||||
!$config.allowSchemaChanges ||
|
||||
bannedDisplayColumnTypes.includes(column.schema.type)}
|
||||
>
|
||||
Use as display column
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="SortOrderUp"
|
||||
on:click={sortAscending}
|
||||
disabled={column.name === $sort.column && $sort.order === "ascending"}
|
||||
>
|
||||
Sort {ascendingLabel}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="SortOrderDown"
|
||||
on:click={sortDescending}
|
||||
disabled={column.name === $sort.column && $sort.order === "descending"}
|
||||
>
|
||||
Sort {descendingLabel}
|
||||
</MenuItem>
|
||||
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
|
||||
Move left
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
disabled={!canMoveRight}
|
||||
icon="ChevronRight"
|
||||
on:click={moveRight}
|
||||
>
|
||||
Move right
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
disabled={idx === "sticky" || !$config.showControls}
|
||||
icon="VisibilityOff"
|
||||
on:click={hideColumn}
|
||||
>
|
||||
Hide column
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
{/if}
|
||||
</Popover>
|
||||
|
||||
<style>
|
||||
|
@ -255,4 +281,13 @@
|
|||
.header-cell:hover .sort-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 300px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
background: var(--spectrum-alias-background-color-secondary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
let focusedOptionIdx = null
|
||||
|
||||
$: options = schema?.constraints?.inclusion || []
|
||||
$: optionColors = schema?.optionColors || {}
|
||||
$: editable = focused && !readonly
|
||||
$: values = Array.isArray(value) ? value : [value].filter(x => x != null)
|
||||
$: {
|
||||
|
@ -93,7 +94,7 @@
|
|||
on:click={editable ? open : null}
|
||||
>
|
||||
{#each values as val}
|
||||
{@const color = getOptionColor(val)}
|
||||
{@const color = optionColors[val] || getOptionColor(val)}
|
||||
{#if color}
|
||||
<div class="badge text" style="--color: {color}">
|
||||
<span>
|
||||
|
@ -121,7 +122,7 @@
|
|||
use:clickOutside={close}
|
||||
>
|
||||
{#each options as option, idx}
|
||||
{@const color = getOptionColor(option)}
|
||||
{@const color = optionColors[option] || getOptionColor(option)}
|
||||
<div
|
||||
class="option"
|
||||
on:click={() => toggleOption(option)}
|
||||
|
|
|
@ -139,9 +139,20 @@
|
|||
{#if $loaded}
|
||||
<div class="grid-data-outer" use:clickOutside={ui.actions.blur}>
|
||||
<div class="grid-data-inner">
|
||||
<StickyColumn />
|
||||
<StickyColumn>
|
||||
<svelte:fragment slot="edit-column">
|
||||
<slot name="edit-column" />
|
||||
</svelte:fragment>
|
||||
</StickyColumn>
|
||||
<div class="grid-data-content">
|
||||
<HeaderRow />
|
||||
<HeaderRow>
|
||||
<svelte:fragment slot="add-column">
|
||||
<slot name="add-column" />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="edit-column">
|
||||
<slot name="edit-column" />
|
||||
</svelte:fragment>
|
||||
</HeaderRow>
|
||||
<GridBody />
|
||||
</div>
|
||||
{#if $canAddRows}
|
||||
|
|
|
@ -1,34 +1,22 @@
|
|||
<script>
|
||||
import NewColumnButton from "./NewColumnButton.svelte"
|
||||
|
||||
import { getContext } from "svelte"
|
||||
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||
import HeaderCell from "../cells/HeaderCell.svelte"
|
||||
import { Icon, TempTooltip, TooltipType } from "@budibase/bbui"
|
||||
import { TempTooltip, TooltipType } from "@budibase/bbui"
|
||||
|
||||
const {
|
||||
renderedColumns,
|
||||
dispatch,
|
||||
scroll,
|
||||
hiddenColumnsWidth,
|
||||
width,
|
||||
config,
|
||||
hasNonAutoColumn,
|
||||
tableId,
|
||||
loading,
|
||||
} = getContext("grid")
|
||||
|
||||
$: columnsWidth = $renderedColumns.reduce(
|
||||
(total, col) => total + col.width,
|
||||
0
|
||||
)
|
||||
$: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
|
||||
$: left = Math.min($width - 40, end)
|
||||
const { renderedColumns, config, hasNonAutoColumn, tableId, loading } =
|
||||
getContext("grid")
|
||||
</script>
|
||||
|
||||
<div class="header">
|
||||
<GridScrollWrapper scrollHorizontally>
|
||||
<div class="row">
|
||||
{#each $renderedColumns as column, idx}
|
||||
<HeaderCell {column} {idx} />
|
||||
<HeaderCell {column} {idx}>
|
||||
<slot name="edit-column" />
|
||||
</HeaderCell>
|
||||
{/each}
|
||||
</div>
|
||||
</GridScrollWrapper>
|
||||
|
@ -39,13 +27,9 @@
|
|||
type={TooltipType.Info}
|
||||
condition={!$hasNonAutoColumn && !$loading}
|
||||
>
|
||||
<div
|
||||
class="add"
|
||||
style="left:{left}px;"
|
||||
on:click={() => dispatch("add-column")}
|
||||
>
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
<NewColumnButton>
|
||||
<slot name="add-column" />
|
||||
</NewColumnButton>
|
||||
</TempTooltip>
|
||||
{/key}
|
||||
{/if}
|
||||
|
@ -61,21 +45,4 @@
|
|||
.row {
|
||||
display: flex;
|
||||
}
|
||||
.add {
|
||||
height: var(--default-row-height);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 40px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border-left: var(--cell-border);
|
||||
border-right: var(--cell-border);
|
||||
border-bottom: var(--cell-border);
|
||||
background: var(--grid-background-alt);
|
||||
z-index: 1;
|
||||
}
|
||||
.add:hover {
|
||||
background: var(--spectrum-global-color-gray-200);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<script>
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { Icon, Popover, clickOutside } from "@budibase/bbui"
|
||||
|
||||
const { renderedColumns, scroll, hiddenColumnsWidth, width, subscribe } =
|
||||
getContext("grid")
|
||||
|
||||
let anchor
|
||||
let open = false
|
||||
$: columnsWidth = $renderedColumns.reduce(
|
||||
(total, col) => (total += col.width),
|
||||
0
|
||||
)
|
||||
$: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
|
||||
$: left = Math.min($width - 40, end)
|
||||
|
||||
const close = () => {
|
||||
open = false
|
||||
}
|
||||
onMount(() => subscribe("close-edit-column", close))
|
||||
</script>
|
||||
|
||||
<div
|
||||
id="add-column-button"
|
||||
bind:this={anchor}
|
||||
class="add"
|
||||
style="left:{left}px"
|
||||
on:click={() => (open = true)}
|
||||
>
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
<Popover
|
||||
bind:open
|
||||
{anchor}
|
||||
align="right"
|
||||
offset={0}
|
||||
popoverTarget={document.getElementById(`add-column-button`)}
|
||||
animate={false}
|
||||
customZindex={100}
|
||||
>
|
||||
<div
|
||||
use:clickOutside={() => {
|
||||
open = false
|
||||
}}
|
||||
class="content"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
<style>
|
||||
.add {
|
||||
height: var(--default-row-height);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 40px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border-left: var(--cell-border);
|
||||
border-right: var(--cell-border);
|
||||
border-bottom: var(--cell-border);
|
||||
background: var(--grid-background-alt);
|
||||
z-index: 1;
|
||||
}
|
||||
.add:hover {
|
||||
background: var(--spectrum-global-color-gray-200);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 300px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
z-index: 2;
|
||||
background: var(--spectrum-alias-background-color-secondary);
|
||||
}
|
||||
</style>
|
|
@ -57,7 +57,9 @@
|
|||
disabled={!$renderedRows.length}
|
||||
/>
|
||||
{#if $stickyColumn}
|
||||
<HeaderCell column={$stickyColumn} orderable={false} idx="sticky" />
|
||||
<HeaderCell column={$stickyColumn} orderable={false} idx="sticky">
|
||||
<slot name="edit-column" />
|
||||
</HeaderCell>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Reference in New Issue