2020-08-07 17:13:57 +02:00
|
|
|
<script>
|
2021-02-09 19:49:12 +01:00
|
|
|
import {
|
|
|
|
Input,
|
|
|
|
Button,
|
|
|
|
Label,
|
|
|
|
Select,
|
|
|
|
Toggle,
|
2023-08-17 11:06:49 +02:00
|
|
|
Icon,
|
2021-04-20 21:06:27 +02:00
|
|
|
DatePicker,
|
2021-11-23 19:20:12 +01:00
|
|
|
Modal,
|
2021-11-24 15:55:14 +01:00
|
|
|
notifications,
|
2023-07-31 16:28:11 +02:00
|
|
|
OptionSelectDnD,
|
|
|
|
Layout,
|
2023-08-17 11:06:49 +02:00
|
|
|
AbsTooltip,
|
2021-02-09 19:49:12 +01:00
|
|
|
} from "@budibase/bbui"
|
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"
|
2022-06-07 09:31:00 +02:00
|
|
|
import { tables, datasources } from "stores/backend"
|
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,
|
2023-07-21 13:57:47 +02:00
|
|
|
RelationshipType,
|
2021-11-10 16:01:44 +01:00
|
|
|
ALLOWABLE_STRING_OPTIONS,
|
|
|
|
ALLOWABLE_NUMBER_OPTIONS,
|
|
|
|
ALLOWABLE_STRING_TYPES,
|
|
|
|
ALLOWABLE_NUMBER_TYPES,
|
|
|
|
SWITCHABLE_TYPES,
|
2023-09-25 15:38:36 +02:00
|
|
|
PrettyRelationshipDefinitions,
|
2021-03-01 19:03:33 +01:00
|
|
|
} from "constants/backend"
|
2021-04-29 20:10:02 +02:00
|
|
|
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
2020-10-23 18:38:10 +02:00
|
|
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
2021-04-30 17:17:57 +02:00
|
|
|
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
2021-04-29 20:10:02 +02:00
|
|
|
import { getBindings } from "components/backend/DataTable/formula"
|
2021-11-23 19:20:12 +01:00
|
|
|
import JSONSchemaModal from "./JSONSchemaModal.svelte"
|
2023-07-20 13:21:09 +02:00
|
|
|
import { ValidColumnNameRegex } from "@budibase/shared-core"
|
2023-09-25 14:25:23 +02:00
|
|
|
import RelationshipSelector from "components/common/RelationshipSelector.svelte"
|
2020-08-07 17:13:57 +02:00
|
|
|
|
2021-04-29 20:06:58 +02:00
|
|
|
const AUTO_TYPE = "auto"
|
|
|
|
const FORMULA_TYPE = FIELDS.FORMULA.type
|
2021-02-17 18:30:58 +01:00
|
|
|
const LINK_TYPE = FIELDS.LINK.type
|
2021-10-29 14:34:10 +02:00
|
|
|
const STRING_TYPE = FIELDS.STRING.type
|
|
|
|
const NUMBER_TYPE = FIELDS.NUMBER.type
|
2021-11-23 19:20:12 +01:00
|
|
|
const JSON_TYPE = FIELDS.JSON.type
|
2021-11-24 18:03:34 +01:00
|
|
|
const DATE_TYPE = FIELDS.DATETIME.type
|
2021-10-29 14:34:10 +02:00
|
|
|
|
2021-09-29 13:07:35 +02:00
|
|
|
const dispatch = createEventDispatcher()
|
2021-10-12 21:19:32 +02:00
|
|
|
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
|
2023-07-31 16:28:11 +02:00
|
|
|
const { dispatch: gridDispatch } = getContext("grid")
|
2020-08-20 12:19:13 +02:00
|
|
|
|
2023-01-03 17:04:11 +01:00
|
|
|
export let field
|
|
|
|
|
2023-08-11 12:59:40 +02:00
|
|
|
let mounted = false
|
2023-07-31 16:28:11 +02:00
|
|
|
let fieldDefinitions = cloneDeep(FIELDS)
|
2023-01-03 17:04:11 +01:00
|
|
|
let originalName
|
|
|
|
let linkEditDisabled
|
|
|
|
let primaryDisplay
|
|
|
|
let indexes = [...($tables.selected.indexes || [])]
|
2023-09-07 18:02:11 +02:00
|
|
|
let isCreating = undefined
|
2023-01-03 17:04:11 +01:00
|
|
|
|
2023-09-25 15:38:36 +02:00
|
|
|
let relationshipPart1 = PrettyRelationshipDefinitions.Many
|
|
|
|
let relationshipPart2 = PrettyRelationshipDefinitions.One
|
2023-09-25 14:25:23 +02:00
|
|
|
|
|
|
|
let relationshipTableIdSecondary = null
|
2023-01-03 17:04:11 +01:00
|
|
|
let table = $tables.selected
|
|
|
|
let confirmDeleteDialog
|
|
|
|
let savingColumn
|
|
|
|
let deleteColName
|
|
|
|
let jsonSchemaModal
|
2023-07-31 16:28:11 +02:00
|
|
|
let allowedTypes = []
|
2023-01-03 17:04:11 +01:00
|
|
|
let editableColumn = {
|
2020-08-20 12:19:13 +02:00
|
|
|
type: "string",
|
|
|
|
constraints: fieldDefinitions.STRING.constraints,
|
2020-10-14 20:40:01 +02:00
|
|
|
// Initial value for column name in other table for linked records
|
2021-03-23 11:54:03 +01:00
|
|
|
fieldName: $tables.selected.name,
|
2020-08-20 12:19:13 +02:00
|
|
|
}
|
2020-08-07 17:13:57 +02:00
|
|
|
|
2023-01-03 17:04:11 +01:00
|
|
|
$: if (primaryDisplay) {
|
|
|
|
editableColumn.constraints.presence = { allowEmpty: false }
|
|
|
|
}
|
2021-02-15 17:12:39 +01:00
|
|
|
|
2023-09-25 14:25:23 +02:00
|
|
|
$: {
|
2023-09-25 15:38:36 +02:00
|
|
|
if (editableColumn.type === LINK_TYPE && editableColumn.tableId) {
|
2023-09-25 14:25:23 +02:00
|
|
|
// Determine the relationship type based on the selected values of both parts
|
|
|
|
if (
|
2023-09-25 15:38:36 +02:00
|
|
|
relationshipPart1 === PrettyRelationshipDefinitions.Many &&
|
|
|
|
relationshipPart2 === PrettyRelationshipDefinitions.One
|
2023-09-25 14:25:23 +02:00
|
|
|
) {
|
|
|
|
editableColumn.relationshipType = RelationshipType.MANY_TO_ONE
|
|
|
|
} else if (
|
2023-09-25 15:38:36 +02:00
|
|
|
relationshipPart1 === PrettyRelationshipDefinitions.Many &&
|
|
|
|
relationshipPart2 === PrettyRelationshipDefinitions.MANY
|
2023-09-25 14:25:23 +02:00
|
|
|
) {
|
|
|
|
editableColumn.relationshipType = RelationshipType.MANY_TO_MANY
|
|
|
|
} else if (
|
2023-09-25 15:38:36 +02:00
|
|
|
relationshipPart1 === PrettyRelationshipDefinitions.One &&
|
|
|
|
relationshipPart2 === PrettyRelationshipDefinitions.Many
|
2023-09-25 14:25:23 +02:00
|
|
|
) {
|
|
|
|
editableColumn.relationshipType = RelationshipType.ONE_TO_MANY
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the tableId based on the selected table
|
|
|
|
editableColumn.tableId = relationshipTableIdSecondary
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-09 17:36:02 +01:00
|
|
|
const initialiseField = (field, savingColumn) => {
|
2023-09-07 18:02:11 +02:00
|
|
|
isCreating = !field
|
2023-02-09 17:36:02 +01:00
|
|
|
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
|
2023-07-31 16:28:11 +02:00
|
|
|
} 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"
|
|
|
|
}
|
2023-02-09 17:36:02 +01:00
|
|
|
}
|
2023-07-31 16:28:11 +02:00
|
|
|
allowedTypes = getAllowedTypes()
|
2023-09-25 14:25:23 +02:00
|
|
|
|
|
|
|
if (editableColumn.type === LINK_TYPE && editableColumn.tableId) {
|
|
|
|
relationshipTableIdSecondary = editableColumn.tableId
|
|
|
|
if (editableColumn.relationshipType === RelationshipType.MANY_TO_ONE) {
|
2023-09-25 15:38:36 +02:00
|
|
|
relationshipPart1 = PrettyRelationshipDefinitions.Many
|
|
|
|
relationshipPart2 = PrettyRelationshipDefinitions.One
|
2023-09-25 14:25:23 +02:00
|
|
|
} else if (
|
|
|
|
editableColumn.relationshipType === RelationshipType.MANY_TO_MANY
|
|
|
|
) {
|
2023-09-25 15:38:36 +02:00
|
|
|
relationshipPart1 = PrettyRelationshipDefinitions.Many
|
|
|
|
relationshipPart2 = PrettyRelationshipDefinitions.Many
|
2023-09-25 14:25:23 +02:00
|
|
|
} else if (
|
|
|
|
editableColumn.relationshipType === RelationshipType.ONE_TO_MANY
|
|
|
|
) {
|
2023-09-25 15:38:36 +02:00
|
|
|
relationshipPart1 = PrettyRelationshipDefinitions.One
|
|
|
|
relationshipPart2 = PrettyRelationshipDefinitions.Many
|
2023-09-25 14:25:23 +02:00
|
|
|
}
|
|
|
|
}
|
2023-01-03 17:04:11 +01:00
|
|
|
}
|
2020-10-23 18:38:10 +02:00
|
|
|
|
2023-02-09 17:36:02 +01:00
|
|
|
$: initialiseField(field, savingColumn)
|
|
|
|
|
2023-01-03 17:04:11 +01:00
|
|
|
$: checkConstraints(editableColumn)
|
|
|
|
$: required = !!editableColumn?.constraints?.presence || primaryDisplay
|
2020-11-25 16:30:10 +01:00
|
|
|
$: uneditable =
|
2021-05-23 16:06:33 +02:00
|
|
|
$tables.selected?._id === TableNames.USERS &&
|
2023-01-03 17:04:11 +01:00
|
|
|
UNEDITABLE_USER_FIELDS.includes(editableColumn.name)
|
2021-03-15 21:38:55 +01:00
|
|
|
$: invalid =
|
2023-01-03 17:04:11 +01:00
|
|
|
!editableColumn?.name ||
|
|
|
|
(editableColumn?.type === LINK_TYPE && !editableColumn?.tableId) ||
|
2021-11-24 15:55:14 +01:00
|
|
|
Object.keys(errors).length !== 0
|
2023-01-03 17:04:11 +01:00
|
|
|
$: errors = checkErrors(editableColumn)
|
2022-06-07 09:31:00 +02:00
|
|
|
$: datasource = $datasources.list.find(
|
|
|
|
source => source._id === table?.sourceId
|
|
|
|
)
|
2020-08-07 18:41:20 +02:00
|
|
|
|
2023-01-03 17:04:11 +01:00
|
|
|
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
|
|
|
|
}, [])
|
|
|
|
}
|
|
|
|
|
|
|
|
let autoColumnInfo = getAutoColumnInformation()
|
|
|
|
|
|
|
|
$: 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
|
|
|
|
|
2021-02-15 20:59:30 +01:00
|
|
|
// used to select what different options can be displayed for column type
|
2021-11-29 18:11:08 +01:00
|
|
|
$: canBeDisplay =
|
2023-01-03 17:04:11 +01:00
|
|
|
editableColumn?.type !== LINK_TYPE &&
|
|
|
|
editableColumn?.type !== AUTO_TYPE &&
|
2023-01-04 10:14:03 +01:00
|
|
|
editableColumn?.type !== JSON_TYPE &&
|
|
|
|
!editableColumn.autocolumn
|
2021-02-15 20:59:49 +01:00
|
|
|
$: canBeRequired =
|
2023-01-03 17:04:11 +01:00
|
|
|
editableColumn?.type !== LINK_TYPE &&
|
|
|
|
!uneditable &&
|
|
|
|
editableColumn?.type !== AUTO_TYPE &&
|
|
|
|
!editableColumn.autocolumn
|
2021-10-28 20:39:42 +02:00
|
|
|
$: external = table.type === "external"
|
2021-11-05 19:55:36 +01:00
|
|
|
// in the case of internal tables the sourceId will just be undefined
|
|
|
|
$: tableOptions = $tables.list.filter(
|
|
|
|
opt =>
|
2022-12-19 13:22:07 +01:00
|
|
|
opt._id !== $tables.selected._id &&
|
2021-11-05 19:55:36 +01:00
|
|
|
opt.type === table.type &&
|
|
|
|
table.sourceId === opt.sourceId
|
|
|
|
)
|
2021-11-10 16:01:44 +01:00
|
|
|
$: typeEnabled =
|
|
|
|
!originalName ||
|
2023-01-03 17:04:11 +01:00
|
|
|
(originalName &&
|
|
|
|
SWITCHABLE_TYPES.indexOf(editableColumn.type) !== -1 &&
|
|
|
|
!editableColumn?.autocolumn)
|
2021-02-15 20:59:30 +01:00
|
|
|
|
2020-08-07 17:13:57 +02:00
|
|
|
async function saveColumn() {
|
2023-01-03 17:04:11 +01:00
|
|
|
savingColumn = true
|
|
|
|
if (errors?.length) {
|
|
|
|
return
|
2021-02-15 20:59:30 +01:00
|
|
|
}
|
2023-01-03 17:04:11 +01:00
|
|
|
|
|
|
|
let saveColumn = cloneDeep(editableColumn)
|
|
|
|
|
|
|
|
if (saveColumn.type === AUTO_TYPE) {
|
|
|
|
saveColumn = buildAutoColumn(
|
2023-02-06 09:36:25 +01:00
|
|
|
$tables.selected.name,
|
2023-01-03 17:04:11 +01:00
|
|
|
saveColumn.name,
|
|
|
|
saveColumn.subtype
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (saveColumn.type !== LINK_TYPE) {
|
|
|
|
delete saveColumn.fieldName
|
2021-11-26 15:14:53 +01:00
|
|
|
}
|
2021-11-24 15:55:14 +01:00
|
|
|
try {
|
|
|
|
await tables.saveField({
|
|
|
|
originalName,
|
2023-01-03 17:04:11 +01:00
|
|
|
field: saveColumn,
|
2021-11-24 15:55:14 +01:00
|
|
|
primaryDisplay,
|
|
|
|
indexes,
|
|
|
|
})
|
|
|
|
dispatch("updatecolumns")
|
2023-07-31 16:28:11 +02:00
|
|
|
gridDispatch("close-edit-column")
|
|
|
|
|
2023-07-03 11:13:42 +02:00
|
|
|
if (
|
|
|
|
saveColumn.type === LINK_TYPE &&
|
2023-07-21 13:57:47 +02:00
|
|
|
saveColumn.relationshipType === RelationshipType.MANY_TO_MANY
|
2023-07-03 11:13:42 +02:00
|
|
|
) {
|
2023-07-03 12:47:42 +02:00
|
|
|
// Fetching the new tables
|
|
|
|
tables.fetch()
|
|
|
|
// Fetching the new relationships
|
|
|
|
datasources.fetch()
|
2023-07-03 11:13:42 +02:00
|
|
|
}
|
2023-03-31 14:21:53 +02:00
|
|
|
if (originalName) {
|
|
|
|
notifications.success("Column updated successfully")
|
|
|
|
} else {
|
|
|
|
notifications.success("Column created successfully")
|
|
|
|
}
|
2021-11-24 15:55:14 +01:00
|
|
|
} catch (err) {
|
2023-01-03 17:04:11 +01:00
|
|
|
notifications.error(`Error saving column: ${err.message}`)
|
2021-11-24 15:55:14 +01:00
|
|
|
}
|
2020-08-07 17:13:57 +02:00
|
|
|
}
|
2020-08-20 12:19:13 +02:00
|
|
|
|
2022-01-18 10:45:37 +01:00
|
|
|
function cancelEdit() {
|
2023-01-03 17:04:11 +01:00
|
|
|
editableColumn.name = originalName
|
2023-07-31 16:28:11 +02:00
|
|
|
gridDispatch("close-edit-column")
|
2022-01-18 10:45:37 +01:00
|
|
|
}
|
|
|
|
|
2023-02-27 13:17:05 +01:00
|
|
|
async function deleteColumn() {
|
2022-01-24 13:37:22 +01:00
|
|
|
try {
|
2023-01-03 17:04:11 +01:00
|
|
|
editableColumn.name = deleteColName
|
|
|
|
if (editableColumn.name === $tables.selected.primaryDisplay) {
|
2022-01-24 13:37:22 +01:00
|
|
|
notifications.error("You cannot delete the display column")
|
|
|
|
} else {
|
2023-02-27 13:17:05 +01:00
|
|
|
await tables.deleteField(editableColumn)
|
2023-03-31 14:21:53 +02:00
|
|
|
notifications.success(`Column ${editableColumn.name} deleted`)
|
2022-01-24 13:37:22 +01:00
|
|
|
confirmDeleteDialog.hide()
|
|
|
|
dispatch("updatecolumns")
|
2023-07-31 16:28:11 +02:00
|
|
|
gridDispatch("close-edit-column")
|
2022-01-24 13:37:22 +01:00
|
|
|
}
|
|
|
|
} catch (error) {
|
2023-01-03 17:04:11 +01:00
|
|
|
notifications.error(`Error deleting column: ${error.message}`)
|
2020-10-23 18:38:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-17 18:30:58 +01:00
|
|
|
function handleTypeChange(event) {
|
|
|
|
// remove any extra fields that may not be related to this type
|
2023-01-03 17:04:11 +01:00
|
|
|
delete editableColumn.autocolumn
|
|
|
|
delete editableColumn.subtype
|
|
|
|
delete editableColumn.tableId
|
|
|
|
delete editableColumn.relationshipType
|
|
|
|
delete editableColumn.formulaType
|
2021-04-15 20:42:58 +02:00
|
|
|
|
|
|
|
// Add in defaults and initial definition
|
|
|
|
const definition = fieldDefinitions[event.detail?.toUpperCase()]
|
|
|
|
if (definition?.constraints) {
|
2023-01-03 17:04:11 +01:00
|
|
|
editableColumn.constraints = definition.constraints
|
2021-04-15 20:42:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Default relationships many to many
|
2023-01-03 17:04:11 +01:00
|
|
|
if (editableColumn.type === LINK_TYPE) {
|
2023-07-21 13:57:47 +02:00
|
|
|
editableColumn.relationshipType = RelationshipType.MANY_TO_MANY
|
2021-03-01 18:06:08 +01:00
|
|
|
}
|
2023-01-03 17:04:11 +01:00
|
|
|
if (editableColumn.type === FORMULA_TYPE) {
|
|
|
|
editableColumn.formulaType = "dynamic"
|
2022-01-19 19:33:58 +01:00
|
|
|
}
|
2020-08-20 12:19:13 +02:00
|
|
|
}
|
2020-10-07 11:45:26 +02:00
|
|
|
|
|
|
|
function onChangeRequired(e) {
|
2021-04-16 18:12:22 +02:00
|
|
|
const req = e.detail
|
2023-01-03 17:04:11 +01:00
|
|
|
editableColumn.constraints.presence = req ? { allowEmpty: false } : false
|
2020-10-07 11:45:26 +02:00
|
|
|
required = req
|
|
|
|
}
|
2020-10-16 22:50:58 +02:00
|
|
|
|
2021-11-23 19:20:12 +01:00
|
|
|
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()
|
2021-11-22 18:42:41 +01:00
|
|
|
deleteColName = ""
|
2020-10-23 18:38:10 +02:00
|
|
|
}
|
2021-03-01 18:06:08 +01:00
|
|
|
|
2023-07-31 16:28:11 +02:00
|
|
|
function extractColumnNumber(columnName) {
|
|
|
|
const match = columnName.match(/Column (\d+)/)
|
|
|
|
return match ? parseInt(match[1]) : 0
|
|
|
|
}
|
|
|
|
|
2021-10-28 20:39:42 +02:00
|
|
|
function getAllowedTypes() {
|
2023-01-03 17:04:11 +01:00
|
|
|
if (
|
|
|
|
originalName &&
|
|
|
|
ALLOWABLE_STRING_TYPES.indexOf(editableColumn.type) !== -1
|
|
|
|
) {
|
2021-11-10 16:01:44 +01:00
|
|
|
return ALLOWABLE_STRING_OPTIONS
|
|
|
|
} else if (
|
|
|
|
originalName &&
|
2023-01-03 17:04:11 +01:00
|
|
|
ALLOWABLE_NUMBER_TYPES.indexOf(editableColumn.type) !== -1
|
2021-11-10 16:01:44 +01:00
|
|
|
) {
|
|
|
|
return ALLOWABLE_NUMBER_OPTIONS
|
|
|
|
} else if (!external) {
|
2021-10-28 20:39:42 +02:00
|
|
|
return [
|
|
|
|
...Object.values(fieldDefinitions),
|
|
|
|
{ name: "Auto Column", type: AUTO_TYPE },
|
|
|
|
]
|
|
|
|
} else {
|
2023-03-13 19:04:29 +01:00
|
|
|
let fields = [
|
2021-10-28 20:39:42 +02:00
|
|
|
FIELDS.STRING,
|
2022-10-07 12:00:25 +02:00
|
|
|
FIELDS.BARCODEQR,
|
2021-10-28 20:39:42 +02:00
|
|
|
FIELDS.LONGFORM,
|
|
|
|
FIELDS.OPTIONS,
|
|
|
|
FIELDS.DATETIME,
|
|
|
|
FIELDS.NUMBER,
|
|
|
|
FIELDS.BOOLEAN,
|
|
|
|
FIELDS.FORMULA,
|
2023-07-07 16:11:41 +02:00
|
|
|
FIELDS.BIGINT,
|
2021-10-28 20:39:42 +02:00
|
|
|
]
|
2023-03-13 19:04:29 +01:00
|
|
|
// no-sql or a spreadsheet
|
|
|
|
if (!external || table.sql) {
|
|
|
|
fields = [...fields, FIELDS.LINK, FIELDS.ARRAY]
|
|
|
|
}
|
|
|
|
return fields
|
2021-10-28 20:39:42 +02:00
|
|
|
}
|
|
|
|
}
|
2021-10-29 14:34:10 +02:00
|
|
|
|
|
|
|
function checkConstraints(fieldToCheck) {
|
2023-01-03 17:04:11 +01:00
|
|
|
if (!fieldToCheck) {
|
|
|
|
return
|
|
|
|
}
|
2021-10-29 14:34:10 +02:00
|
|
|
// most types need this, just make sure its always present
|
|
|
|
if (fieldToCheck && !fieldToCheck.constraints) {
|
|
|
|
fieldToCheck.constraints = {}
|
|
|
|
}
|
|
|
|
// some string types may have been built by server, may not always have constraints
|
|
|
|
if (fieldToCheck.type === STRING_TYPE && !fieldToCheck.constraints.length) {
|
|
|
|
fieldToCheck.constraints.length = {}
|
|
|
|
}
|
|
|
|
// some number types made server-side will be missing constraints
|
|
|
|
if (
|
|
|
|
fieldToCheck.type === NUMBER_TYPE &&
|
|
|
|
!fieldToCheck.constraints.numericality
|
|
|
|
) {
|
|
|
|
fieldToCheck.constraints.numericality = {}
|
|
|
|
}
|
2021-11-24 18:03:34 +01:00
|
|
|
if (fieldToCheck.type === DATE_TYPE && !fieldToCheck.constraints.datetime) {
|
|
|
|
fieldToCheck.constraints.datetime = {}
|
|
|
|
}
|
2021-10-29 14:34:10 +02:00
|
|
|
}
|
2021-11-24 15:55:14 +01:00
|
|
|
|
|
|
|
function checkErrors(fieldInfo) {
|
2023-01-03 17:04:11 +01:00
|
|
|
if (!editableColumn) {
|
|
|
|
return {}
|
|
|
|
}
|
2021-11-24 15:55:14 +01:00
|
|
|
function inUse(tbl, column, ogName = null) {
|
2023-01-03 17:04:11 +01:00
|
|
|
const parsedColumn = column ? column.toLowerCase().trim() : column
|
|
|
|
|
|
|
|
return Object.keys(tbl?.schema || {}).some(key => {
|
|
|
|
let lowerKey = key.toLowerCase()
|
|
|
|
return lowerKey !== ogName?.toLowerCase() && lowerKey === parsedColumn
|
|
|
|
})
|
2021-11-24 15:55:14 +01:00
|
|
|
}
|
|
|
|
const newError = {}
|
2022-05-24 11:22:20 +02:00
|
|
|
if (!external && fieldInfo.name?.startsWith("_")) {
|
|
|
|
newError.name = `Column name cannot start with an underscore.`
|
2023-07-20 13:21:09 +02:00
|
|
|
} else if (fieldInfo.name && !fieldInfo.name.match(ValidColumnNameRegex)) {
|
2022-11-08 18:16:35 +01:00
|
|
|
newError.name = `Illegal character; must be alpha-numeric.`
|
2022-05-24 11:22:20 +02:00
|
|
|
} else if (PROHIBITED_COLUMN_NAMES.some(name => fieldInfo.name === name)) {
|
2021-11-24 15:55:14 +01:00
|
|
|
newError.name = `${PROHIBITED_COLUMN_NAMES.join(
|
|
|
|
", "
|
|
|
|
)} are not allowed as column names`
|
2022-12-19 13:22:07 +01:00
|
|
|
} else if (inUse($tables.selected, fieldInfo.name, originalName)) {
|
2021-11-24 15:55:14 +01:00
|
|
|
newError.name = `Column name already in use.`
|
|
|
|
}
|
2023-01-03 17:04:11 +01:00
|
|
|
|
|
|
|
if (fieldInfo.type == "auto" && !fieldInfo.subtype) {
|
|
|
|
newError.subtype = `Auto Column requires a type`
|
|
|
|
}
|
|
|
|
|
2021-11-24 15:55:14 +01:00
|
|
|
if (fieldInfo.fieldName && fieldInfo.tableId) {
|
|
|
|
const relatedTable = $tables.list.find(
|
|
|
|
tbl => tbl._id === fieldInfo.tableId
|
|
|
|
)
|
2022-09-21 14:18:04 +02:00
|
|
|
if (inUse(relatedTable, fieldInfo.fieldName) && !originalName) {
|
2021-11-24 15:55:14 +01:00
|
|
|
newError.relatedName = `Column name already in use in table ${relatedTable.name}`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return newError
|
|
|
|
}
|
2023-08-11 12:59:40 +02:00
|
|
|
|
|
|
|
onMount(() => {
|
|
|
|
mounted = true
|
|
|
|
})
|
2020-08-07 17:13:57 +02:00
|
|
|
</script>
|
|
|
|
|
2023-07-31 16:28:11 +02:00
|
|
|
<Layout noPadding gap="S">
|
2023-08-11 12:59:40 +02:00
|
|
|
{#if mounted}
|
|
|
|
<Input
|
2023-08-11 13:00:33 +02:00
|
|
|
autofocus
|
|
|
|
bind:value={editableColumn.name}
|
|
|
|
disabled={uneditable ||
|
|
|
|
(linkEditDisabled && editableColumn.type === LINK_TYPE)}
|
|
|
|
error={errors?.name}
|
|
|
|
/>
|
|
|
|
{/if}
|
2020-10-14 22:43:36 +02:00
|
|
|
<Select
|
2021-11-10 16:01:44 +01:00
|
|
|
disabled={!typeEnabled}
|
2023-01-03 17:04:11 +01:00
|
|
|
bind:value={editableColumn.type}
|
2021-02-17 18:30:58 +01:00
|
|
|
on:change={handleTypeChange}
|
2023-07-31 16:28:11 +02:00
|
|
|
options={allowedTypes}
|
2021-04-15 20:42:58 +02:00
|
|
|
getOptionLabel={field => field.name}
|
2021-05-04 12:32:22 +02:00
|
|
|
getOptionValue={field => field.type}
|
2023-07-31 16:28:11 +02:00
|
|
|
getOptionIcon={field => field.icon}
|
2023-01-03 17:04:11 +01:00
|
|
|
isOptionEnabled={option => {
|
|
|
|
if (option.type == AUTO_TYPE) {
|
|
|
|
return availableAutoColumnKeys?.length > 0
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}}
|
2021-05-04 12:04:42 +02:00
|
|
|
/>
|
2020-08-07 17:13:57 +02:00
|
|
|
|
2023-01-03 17:04:11 +01:00
|
|
|
{#if editableColumn.type === "string"}
|
2020-09-29 14:54:04 +02:00
|
|
|
<Input
|
|
|
|
type="number"
|
2020-09-25 12:35:32 +02:00
|
|
|
label="Max Length"
|
2023-01-03 17:04:11 +01:00
|
|
|
bind:value={editableColumn.constraints.length.maximum}
|
2021-05-04 12:04:42 +02:00
|
|
|
/>
|
2023-01-03 17:04:11 +01:00
|
|
|
{:else if editableColumn.type === "options"}
|
2023-07-31 16:28:11 +02:00
|
|
|
<OptionSelectDnD
|
|
|
|
bind:constraints={editableColumn.constraints}
|
|
|
|
bind:optionColors={editableColumn.optionColors}
|
2021-05-04 12:04:42 +02:00
|
|
|
/>
|
2023-01-03 17:04:11 +01:00
|
|
|
{:else if editableColumn.type === "longform"}
|
2022-02-07 13:04:43 +01:00
|
|
|
<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>
|
|
|
|
|
2022-02-07 13:04:43 +01:00
|
|
|
<Toggle
|
2023-01-03 17:04:11 +01:00
|
|
|
bind:value={editableColumn.useRichText}
|
2022-02-07 13:04:43 +01:00
|
|
|
text="Enable rich text support (markdown)"
|
|
|
|
/>
|
|
|
|
</div>
|
2023-01-03 17:04:11 +01:00
|
|
|
{:else if editableColumn.type === "array"}
|
2023-07-31 16:28:11 +02:00
|
|
|
<OptionSelectDnD
|
|
|
|
bind:constraints={editableColumn.constraints}
|
|
|
|
bind:optionColors={editableColumn.optionColors}
|
2021-08-19 17:54:44 +02:00
|
|
|
/>
|
2023-01-03 17:04:11 +01:00
|
|
|
{:else if editableColumn.type === "datetime" && !editableColumn.autocolumn}
|
2023-07-31 16:28:11 +02:00
|
|
|
<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>
|
2022-06-07 09:31:00 +02:00
|
|
|
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
|
|
|
|
<div>
|
2023-09-19 12:07:31 +02:00
|
|
|
<div class="row">
|
2023-08-17 11:06:49 +02:00
|
|
|
<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>
|
2023-01-03 17:04:11 +01:00
|
|
|
<Toggle
|
|
|
|
bind:value={editableColumn.ignoreTimezones}
|
|
|
|
text="Ignore time zones"
|
|
|
|
/>
|
2022-06-07 09:31:00 +02:00
|
|
|
</div>
|
|
|
|
{/if}
|
2023-01-03 17:04:11 +01:00
|
|
|
{:else if editableColumn.type === "number" && !editableColumn.autocolumn}
|
2023-07-31 16:28:11 +02:00
|
|
|
<div class="split-label">
|
|
|
|
<div class="label-length">
|
2023-09-11 08:52:43 +02:00
|
|
|
<Label size="M">Min Value</Label>
|
2023-07-31 16:28:11 +02:00
|
|
|
</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>
|
2023-01-03 17:04:11 +01:00
|
|
|
{:else if editableColumn.type === "link"}
|
2023-09-25 14:25:23 +02:00
|
|
|
<RelationshipSelector
|
|
|
|
bind:relationshipPart1
|
|
|
|
bind:relationshipPart2
|
|
|
|
bind:relationshipTableIdPrimary={table.name}
|
|
|
|
bind:relationshipTableIdSecondary
|
|
|
|
bind:editableColumn
|
|
|
|
{linkEditDisabled}
|
|
|
|
{tableOptions}
|
|
|
|
{errors}
|
2021-06-04 22:25:34 +02:00
|
|
|
/>
|
2023-01-03 17:04:11 +01:00
|
|
|
{:else if editableColumn.type === FORMULA_TYPE}
|
2022-01-20 19:04:44 +01:00
|
|
|
{#if !table.sql}
|
2023-07-31 16:28:11 +02:00
|
|
|
<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" },
|
|
|
|
]}
|
2023-09-07 18:02:11 +02:00
|
|
|
disabled={!isCreating}
|
2023-07-31 16:28:11 +02:00
|
|
|
getOptionLabel={option => option.label}
|
|
|
|
getOptionValue={option => option.value}
|
|
|
|
tooltip="Dynamic formula are calculated when retrieved, but cannot be filtered or sorted by,
|
2022-01-20 19:04:44 +01:00
|
|
|
while static formula are calculated when the row is saved."
|
2023-07-31 16:28:11 +02:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-01-20 19:04:44 +01:00
|
|
|
{/if}
|
2023-07-31 16:28:11 +02:00
|
|
|
<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>
|
2023-01-03 17:04:11 +01:00
|
|
|
{:else if editableColumn.type === JSON_TYPE}
|
|
|
|
<Button primary text on:click={openJsonSchemaEditor}
|
|
|
|
>Open schema editor</Button
|
|
|
|
>
|
|
|
|
{/if}
|
|
|
|
{#if editableColumn.type === AUTO_TYPE || editableColumn.autocolumn}
|
2021-04-15 20:42:58 +02:00
|
|
|
<Select
|
2022-01-19 19:33:58 +01:00
|
|
|
label="Auto column type"
|
2023-01-03 17:04:11 +01:00
|
|
|
value={editableColumn.subtype}
|
|
|
|
on:change={e => (editableColumn.subtype = e.detail)}
|
|
|
|
options={Object.entries(autoColumnOptions)}
|
2021-04-15 20:42:58 +02:00
|
|
|
getOptionLabel={option => option[1].name}
|
2021-05-04 12:32:22 +02:00
|
|
|
getOptionValue={option => option[0]}
|
2023-01-03 17:04:11 +01:00
|
|
|
disabled={!availableAutoColumnKeys?.length || editableColumn.autocolumn}
|
|
|
|
error={errors?.subtype}
|
2021-05-04 12:04:42 +02:00
|
|
|
/>
|
2020-09-25 12:35:32 +02:00
|
|
|
{/if}
|
2021-04-16 18:12:22 +02:00
|
|
|
|
2023-07-31 16:28:11 +02:00
|
|
|
{#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>
|
2021-11-23 19:20:12 +01:00
|
|
|
<Modal bind:this={jsonSchemaModal}>
|
2021-11-26 18:39:18 +01:00
|
|
|
<JSONSchemaModal
|
2023-01-03 17:04:11 +01:00
|
|
|
schema={editableColumn.schema}
|
|
|
|
json={editableColumn.json}
|
2021-11-29 18:11:08 +01:00
|
|
|
on:save={({ detail }) => {
|
2023-01-03 17:04:11 +01:00
|
|
|
editableColumn.schema = detail.schema
|
|
|
|
editableColumn.json = detail.json
|
2021-11-29 18:11:08 +01:00
|
|
|
}}
|
2021-11-26 18:39:18 +01:00
|
|
|
/>
|
2021-11-23 19:20:12 +01:00
|
|
|
</Modal>
|
2023-07-31 16:28:11 +02:00
|
|
|
|
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}
|
2021-05-04 12:04:42 +02:00
|
|
|
title="Confirm Deletion"
|
2022-01-18 09:53:15 +01:00
|
|
|
disabled={deleteColName !== originalName}
|
2021-11-22 16:26:24 +01:00
|
|
|
>
|
|
|
|
<p>
|
2022-01-18 09:53:15 +01:00
|
|
|
Are you sure you wish to delete the column <b>{originalName}?</b>
|
2021-11-22 16:26:24 +01:00
|
|
|
Your data will be deleted and this action cannot be undone - enter the column
|
|
|
|
name to confirm.
|
|
|
|
</p>
|
2023-02-01 09:20:46 +01:00
|
|
|
<Input bind:value={deleteColName} placeholder={originalName} />
|
2021-11-22 16:26:24 +01:00
|
|
|
</ConfirmDialog>
|
2023-07-31 16:28:11 +02:00
|
|
|
|
|
|
|
<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);
|
|
|
|
}
|
2023-07-31 16:28:11 +02:00
|
|
|
.label-length {
|
|
|
|
flex-basis: 40%;
|
|
|
|
}
|
|
|
|
.input-length {
|
|
|
|
flex-grow: 1;
|
|
|
|
}
|
2023-09-19 12:07:31 +02:00
|
|
|
.row {
|
|
|
|
gap: 8px;
|
|
|
|
display: flex;
|
|
|
|
}
|
2023-07-31 16:28:11 +02:00
|
|
|
</style>
|