diff --git a/.eslintrc.json b/.eslintrc.json index d475bba8d1..9dab2f1a88 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -55,7 +55,9 @@ } ], "no-redeclare": "off", - "@typescript-eslint/no-redeclare": "error" + "@typescript-eslint/no-redeclare": "error", + // have to turn this off to allow function overloading in typescript + "no-dupe-class-members": "off" } }, { @@ -88,7 +90,9 @@ "jest/expect-expect": "off", // We do this in some tests where the behaviour of internal tables // differs to external, but the API is broadly the same - "jest/no-conditional-expect": "off" + "jest/no-conditional-expect": "off", + // have to turn this off to allow function overloading in typescript + "no-dupe-class-members": "off" } }, { diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts index f297d3089f..6694320978 100644 --- a/packages/backend-core/src/context/types.ts +++ b/packages/backend-core/src/context/types.ts @@ -1,4 +1,6 @@ import { IdentityContext, Snippet, VM } from "@budibase/types" +import { OAuth2Client } from "google-auth-library" +import { GoogleSpreadsheet } from "google-spreadsheet" // keep this out of Budibase types, don't want to expose context info export type ContextMap = { @@ -12,4 +14,8 @@ export type ContextMap = { vm?: VM cleanup?: (() => void | Promise)[] snippets?: Snippet[] + googleSheets?: { + oauthClient: OAuth2Client + clients: Record + } } diff --git a/packages/backend-core/src/db/Replication.ts b/packages/backend-core/src/db/Replication.ts index 735c2fa86e..617269df10 100644 --- a/packages/backend-core/src/db/Replication.ts +++ b/packages/backend-core/src/db/Replication.ts @@ -1,14 +1,31 @@ import PouchDB from "pouchdb" import { getPouchDB, closePouchDB } from "./couch" -import { DocumentType } from "../constants" +import { DocumentType } from "@budibase/types" + +enum ReplicationDirection { + TO_PRODUCTION = "toProduction", + TO_DEV = "toDev", +} class Replication { source: PouchDB.Database target: PouchDB.Database + direction: ReplicationDirection | undefined constructor({ source, target }: { source: string; target: string }) { this.source = getPouchDB(source) this.target = getPouchDB(target) + if ( + source.startsWith(DocumentType.APP_DEV) && + target.startsWith(DocumentType.APP) + ) { + this.direction = ReplicationDirection.TO_PRODUCTION + } else if ( + source.startsWith(DocumentType.APP) && + target.startsWith(DocumentType.APP_DEV) + ) { + this.direction = ReplicationDirection.TO_DEV + } } async close() { @@ -40,12 +57,18 @@ class Replication { } const filter = opts.filter + const direction = this.direction + const toDev = direction === ReplicationDirection.TO_DEV delete opts.filter return { ...opts, filter: (doc: any, params: any) => { - if (doc._id && doc._id.startsWith(DocumentType.AUTOMATION_LOG)) { + // don't sync design documents + if (toDev && doc._id?.startsWith("_design")) { + return false + } + if (doc._id?.startsWith(DocumentType.AUTOMATION_LOG)) { return false } if (doc._id === DocumentType.APP_METADATA) { diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts index ef351f7d4d..8194d1aabf 100644 --- a/packages/backend-core/src/db/couch/DatabaseImpl.ts +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -12,6 +12,7 @@ import { isDocument, RowResponse, RowValue, + SQLiteDefinition, SqlQueryBinding, } from "@budibase/types" import { getCouchInfo } from "./connections" @@ -21,6 +22,8 @@ import { ReadStream, WriteStream } from "fs" import { newid } from "../../docIds/newid" import { SQLITE_DESIGN_DOC_ID } from "../../constants" import { DDInstrumentedDatabase } from "../instrumentation" +import { checkSlashesInUrl } from "../../helpers" +import env from "../../environment" const DATABASE_NOT_FOUND = "Database does not exist." @@ -281,25 +284,61 @@ export class DatabaseImpl implements Database { }) } + async _sqlQuery( + url: string, + method: "POST" | "GET", + body?: Record + ): Promise { + url = checkSlashesInUrl(`${this.couchInfo.sqlUrl}/${url}`) + const args: { url: string; method: string; cookie: string; body?: any } = { + url, + method, + cookie: this.couchInfo.cookie, + } + if (body) { + args.body = body + } + return this.performCall(() => { + return async () => { + const response = await directCouchUrlCall(args) + const json = await response.json() + if (response.status > 300) { + throw json + } + return json as T + } + }) + } + async sql( sql: string, parameters?: SqlQueryBinding ): Promise { const dbName = this.name const url = `/${dbName}/${SQLITE_DESIGN_DOC_ID}` - const response = await directCouchUrlCall({ - url: `${this.couchInfo.sqlUrl}/${url}`, - method: "POST", - cookie: this.couchInfo.cookie, - body: { - query: sql, - args: parameters, - }, + return await this._sqlQuery(url, "POST", { + query: sql, + args: parameters, }) - if (response.status > 300) { - throw new Error(await response.text()) + } + + // checks design document is accurate (cleans up tables) + // this will check the design document and remove anything from + // disk which is not supposed to be there + async sqlDiskCleanup(): Promise { + const dbName = this.name + const url = `/${dbName}/_cleanup` + return await this._sqlQuery(url, "POST") + } + + // removes a document from sqlite + async sqlPurgeDocument(docIds: string[] | string): Promise { + if (!Array.isArray(docIds)) { + docIds = [docIds] } - return (await response.json()) as T[] + const dbName = this.name + const url = `/${dbName}/_purge` + return await this._sqlQuery(url, "POST", { docs: docIds }) } async query( @@ -314,6 +353,17 @@ export class DatabaseImpl implements Database { async destroy() { try { + if (env.SQS_SEARCH_ENABLE) { + // delete the design document, then run the cleanup operation + try { + const definition = await this.get( + SQLITE_DESIGN_DOC_ID + ) + await this.remove(SQLITE_DESIGN_DOC_ID, definition._rev) + } finally { + await this.sqlDiskCleanup() + } + } return await this.nano().db.destroy(this.name) } catch (err: any) { // didn't exist, don't worry diff --git a/packages/backend-core/src/db/couch/utils.ts b/packages/backend-core/src/db/couch/utils.ts index 005b02a896..270d953320 100644 --- a/packages/backend-core/src/db/couch/utils.ts +++ b/packages/backend-core/src/db/couch/utils.ts @@ -21,7 +21,7 @@ export async function directCouchUrlCall({ url: string cookie: string method: string - body?: any + body?: Record }) { const params: any = { method: method, diff --git a/packages/backend-core/src/db/instrumentation.ts b/packages/backend-core/src/db/instrumentation.ts index 32ba81ebd8..4e2b147ef3 100644 --- a/packages/backend-core/src/db/instrumentation.ts +++ b/packages/backend-core/src/db/instrumentation.ts @@ -56,12 +56,17 @@ export class DDInstrumentedDatabase implements Database { }) } + remove(idOrDoc: Document): Promise + remove(idOrDoc: string, rev?: string): Promise remove( - id: string | Document, - rev?: string | undefined + idOrDoc: string | Document, + rev?: string ): Promise { return tracer.trace("db.remove", span => { - span?.addTags({ db_name: this.name, doc_id: id }) + span?.addTags({ db_name: this.name, doc_id: idOrDoc }) + const isDocument = typeof idOrDoc === "object" + const id = isDocument ? idOrDoc._id! : idOrDoc + rev = isDocument ? idOrDoc._rev : rev return this.db.remove(id, rev) }) } @@ -160,4 +165,18 @@ export class DDInstrumentedDatabase implements Database { return this.db.sql(sql, parameters) }) } + + sqlPurgeDocument(docIds: string[] | string): Promise { + return tracer.trace("db.sqlPurgeDocument", span => { + span?.addTags({ db_name: this.name }) + return this.db.sqlPurgeDocument(docIds) + }) + } + + sqlDiskCleanup(): Promise { + return tracer.trace("db.sqlDiskCleanup", span => { + span?.addTags({ db_name: this.name }) + return this.db.sqlDiskCleanup() + }) + } } diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index 9ade81b9d7..20ff16739f 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -109,6 +109,7 @@ const environment = { API_ENCRYPTION_KEY: getAPIEncryptionKey(), COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005", COUCH_DB_SQL_URL: process.env.COUCH_DB_SQL_URL || "http://localhost:4006", + SQS_SEARCH_ENABLE: process.env.SQS_SEARCH_ENABLE, COUCH_DB_USERNAME: process.env.COUCH_DB_USER, COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts index f77c6385ba..37547573bd 100644 --- a/packages/backend-core/src/users/db.ts +++ b/packages/backend-core/src/users/db.ts @@ -492,7 +492,7 @@ export class UserDB { await platform.users.removeUser(dbUser) - await db.remove(userId, dbUser._rev) + await db.remove(userId, dbUser._rev!) const creatorsToDelete = (await isCreator(dbUser)) ? 1 : 0 await UserDB.quotas.removeUsers(1, creatorsToDelete) diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js index 124f43ff04..526659cb7a 100644 --- a/packages/bbui/src/Actions/click_outside.js +++ b/packages/bbui/src/Actions/click_outside.js @@ -71,8 +71,8 @@ const handleMouseDown = e => { // Clear any previous listeners in case of multiple down events, and register // a single mouse up listener - document.removeEventListener("mouseup", handleMouseUp) - document.addEventListener("mouseup", handleMouseUp, true) + document.removeEventListener("click", handleMouseUp) + document.addEventListener("click", handleMouseUp, true) } // Global singleton listeners for our events diff --git a/packages/bbui/src/Form/Core/Signature.svelte b/packages/bbui/src/Form/Core/Signature.svelte new file mode 100644 index 0000000000..729d3ac5e2 --- /dev/null +++ b/packages/bbui/src/Form/Core/Signature.svelte @@ -0,0 +1,267 @@ + + +
+ {#if !disabled} +
+ {#if updated && saveIcon} + + { + dispatch("change", toDataUrl()) + }} + /> + + {/if} + {#if signatureFile?.url && !updated} + + { + if (editable) { + clearCanvas() + } + dispatch("clear") + }} + /> + + {/if} +
+ {/if} + {#if !editable && signatureFile?.url} + + {#if !urlFailed} + { + urlFailed = true + }} + /> + {:else} + Could not load signature + {/if} + {:else} +
+ + {#if editable} +
+
+ +
+
+
+ {/if} +
+ {/if} +
+ + diff --git a/packages/bbui/src/Form/Core/index.js b/packages/bbui/src/Form/Core/index.js index 7117b90081..6395fe2fac 100644 --- a/packages/bbui/src/Form/Core/index.js +++ b/packages/bbui/src/Form/Core/index.js @@ -16,3 +16,4 @@ export { default as CoreStepper } from "./Stepper.svelte" export { default as CoreRichTextField } from "./RichTextField.svelte" export { default as CoreSlider } from "./Slider.svelte" export { default as CoreFile } from "./File.svelte" +export { default as CoreSignature } from "./Signature.svelte" diff --git a/packages/bbui/src/Modal/Modal.svelte b/packages/bbui/src/Modal/Modal.svelte index be9c338892..4656be69d1 100644 --- a/packages/bbui/src/Modal/Modal.svelte +++ b/packages/bbui/src/Modal/Modal.svelte @@ -173,6 +173,7 @@ } .spectrum-Modal { + border: 2px solid var(--spectrum-global-color-gray-200); overflow: visible; max-height: none; margin: 40px 0; diff --git a/packages/bbui/src/Modal/ModalContent.svelte b/packages/bbui/src/Modal/ModalContent.svelte index 189ef70c2b..61ceaeb00a 100644 --- a/packages/bbui/src/Modal/ModalContent.svelte +++ b/packages/bbui/src/Modal/ModalContent.svelte @@ -27,6 +27,7 @@ export let secondaryButtonText = undefined export let secondaryAction = undefined export let secondaryButtonWarning = false + export let custom = false const { hide, cancel } = getContext(Context.Modal) let loading = false @@ -63,12 +64,13 @@ class:spectrum-Dialog--medium={size === "M"} class:spectrum-Dialog--large={size === "L"} class:spectrum-Dialog--extraLarge={size === "XL"} + class:no-grid={custom} style="position: relative;" role="dialog" tabindex="-1" aria-modal="true" > -
+
+
+ +
diff --git a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte index ac1c4f91cb..7898e13ec8 100644 --- a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte @@ -61,6 +61,7 @@ selected={automation._id === selectedAutomationId} on:click={() => selectAutomation(automation._id)} selectedBy={$userSelectedResourceMap[automation._id]} + disabled={automation.disabled} > diff --git a/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte b/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte index 1bc4b0f18e..9465374ae2 100644 --- a/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte @@ -39,6 +39,15 @@ >Duplicate Edit + + {automation.disabled ? "Activate" : "Pause"} + Delete diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 879927343f..85ae1924d0 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -364,6 +364,7 @@ value.customType !== "cron" && value.customType !== "triggerSchema" && value.customType !== "automationFields" && + value.type !== "signature_single" && value.type !== "attachment" && value.type !== "attachment_single" ) @@ -456,7 +457,7 @@ value={inputData[key]} options={Object.keys(table?.schema || {})} /> - {:else if value.type === "attachment"} + {:else if value.type === "attachment" || value.type === "signature_single"}
diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte index ab020aad08..b5a54138ca 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte @@ -24,6 +24,11 @@ let table let schemaFields + let attachmentTypes = [ + FieldType.ATTACHMENTS, + FieldType.ATTACHMENT_SINGLE, + FieldType.SIGNATURE_SINGLE, + ] $: { table = $tables.list.find(table => table._id === value?.tableId) @@ -120,15 +125,9 @@ {#if schemaFields.length} {#each schemaFields as [field, schema]} {#if !schema.autocolumn} -
+
-
+
{#if isTestModal} onChange(e, field)} useLabel={false} /> -{:else if schema.type === FieldType.ATTACHMENTS || schema.type === FieldType.ATTACHMENT_SINGLE} +{:else if attachmentTypes.includes(schema.type)}
onChange( { detail: - schema.type === FieldType.ATTACHMENT_SINGLE + schema.type === FieldType.ATTACHMENT_SINGLE || + schema.type === FieldType.SIGNATURE_SINGLE ? e.detail.length > 0 - ? { url: e.detail[0].name, filename: e.detail[0].value } + ? { + url: e.detail[0].name, + filename: e.detail[0].value, + } : {} : e.detail.map(({ name, value }) => ({ url: name, @@ -125,7 +136,8 @@ customButtonText={"Add attachment"} keyPlaceholder={"URL"} valuePlaceholder={"Filename"} - actionButtonDisabled={schema.type === FieldType.ATTACHMENT_SINGLE && + actionButtonDisabled={(schema.type === FieldType.ATTACHMENT_SINGLE || + schema.type === FieldType.SIGNATURE) && Object.keys(value[field]).length >= 1} />
diff --git a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte index 4ff8ae994b..1a6bb86113 100644 --- a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte +++ b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte @@ -1,4 +1,5 @@ + { + const signatureFile = sigCanvas.toFile() + + let attachRequest = new FormData() + attachRequest.append("file", signatureFile) + + try { + const uploadReq = await API.uploadBuilderAttachment(attachRequest) + const [signatureAttachment] = uploadReq + value = signatureAttachment + } catch (error) { + $notifications.error(error.message || "Failed to save signature") + value = [] + } + }} + title={meta.name} + {value} + bind:this={signatureModal} +/> + {#if type === "options" && meta.constraints.inclusion.length !== 0} {/if} + + diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index 26972ede16..e8e1008e3c 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -1,6 +1,6 @@ - { - if (!open) { - popover.show() - open = true - } - }} -/> + { - drawers = [] - $draggable.actions.select(componentInstance._id) - }} - on:close={() => { - open = false - if ($draggable.selected === componentInstance._id) { - $draggable.actions.select() - } - }} + open={isOpen} + on:close={close} {anchor} align="left-outside" showPopover={drawers.length === 0} clickOutsideOverride={drawers.length > 0} maxHeight={600} offset={18} - handlePostionUpdate={customPositionHandler} > diff --git a/packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js b/packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js deleted file mode 100644 index 2dc3f60185..0000000000 --- a/packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js +++ /dev/null @@ -1,18 +0,0 @@ -export const customPositionHandler = (anchorBounds, eleBounds, cfg) => { - let { left, top, offset } = cfg - let percentageOffset = 30 - // left-outside - left = anchorBounds.left - eleBounds.width - (offset || 5) - - // shift up from the anchor, if space allows - let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset - let defaultTop = anchorBounds.top - offsetPos - - if (window.innerHeight - defaultTop < eleBounds.height) { - top = window.innerHeight - eleBounds.height - 5 - } else { - top = anchorBounds.top - offsetPos - } - - return { ...cfg, left, top } -} diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte index 771bcf20e0..27590a9858 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte @@ -1,5 +1,5 @@
- - onRowClick?.({ row: e.detail })} - /> - + onRowClick?.({ row: e.detail })} + />
@@ -185,14 +189,9 @@ border: 1px solid var(--spectrum-global-color-gray-300); border-radius: 4px; overflow: hidden; + height: 410px; } div.in-builder :global(*) { pointer-events: none; } - span { - display: contents; - } - span :global(.grid) { - height: var(--height); - } diff --git a/packages/client/src/components/app/blocks/FormBlockComponent.svelte b/packages/client/src/components/app/blocks/FormBlockComponent.svelte index 31610f79ac..396dfcf808 100644 --- a/packages/client/src/components/app/blocks/FormBlockComponent.svelte +++ b/packages/client/src/components/app/blocks/FormBlockComponent.svelte @@ -15,6 +15,7 @@ [FieldType.BOOLEAN]: "booleanfield", [FieldType.LONGFORM]: "longformfield", [FieldType.DATETIME]: "datetimefield", + [FieldType.SIGNATURE_SINGLE]: "signaturesinglefield", [FieldType.ATTACHMENTS]: "attachmentfield", [FieldType.ATTACHMENT_SINGLE]: "attachmentsinglefield", [FieldType.LINK]: "relationshipfield", diff --git a/packages/client/src/components/app/forms/SignatureField.svelte b/packages/client/src/components/app/forms/SignatureField.svelte new file mode 100644 index 0000000000..bdae148368 --- /dev/null +++ b/packages/client/src/components/app/forms/SignatureField.svelte @@ -0,0 +1,129 @@ + + + + + + {#if fieldState} + {#if (Array.isArray(fieldState?.value) && !fieldState?.value?.length) || !fieldState?.value} + { + if (!$builderStore.inBuilder) { + modal.show() + } + }} + > + Add signature + + {:else} +
+ +
+ {/if} + {/if} +
+ + diff --git a/packages/client/src/components/app/forms/index.js b/packages/client/src/components/app/forms/index.js index 15966c3765..391b5fa19f 100644 --- a/packages/client/src/components/app/forms/index.js +++ b/packages/client/src/components/app/forms/index.js @@ -16,5 +16,6 @@ export { default as formstep } from "./FormStep.svelte" export { default as jsonfield } from "./JSONField.svelte" export { default as s3upload } from "./S3Upload.svelte" export { default as codescanner } from "./CodeScannerField.svelte" +export { default as signaturesinglefield } from "./SignatureField.svelte" export { default as bbreferencefield } from "./BBReferenceField.svelte" export { default as bbreferencesinglefield } from "./BBReferenceSingleField.svelte" diff --git a/packages/client/src/components/app/forms/validation.js b/packages/client/src/components/app/forms/validation.js index bdd7213cb0..46a5330cf3 100644 --- a/packages/client/src/components/app/forms/validation.js +++ b/packages/client/src/components/app/forms/validation.js @@ -200,6 +200,17 @@ const parseType = (value, type) => { return value } + // Parse attachment/signature single, treating no key as null + if ( + type === FieldTypes.ATTACHMENT_SINGLE || + type === FieldTypes.SIGNATURE_SINGLE + ) { + if (!value?.key) { + return null + } + return value + } + // Parse links, treating no elements as null if (type === FieldTypes.LINK) { if (!Array.isArray(value) || !value.length) { @@ -246,10 +257,8 @@ const maxLengthHandler = (value, rule) => { // Evaluates a max file size (MB) constraint const maxFileSizeHandler = (value, rule) => { const limit = parseType(rule.value, "number") - return ( - value == null || - !value.some(attachment => attachment.size / 1000000 > limit) - ) + const check = attachment => attachment.size / 1000000 > limit + return value == null || !(value?.key ? check(value) : value.some(check)) } // Evaluates a max total upload size (MB) constraint @@ -257,8 +266,11 @@ const maxUploadSizeHandler = (value, rule) => { const limit = parseType(rule.value, "number") return ( value == null || - value.reduce((acc, currentItem) => acc + currentItem.size, 0) / 1000000 <= - limit + (value?.key + ? value.size / 1000000 <= limit + : value.reduce((acc, currentItem) => acc + currentItem.size, 0) / + 1000000 <= + limit) ) } diff --git a/packages/frontend-core/src/components/SignatureModal.svelte b/packages/frontend-core/src/components/SignatureModal.svelte new file mode 100644 index 0000000000..8132c5bced --- /dev/null +++ b/packages/frontend-core/src/components/SignatureModal.svelte @@ -0,0 +1,59 @@ + + + + { + onConfirm(canvas) + }} + > +
+ {title} +
+ +
+
+ + diff --git a/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte b/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte index ea452c86a8..f485593c46 100644 --- a/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte @@ -8,7 +8,6 @@ export let onChange export let readonly = false export let api - export let invertX = false export let schema export let maximum @@ -92,13 +91,7 @@
{#if isOpen} - +
{#if isOpen} - + { - columns.actions.changePrimaryDisplay(column.name) + datasource.actions.changePrimaryDisplay(column.name) open = false } const hideColumn = () => { - columns.update(state => { - const index = state.findIndex(col => col.name === column.name) - state[index].visible = false - return state.slice() - }) - columns.actions.saveChanges() + datasource.actions.addSchemaMutation(column.name, { visible: false }) + datasource.actions.saveSchemaMutations() open = false } diff --git a/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte b/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte index 6a05b4ae49..7829e5da7d 100644 --- a/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte @@ -8,7 +8,6 @@ export let onChange export let readonly = false export let api - export let invertX = false let textarea let isOpen = false @@ -67,7 +66,7 @@
{#if isOpen} - +