budibase/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte

957 lines
28 KiB
Svelte
Raw Normal View History

2020-08-07 17:13:57 +02:00
<script>
2021-02-09 19:49:12 +01:00
import {
Input,
Button,
Label,
Select,
Multiselect,
2021-02-09 19:49:12 +01:00
Toggle,
2023-08-17 11:06:49 +02:00
Icon,
DatePicker,
Modal,
notifications,
Layout,
2023-08-17 11:06:49 +02:00
AbsTooltip,
ProgressCircle,
2021-02-09 19:49:12 +01:00
} from "@budibase/bbui"
2024-05-09 12:28:44 +02:00
import {
SWITCHABLE_TYPES,
ValidColumnNameRegex,
helpers,
2024-07-26 14:35:36 +02:00
PROTECTED_INTERNAL_COLUMNS,
2024-07-26 14:37:47 +02:00
PROTECTED_EXTERNAL_COLUMNS,
canHaveDefaultColumn,
2024-05-09 12:28:44 +02:00
} from "@budibase/shared-core"
import { makePropSafe } from "@budibase/string-templates"
2023-08-11 12:59:40 +02:00
import { createEventDispatcher, getContext, onMount } from "svelte"
2021-04-29 20:10:02 +02:00
import { cloneDeep } from "lodash/fp"
import { tables, datasources } from "stores/builder"
2021-04-29 20:10:02 +02:00
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
2021-03-01 19:03:33 +01:00
import {
FIELDS,
RelationshipType,
2023-09-25 15:38:36 +02:00
PrettyRelationshipDefinitions,
DB_TYPE_EXTERNAL,
2021-03-01 19:03:33 +01:00
} from "constants/backend"
import { getAutoColumnInformation, buildAutoColumn } from "helpers/utils"
2020-10-23 18:38:10 +02:00
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
2021-04-29 20:10:02 +02:00
import { getBindings } from "components/backend/DataTable/formula"
import JSONSchemaModal from "./JSONSchemaModal.svelte"
2024-05-03 18:22:06 +02:00
import {
BBReferenceFieldSubType,
FieldType,
SourceName,
} from "@budibase/types"
import RelationshipSelector from "components/common/RelationshipSelector.svelte"
import { RowUtils, canBeDisplayColumn } from "@budibase/frontend-core"
import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte"
import OptionsEditor from "./OptionsEditor.svelte"
2024-08-15 11:53:43 +02:00
import { isEnabled } from "helpers/featureFlags"
import { getUserBindings } from "dataBinding"
import { makePropSafe as safe } from "@budibase/string-templates"
export let field
2020-08-07 17:13:57 +02:00
const dispatch = createEventDispatcher()
const { dispatch: gridDispatch, rows } = getContext("grid")
const SafeID = `${safe("user")}.${safe("_id")}`
const SingleUserDefault = `{{ ${SafeID} }}`
const MultiUserDefault = `{{ js "${btoa(`return [$("${SafeID}")]`)}" }}`
2023-08-11 12:59:40 +02:00
let mounted = false
let originalName
let linkEditDisabled
let primaryDisplay
let indexes = [...($tables.selected.indexes || [])]
let isCreating = undefined
2024-04-17 16:13:28 +02:00
let relationshipPart1 = PrettyRelationshipDefinitions.MANY
let relationshipPart2 = PrettyRelationshipDefinitions.ONE
let relationshipTableIdPrimary = null
let relationshipTableIdSecondary = null
let table = $tables.selected
let confirmDeleteDialog
let savingColumn
let deleteColName
let jsonSchemaModal
let editableColumn = {
2023-10-05 11:16:52 +02:00
type: FIELDS.STRING.type,
constraints: FIELDS.STRING.constraints,
// Initial value for column name in other table for linked records
2021-03-23 11:54:03 +01:00
fieldName: $tables.selected.name,
}
let relationshipOpts1 = Object.values(PrettyRelationshipDefinitions)
let relationshipOpts2 = Object.values(PrettyRelationshipDefinitions)
2024-08-16 13:49:08 +02:00
const relationshipMap = {
2023-09-29 12:56:50 +02:00
[RelationshipType.ONE_TO_MANY]: {
2023-09-26 10:33:44 +02:00
part1: PrettyRelationshipDefinitions.MANY,
part2: PrettyRelationshipDefinitions.ONE,
},
[RelationshipType.MANY_TO_MANY]: {
part1: PrettyRelationshipDefinitions.MANY,
part2: PrettyRelationshipDefinitions.MANY,
},
2023-09-29 12:56:50 +02:00
[RelationshipType.MANY_TO_ONE]: {
2023-09-26 10:33:44 +02:00
part1: PrettyRelationshipDefinitions.ONE,
part2: PrettyRelationshipDefinitions.MANY,
},
}
2024-08-16 13:49:08 +02:00
const autoColumnInfo = getAutoColumnInformation()
let optionsValid = true
2023-09-26 10:33:44 +02:00
$: rowGoldenSample = RowUtils.generateGoldenSample($rows)
$: if (primaryDisplay) {
editableColumn.constraints.presence = { allowEmpty: false }
}
$: {
// this parses any changes the user has made when creating a new internal relationship
// into what we expect the schema to look like
if (editableColumn.type === FieldType.LINK) {
relationshipTableIdPrimary = table._id
if (relationshipPart1 === PrettyRelationshipDefinitions.ONE) {
relationshipOpts2 = relationshipOpts2.filter(
opt => opt !== PrettyRelationshipDefinitions.ONE
)
} else {
relationshipOpts2 = Object.values(PrettyRelationshipDefinitions)
}
if (relationshipPart2 === PrettyRelationshipDefinitions.ONE) {
relationshipOpts1 = relationshipOpts1.filter(
opt => opt !== PrettyRelationshipDefinitions.ONE
)
} else {
relationshipOpts1 = Object.values(PrettyRelationshipDefinitions)
}
// Determine the relationship type based on the selected values of both parts
2023-09-26 10:33:44 +02:00
editableColumn.relationshipType = Object.entries(relationshipMap).find(
([_, parts]) =>
parts.part1 === relationshipPart1 && parts.part2 === relationshipPart2
)?.[0]
// Set the tableId based on the selected table
editableColumn.tableId = relationshipTableIdSecondary
}
}
$: initialiseField(field, savingColumn)
$: checkConstraints(editableColumn)
$: required =
primaryDisplay ||
editableColumn?.constraints?.presence === true ||
editableColumn?.constraints?.presence?.allowEmpty === false
$: uneditable =
2021-05-23 16:06:33 +02:00
$tables.selected?._id === TableNames.USERS &&
UNEDITABLE_USER_FIELDS.includes(editableColumn.name)
2021-03-15 21:38:55 +01:00
$: invalid =
!editableColumn?.name ||
(editableColumn?.type === FieldType.LINK && !editableColumn?.tableId) ||
Object.keys(errors).length !== 0 ||
!optionsValid
$: errors = checkErrors(editableColumn)
$: datasource = $datasources.list.find(
source => source._id === table?.sourceId
)
$: tableAutoColumnsTypes = getTableAutoColumnTypes($tables?.selected)
$: availableAutoColumns = Object.keys(autoColumnInfo).reduce((acc, key) => {
if (!tableAutoColumnsTypes.includes(key)) {
acc[key] = autoColumnInfo[key]
}
return acc
}, {})
$: availableAutoColumnKeys = availableAutoColumns
? Object.keys(availableAutoColumns)
: []
$: autoColumnOptions = editableColumn.autocolumn
? autoColumnInfo
: availableAutoColumns
// used to select what different options can be displayed for column type
$: canBeDisplay =
canBeDisplayColumn(editableColumn) && !editableColumn.autocolumn
$: defaultValuesEnabled = isEnabled("DEFAULT_VALUES")
$: canHaveDefault = !required && canHaveDefaultColumn(editableColumn.type)
2021-02-15 20:59:49 +01:00
$: canBeRequired =
editableColumn?.type !== FieldType.LINK &&
!uneditable &&
editableColumn?.type !== FieldType.AUTO &&
!editableColumn.autocolumn
2024-07-17 12:02:47 +02:00
$: hasDefault =
editableColumn?.default != null && editableColumn?.default !== ""
$: externalTable = table.sourceType === DB_TYPE_EXTERNAL
// in the case of internal tables the sourceId will just be undefined
$: tableOptions = $tables.list.filter(
opt =>
opt.sourceType === table.sourceType && table.sourceId === opt.sourceId
)
$: typeEnabled =
!originalName ||
(originalName &&
2024-04-17 16:13:46 +02:00
SWITCHABLE_TYPES[field.type] &&
!editableColumn?.autocolumn)
2024-04-26 09:58:41 +02:00
$: allowedTypes = getAllowedTypes(datasource).map(t => ({
fieldId: makeFieldId(t.type, t.subtype),
...t,
}))
$: defaultValueBindings = [
{
type: "context",
runtimeBinding: `${makePropSafe("now")}`,
readableBinding: `Date`,
category: "Date",
icon: "Date",
display: {
name: "Server date",
},
},
...getUserBindings(),
]
$: sanitiseDefaultValue(
editableColumn.type,
editableColumn.constraints?.inclusion || [],
editableColumn.default
)
2024-04-26 09:58:41 +02:00
const fieldDefinitions = Object.values(FIELDS).reduce(
// Storing the fields by complex field id
(acc, field) => ({
...acc,
[makeFieldId(field.type, field.subtype)]: field,
}),
{}
)
function makeFieldId(type, subtype, autocolumn) {
// don't make field IDs for auto types
if (type === FieldType.AUTO || autocolumn) {
return type.toUpperCase()
2024-04-22 13:11:13 +02:00
} else if (
type === FieldType.BB_REFERENCE ||
type === FieldType.BB_REFERENCE_SINGLE
) {
return `${type}${subtype || ""}`.toUpperCase()
2024-03-20 13:35:09 +01:00
} else {
return type.toUpperCase()
}
}
const initialiseField = (field, savingColumn) => {
isCreating = !field
if (field && !savingColumn) {
editableColumn = cloneDeep(field)
originalName = editableColumn.name ? editableColumn.name + "" : null
linkEditDisabled = originalName != null
primaryDisplay =
$tables.selected.primaryDisplay == null ||
$tables.selected.primaryDisplay === editableColumn.name
// Here we are setting the relationship values based on the editableColumn
// This part of the code is used when viewing an existing field hence the check
// for the tableId
if (editableColumn.type === FieldType.LINK && editableColumn.tableId) {
relationshipTableIdPrimary = table._id
relationshipTableIdSecondary = editableColumn.tableId
if (editableColumn.relationshipType in relationshipMap) {
const { part1, part2 } =
relationshipMap[editableColumn.relationshipType]
relationshipPart1 = part1
relationshipPart2 = part2
}
}
}
if (!savingColumn) {
editableColumn.fieldId = makeFieldId(
editableColumn.type,
editableColumn.subtype,
editableColumn.autocolumn
)
}
}
const getTableAutoColumnTypes = table => {
return Object.keys(table?.schema).reduce((acc, key) => {
let fieldSchema = table?.schema[key]
if (fieldSchema.autocolumn) {
acc.push(fieldSchema.subtype)
}
return acc
}, [])
}
2020-08-07 17:13:57 +02:00
async function saveColumn() {
if (errors?.length) {
return
}
savingColumn = true
let saveColumn = cloneDeep(editableColumn)
delete saveColumn.fieldId
2023-09-14 16:06:02 +02:00
if (saveColumn.type === FieldType.AUTO) {
saveColumn = buildAutoColumn(
2023-02-06 09:36:25 +01:00
$tables.selected.name,
saveColumn.name,
saveColumn.subtype
)
}
if (saveColumn.type !== FieldType.LINK) {
delete saveColumn.fieldName
}
// Ensure we don't have a default value if we can't have one
if (!canHaveDefault || !defaultValuesEnabled) {
delete saveColumn.default
}
// Ensure primary display columns are always required and don't have default values
if (primaryDisplay) {
saveColumn.constraints.presence = { allowEmpty: false }
delete saveColumn.default
}
// Ensure the field is not required if we have a default value
if (saveColumn.default) {
saveColumn.constraints.presence = false
}
try {
await tables.saveField({
originalName,
field: saveColumn,
primaryDisplay,
indexes,
})
dispatch("updatecolumns")
gridDispatch("close-edit-column")
if (originalName) {
notifications.success("Column updated successfully")
} else {
notifications.success("Column created successfully")
}
} catch (err) {
notifications.error(`Error saving column: ${err.message}`)
} finally {
savingColumn = false
}
2020-08-07 17:13:57 +02:00
}
function cancelEdit() {
editableColumn.name = originalName
gridDispatch("close-edit-column")
}
2023-02-27 13:17:05 +01:00
async function deleteColumn() {
try {
editableColumn.name = deleteColName
if (editableColumn.name === $tables.selected.primaryDisplay) {
notifications.error("You cannot delete the display column")
} else {
2023-02-27 13:17:05 +01:00
await tables.deleteField(editableColumn)
notifications.success(`Column ${editableColumn.name} deleted`)
confirmDeleteDialog.hide()
dispatch("updatecolumns")
gridDispatch("close-edit-column")
}
} catch (error) {
notifications.error(`Error deleting column: ${error.message}`)
2020-10-23 18:38:10 +02:00
}
}
2023-10-04 14:48:53 +02:00
function onHandleTypeChange(event) {
handleTypeChange(event.detail)
}
function handleTypeChange(type) {
// remove any extra fields that may not be related to this type
delete editableColumn.autocolumn
delete editableColumn.subtype
delete editableColumn.tableId
delete editableColumn.relationshipType
delete editableColumn.formulaType
2023-10-04 16:14:17 +02:00
delete editableColumn.constraints
// Add in defaults and initial definition
2023-10-04 14:48:53 +02:00
const definition = fieldDefinitions[type?.toUpperCase()]
if (definition?.constraints) {
editableColumn.constraints = cloneDeep(definition.constraints)
}
2023-10-04 14:06:46 +02:00
editableColumn.type = definition.type
editableColumn.subtype = definition.subtype
// Default relationships many to many
if (editableColumn.type === FieldType.LINK) {
editableColumn.relationshipType = RelationshipType.MANY_TO_MANY
} else if (editableColumn.type === FieldType.FORMULA) {
editableColumn.formulaType = "dynamic"
}
}
2024-07-17 12:02:47 +02:00
function setRequired(req) {
editableColumn.constraints.presence = req ? { allowEmpty: false } : false
required = req
}
2024-07-17 12:02:47 +02:00
function onChangeRequired(e) {
setRequired(e.detail)
}
function openJsonSchemaEditor() {
jsonSchemaModal.show()
}
2020-10-23 18:38:10 +02:00
function confirmDelete() {
confirmDeleteDialog.show()
2020-10-24 00:55:51 +02:00
}
function hideDeleteDialog() {
confirmDeleteDialog.hide()
deleteColName = ""
2020-10-23 18:38:10 +02:00
}
2024-04-25 14:31:46 +02:00
function getAllowedTypes(datasource) {
2024-04-16 14:21:37 +02:00
if (originalName) {
2024-05-03 18:22:06 +02:00
let possibleTypes = SWITCHABLE_TYPES[field.type] || [editableColumn.type]
2024-05-09 12:28:44 +02:00
if (helpers.schema.isDeprecatedSingleUserColumn(editableColumn)) {
2024-05-03 18:22:06 +02:00
// This will handle old single users columns
return [
{
...FIELDS.USER,
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USER,
},
]
} else if (
editableColumn.type === FieldType.BB_REFERENCE &&
editableColumn.subtype === BBReferenceFieldSubType.USERS
) {
// This will handle old multi users columns
return [
{
...FIELDS.USERS,
subtype: BBReferenceFieldSubType.USERS,
},
]
}
2024-04-17 16:43:13 +02:00
return Object.entries(FIELDS)
2024-04-22 13:11:13 +02:00
.filter(([_, field]) => possibleTypes.includes(field.type))
2024-04-17 16:43:13 +02:00
.map(([_, fieldDefinition]) => fieldDefinition)
2023-09-14 16:16:47 +02:00
}
if (!externalTable) {
2021-10-28 20:39:42 +02:00
return [
2023-10-05 11:16:52 +02:00
FIELDS.STRING,
FIELDS.NUMBER,
2023-10-05 11:16:52 +02:00
FIELDS.OPTIONS,
FIELDS.ARRAY,
FIELDS.BOOLEAN,
FIELDS.DATETIME,
FIELDS.LINK,
FIELDS.LONGFORM,
FIELDS.USER,
FIELDS.USERS,
FIELDS.ATTACHMENT_SINGLE,
FIELDS.ATTACHMENTS,
2023-10-05 11:16:52 +02:00
FIELDS.FORMULA,
FIELDS.JSON,
FIELDS.BARCODEQR,
FIELDS.SIGNATURE_SINGLE,
FIELDS.BIGINT,
FIELDS.AUTO,
2021-10-28 20:39:42 +02:00
]
} else {
let fields = [
2023-10-05 11:16:52 +02:00
FIELDS.STRING,
FIELDS.NUMBER,
FIELDS.OPTIONS,
FIELDS.ARRAY,
2023-10-05 11:16:52 +02:00
FIELDS.BOOLEAN,
FIELDS.DATETIME,
FIELDS.LINK,
FIELDS.LONGFORM,
FIELDS.USER,
FIELDS.USERS,
2023-10-05 11:16:52 +02:00
FIELDS.FORMULA,
FIELDS.BARCODEQR,
2023-10-05 11:16:52 +02:00
FIELDS.BIGINT,
2021-10-28 20:39:42 +02:00
]
2024-04-25 14:31:46 +02:00
// Filter out multiple users for google sheets
if (datasource?.source === SourceName.GOOGLE_SHEETS) {
fields = fields.filter(x => x !== FIELDS.USERS)
2024-04-25 14:31:46 +02:00
}
// Filter out SQL-specific types for non-SQL datasources
if (!table.sql) {
fields = fields.filter(x => x !== FIELDS.LINK && x !== FIELDS.ARRAY)
}
return fields
2021-10-28 20:39:42 +02:00
}
}
function checkConstraints(fieldToCheck) {
if (!fieldToCheck) {
return
}
// most types need this, just make sure its always present
if (!fieldToCheck.constraints) {
fieldToCheck.constraints = {}
}
// some string types may have been built by server, may not always have constraints
if (
fieldToCheck.type === FieldType.STRING &&
!fieldToCheck.constraints.length
) {
fieldToCheck.constraints.length = {}
}
// some number types made server-side will be missing constraints
if (
fieldToCheck.type === FieldType.NUMBER &&
!fieldToCheck.constraints.numericality
) {
fieldToCheck.constraints.numericality = {}
}
if (
fieldToCheck.type === FieldType.DATETIME &&
!fieldToCheck.constraints.datetime
) {
fieldToCheck.constraints.datetime = {}
}
}
function checkErrors(fieldInfo) {
if (!editableColumn) {
return {}
}
function inUse(tbl, column, ogName = null) {
const parsedColumn = column ? column.toLowerCase().trim() : column
return Object.keys(tbl?.schema || {}).some(key => {
let lowerKey = key.toLowerCase()
return lowerKey !== ogName?.toLowerCase() && lowerKey === parsedColumn
})
}
const newError = {}
const prohibited = externalTable
2024-07-26 14:37:47 +02:00
? PROTECTED_EXTERNAL_COLUMNS
2024-07-26 14:35:36 +02:00
: PROTECTED_INTERNAL_COLUMNS
if (!externalTable && fieldInfo.name?.startsWith("_")) {
newError.name = `Column name cannot start with an underscore.`
} else if (fieldInfo.name && !fieldInfo.name.match(ValidColumnNameRegex)) {
2022-11-08 18:16:35 +01:00
newError.name = `Illegal character; must be alpha-numeric.`
} else if (prohibited.some(name => fieldInfo?.name === name)) {
newError.name = `${prohibited.join(
", "
)} are not allowed as column names - case insensitive.`
} else if (inUse($tables.selected, fieldInfo.name, originalName)) {
newError.name = `Column name already in use.`
}
2024-03-13 15:16:21 +01:00
if (fieldInfo.type === FieldType.AUTO && !fieldInfo.subtype) {
newError.subtype = `Auto Column requires a type.`
}
if (fieldInfo.fieldName && fieldInfo.tableId) {
const relatedTable = $tables.list.find(
tbl => tbl._id === fieldInfo.tableId
)
if (inUse(relatedTable, fieldInfo.fieldName) && !originalName) {
newError.relatedName = `Column name already in use in table ${relatedTable.name}`
}
}
return newError
}
2023-08-11 12:59:40 +02:00
const sanitiseDefaultValue = (type, options, defaultValue) => {
if (!defaultValue?.length) {
return
}
// Delete default value for options fields if the option is no longer available
if (type === FieldType.OPTIONS && !options.includes(defaultValue)) {
delete editableColumn.default
}
// Filter array default values to only valid options
if (type === FieldType.ARRAY) {
editableColumn.default = defaultValue.filter(x => options.includes(x))
}
}
2023-08-11 12:59:40 +02:00
onMount(() => {
mounted = true
})
2020-08-07 17:13:57 +02:00
</script>
<Layout noPadding gap="S">
2023-08-11 12:59:40 +02:00
{#if mounted}
<Input
value={editableColumn.name}
2023-08-11 13:00:33 +02:00
autofocus
on:input={e => {
if (
!uneditable &&
!(linkEditDisabled && editableColumn.type === FieldType.LINK)
) {
editableColumn.name = e.target.value
}
}}
2023-08-11 13:00:33 +02:00
disabled={uneditable ||
(linkEditDisabled && editableColumn.type === FieldType.LINK)}
2023-08-11 13:00:33 +02:00
error={errors?.name}
/>
{/if}
<Select
placeholder={null}
disabled={!typeEnabled}
bind:value={editableColumn.fieldId}
2023-10-04 14:48:53 +02:00
on:change={onHandleTypeChange}
options={allowedTypes}
getOptionLabel={field => field.name}
getOptionValue={field => field.fieldId}
getOptionIcon={field => field.icon}
isOptionEnabled={option => {
if (option.type === FieldType.AUTO) {
return availableAutoColumnKeys?.length > 0
}
return true
}}
/>
2020-08-07 17:13:57 +02:00
2024-03-13 15:16:21 +01:00
{#if editableColumn.type === FieldType.STRING}
<Input
type="number"
label="Max Length"
bind:value={editableColumn.constraints.length.maximum}
/>
2024-03-13 15:16:21 +01:00
{:else if editableColumn.type === FieldType.OPTIONS}
<OptionsEditor
bind:constraints={editableColumn.constraints}
bind:optionColors={editableColumn.optionColors}
bind:valid={optionsValid}
/>
2024-03-13 15:16:21 +01:00
{:else if editableColumn.type === FieldType.LONGFORM}
<div>
2023-08-17 11:06:49 +02:00
<div class="tooltip-alignment">
<Label size="M">Formatting</Label>
<AbsTooltip
position="top"
type="info"
text={"Rich text includes support for images, link"}
>
<Icon size="XS" name="InfoOutline" />
</AbsTooltip>
</div>
<Toggle
bind:value={editableColumn.useRichText}
text="Enable rich text support (markdown)"
/>
</div>
2024-03-13 15:16:21 +01:00
{:else if editableColumn.type === FieldType.ARRAY}
<OptionsEditor
bind:constraints={editableColumn.constraints}
bind:optionColors={editableColumn.optionColors}
bind:valid={optionsValid}
/>
{:else if editableColumn.type === FieldType.DATETIME && !editableColumn.autocolumn}
<div class="split-label">
<div class="label-length">
<Label size="M">Earliest</Label>
</div>
<div class="input-length">
2024-05-23 11:51:02 +02:00
<DatePicker
bind:value={editableColumn.constraints.datetime.earliest}
enableTime={!editableColumn.dateOnly}
timeOnly={editableColumn.timeOnly}
/>
</div>
</div>
<div class="split-label">
<div class="label-length">
<Label size="M">Latest</Label>
</div>
<div class="input-length">
2024-05-23 11:51:02 +02:00
<DatePicker
bind:value={editableColumn.constraints.datetime.latest}
enableTime={!editableColumn.dateOnly}
timeOnly={editableColumn.timeOnly}
/>
</div>
</div>
{#if !editableColumn.timeOnly}
{#if datasource?.source !== SourceName.ORACLE && datasource?.source !== SourceName.SQL_SERVER && !editableColumn.dateOnly}
<div>
<div class="row">
<Label>Time zones</Label>
<AbsTooltip
position="top"
type="info"
text={isCreating
? null
: "We recommend not changing how timezones are handled for existing columns, as existing data will not be updated"}
>
<Icon size="XS" name="InfoOutline" />
</AbsTooltip>
</div>
<Toggle
bind:value={editableColumn.ignoreTimezones}
text="Ignore time zones"
/>
2023-08-17 11:06:49 +02:00
</div>
{/if}
<Toggle bind:value={editableColumn.dateOnly} text="Date only" />
{/if}
2024-03-13 15:16:21 +01:00
{:else if editableColumn.type === FieldType.NUMBER && !editableColumn.autocolumn}
<div class="split-label">
<div class="label-length">
<Label size="M">Min 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 === FieldType.LINK && !editableColumn.autocolumn}
<RelationshipSelector
bind:relationshipPart1
bind:relationshipPart2
bind:relationshipTableIdPrimary
bind:relationshipTableIdSecondary
bind:editableColumn
{relationshipOpts1}
{relationshipOpts2}
{linkEditDisabled}
{tableOptions}
{errors}
/>
{:else if editableColumn.type === FieldType.FORMULA}
{#if !externalTable}
<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" },
]}
disabled={!isCreating}
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}
<div class="split-label">
<div class="label-length">
<Label size="M">Formula</Label>
</div>
<div class="input-length">
<ModalBindableInput
panel={ServerBindingPanel}
title="Formula"
value={editableColumn.formula}
on:change={e => {
editableColumn = {
...editableColumn,
formula: e.detail,
}
}}
2024-10-02 11:25:22 +02:00
bindings={getBindings({ table })}
allowJS
context={rowGoldenSample}
/>
</div>
</div>
{:else if editableColumn.type === FieldType.JSON}
<Button primary text on:click={openJsonSchemaEditor}>
Open schema editor
</Button>
{/if}
{#if editableColumn.type === FieldType.AUTO || editableColumn.autocolumn}
<Select
label="Auto column type"
value={editableColumn.subtype}
on:change={e => (editableColumn.subtype = e.detail)}
options={Object.entries(autoColumnOptions)}
getOptionLabel={option => option[1].name}
2021-05-04 12:32:22 +02:00
getOptionValue={option => option[0]}
disabled={!availableAutoColumnKeys?.length || editableColumn.autocolumn}
error={errors?.subtype}
/>
{/if}
2021-04-16 18:12:22 +02:00
{#if canBeRequired || canBeDisplay}
<div>
{#if canBeRequired}
<Toggle
value={required}
on:change={onChangeRequired}
2024-07-17 12:02:47 +02:00
disabled={primaryDisplay || hasDefault}
thin
text="Required"
/>
{/if}
</div>
{/if}
2024-07-17 12:02:47 +02:00
{#if defaultValuesEnabled}
{#if editableColumn.type === FieldType.OPTIONS}
<Select
disabled={!canHaveDefault}
options={editableColumn.constraints?.inclusion || []}
label="Default value"
value={editableColumn.default}
on:change={e => (editableColumn.default = e.detail)}
placeholder="None"
/>
{:else if editableColumn.type === FieldType.ARRAY}
<Multiselect
disabled={!canHaveDefault}
options={editableColumn.constraints?.inclusion || []}
label="Default value"
value={editableColumn.default}
on:change={e =>
(editableColumn.default = e.detail?.length ? e.detail : undefined)}
placeholder="None"
/>
{:else if editableColumn.subtype === BBReferenceFieldSubType.USER}
{@const defaultValue =
editableColumn.type === FieldType.BB_REFERENCE_SINGLE
? SINGLE_USER_DEFAULT
: MULTI_USER_DEFAULT}
<Toggle
disabled={!canHaveDefault}
text="Default to current user"
value={editableColumn.default === defaultValue}
on:change={e =>
(editableColumn.default = e.detail ? defaultValue : undefined)}
/>
{:else}
2024-07-17 12:02:47 +02:00
<ModalBindableInput
disabled={!canHaveDefault}
2024-07-17 12:02:47 +02:00
panel={ServerBindingPanel}
title="Default value"
label="Default value"
placeholder="None"
2024-07-17 12:02:47 +02:00
value={editableColumn.default}
on:change={e => (editableColumn.default = e.detail)}
bindings={defaultValueBindings}
2024-07-17 12:02:47 +02:00
allowJS
/>
{/if}
2024-07-17 12:02:47 +02:00
{/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 || savingColumn}
newStyles
cta
on:click={saveColumn}
>
{#if savingColumn}
<div class="save-loading">
<ProgressCircle overBackground={true} size="S" />
</div>
{:else}
Save
{/if}
</Button>
</div>
<Modal bind:this={jsonSchemaModal}>
<JSONSchemaModal
schema={editableColumn.schema}
json={editableColumn.json}
on:save={({ detail }) => {
editableColumn.schema = detail.schema
editableColumn.json = detail.json
}}
/>
</Modal>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
2020-10-23 18:38:10 +02:00
<ConfirmDialog
bind:this={confirmDeleteDialog}
okText="Delete Column"
onOk={deleteColumn}
2020-10-24 00:55:51 +02:00
onCancel={hideDeleteDialog}
title="Confirm Deletion"
disabled={deleteColName !== originalName}
>
<p>
2023-10-17 11:53:08 +02:00
Are you sure you wish to delete the column
<b on:click={() => (deleteColName = originalName)}>{originalName}?</b>
Your data will be deleted and this action cannot be undone - enter the column
name to confirm.
</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;
}
2023-08-17 11:06:49 +02:00
.tooltip-alignment {
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.label-length {
flex-basis: 40%;
}
.input-length {
flex-grow: 1;
}
.row {
gap: 8px;
display: flex;
}
b {
transition: color 130ms ease-out;
}
b:hover {
cursor: pointer;
color: var(--spectrum-global-color-gray-900);
}
.save-loading {
display: flex;
justify-content: center;
}
</style>