diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml index 553298c9e3..0eb1cbbba1 100644 --- a/.github/workflows/release-master.yml +++ b/.github/workflows/release-master.yml @@ -79,7 +79,6 @@ jobs: - name: Build/release Docker images run: | docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - yarn build yarn build:docker env: DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} diff --git a/lerna.json b/lerna.json index f8edafa311..99d61cf6dc 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.0.999-alpha.38", + "version": "2.5.6-alpha.28", "npmClient": "yarn", "packages": [ "packages/backend-core", diff --git a/package.json b/package.json index 0689c81699..4deb5a5bed 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "dev": "yarn run kill-all && lerna link && lerna run --stream --parallel dev:builder --concurrency 1 --stream", "dev:noserver": "yarn run kill-builder && lerna link && lerna run --stream dev:stack:up && lerna run --stream --parallel dev:builder --concurrency 1 --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker", "dev:server": "yarn run kill-server && lerna run --stream --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server", - "dev:built": "cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built", + "dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built", "test": "lerna run --stream test --stream", "lint:eslint": "eslint packages && eslint qa-core", "lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"", diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index 68f056c80c..0d413b8fa9 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -1,3 +1,5 @@ +import { existsSync, readFileSync } from "fs" + function isTest() { return isCypress() || isJest() } @@ -45,6 +47,35 @@ function httpLogging() { return process.env.HTTP_LOGGING } +function findVersion() { + function findFileInAncestors( + fileName: string, + currentDir: string + ): string | null { + const filePath = `${currentDir}/${fileName}` + if (existsSync(filePath)) { + return filePath + } + + const parentDir = `${currentDir}/..` + if (parentDir === currentDir) { + // reached root directory + return null + } + + return findFileInAncestors(fileName, parentDir) + } + + try { + const packageJsonFile = findFileInAncestors("package.json", process.cwd()) + const content = readFileSync(packageJsonFile!, "utf-8") + const version = JSON.parse(content).version + return version + } catch { + throw new Error("Cannot find a valid version in its package.json") + } +} + const environment = { isTest, isJest, @@ -122,6 +153,7 @@ const environment = { ENABLE_SSO_MAINTENANCE_MODE: selfHosted ? process.env.ENABLE_SSO_MAINTENANCE_MODE : false, + VERSION: findVersion(), _set(key: any, value: any) { process.env[key] = value // @ts-ignore diff --git a/packages/backend-core/src/events/identification.ts b/packages/backend-core/src/events/identification.ts index 10b590b353..5c02e5db9e 100644 --- a/packages/backend-core/src/events/identification.ts +++ b/packages/backend-core/src/events/identification.ts @@ -23,8 +23,6 @@ import * as installation from "../installation" import * as configs from "../configs" import { withCache, TTL, CacheKey } from "../cache/generic" -const pkg = require("../../package.json") - /** * An identity can be: * - account user (Self host) @@ -102,7 +100,7 @@ const identifyInstallationGroup = async ( const id = installId const type = IdentityType.INSTALLATION const hosting = getHostingFromEnv() - const version = pkg.version + const version = env.VERSION const environment = getDeploymentEnvironment() const group: InstallationGroup = { diff --git a/packages/backend-core/src/events/processors/posthog/PosthogProcessor.ts b/packages/backend-core/src/events/processors/posthog/PosthogProcessor.ts index 0dbe70d543..d37b85a9b8 100644 --- a/packages/backend-core/src/events/processors/posthog/PosthogProcessor.ts +++ b/packages/backend-core/src/events/processors/posthog/PosthogProcessor.ts @@ -4,7 +4,6 @@ import { EventProcessor } from "../types" import env from "../../../environment" import * as context from "../../../context" import * as rateLimiting from "./rateLimiting" -const pkg = require("../../../../package.json") const EXCLUDED_EVENTS: Event[] = [ Event.USER_UPDATED, @@ -49,7 +48,7 @@ export default class PosthogProcessor implements EventProcessor { properties = this.clearPIIProperties(properties) - properties.version = pkg.version + properties.version = env.VERSION properties.service = env.SERVICE properties.environment = identity.environment properties.hosting = identity.hosting diff --git a/packages/backend-core/src/installation.ts b/packages/backend-core/src/installation.ts index dd2461441c..17eda2004d 100644 --- a/packages/backend-core/src/installation.ts +++ b/packages/backend-core/src/installation.ts @@ -6,8 +6,7 @@ import { Installation, IdentityType, Database } from "@budibase/types" import * as context from "./context" import semver from "semver" import { bustCache, withCache, TTL, CacheKey } from "./cache/generic" - -const pkg = require("../package.json") +import environment from "./environment" export const getInstall = async (): Promise => { return withCache(CacheKey.INSTALLATION, TTL.ONE_DAY, getInstallFromDB, { @@ -18,7 +17,7 @@ async function createInstallDoc(platformDb: Database) { const install: Installation = { _id: StaticDatabases.PLATFORM_INFO.docs.install, installId: newid(), - version: pkg.version, + version: environment.VERSION, } try { const resp = await platformDb.put(install) @@ -80,7 +79,7 @@ export const checkInstallVersion = async (): Promise => { const install = await getInstall() const currentVersion = install.version - const newVersion = pkg.version + const newVersion = environment.VERSION if (currentVersion !== newVersion) { const isUpgrade = semver.gt(newVersion, currentVersion) diff --git a/packages/backend-core/tsconfig.build.json b/packages/backend-core/tsconfig.build.json index 12f8255a7c..bfbed31e23 100644 --- a/packages/backend-core/tsconfig.build.json +++ b/packages/backend-core/tsconfig.build.json @@ -10,15 +10,11 @@ "incremental": true, "sourceMap": true, "declaration": true, - "types": [ "node", "jest" ], + "types": ["node", "jest"], "outDir": "dist", "skipLibCheck": true }, - "include": [ - "**/*.js", - "**/*.ts", - "package.json" - ], + "include": ["**/*.js", "**/*.ts"], "exclude": [ "node_modules", "dist", @@ -26,4 +22,4 @@ "**/*.spec.js", "__mocks__" ] -} \ No newline at end of file +} diff --git a/packages/builder/src/api.js b/packages/builder/src/api.js index 0f61dba4e1..37894d9bbc 100644 --- a/packages/builder/src/api.js +++ b/packages/builder/src/api.js @@ -10,7 +10,10 @@ import { auth } from "./stores/portal" export const API = createAPIClient({ attachHeaders: headers => { // Attach app ID header from store - headers["x-budibase-app-id"] = get(store).appId + let appId = get(store).appId + if (appId) { + headers["x-budibase-app-id"] = appId + } // Add csrf token if authenticated const user = get(auth).user diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index acb3dacf93..dfe30a3711 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -42,13 +42,14 @@ {/if} - {#if isUsersTable} - - {/if} {#if !isInternal} {/if} - + {#if isUsersTable} + + {:else} + + {/if} diff --git a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte index 4d5faca4c6..bc8e0c5318 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte @@ -16,7 +16,7 @@ - Manage access + Access - Create view + Add view diff --git a/packages/builder/src/components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte b/packages/builder/src/components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte index e45ffc586e..12ca61722f 100644 --- a/packages/builder/src/components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte @@ -1,7 +1,7 @@ - rows.actions.refreshRow(e.detail)} /> + rows.actions.refreshRow(e.detail)} + on:deleteRows={deleteRow} + /> diff --git a/packages/builder/src/components/design/settings/componentSettings.js b/packages/builder/src/components/design/settings/componentSettings.js index 65a21f368d..39a82c20dd 100644 --- a/packages/builder/src/components/design/settings/componentSettings.js +++ b/packages/builder/src/components/design/settings/componentSettings.js @@ -21,6 +21,7 @@ import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCom import ColumnEditor from "./controls/ColumnEditor/ColumnEditor.svelte" import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte" import BarButtonList from "./controls/BarButtonList.svelte" +import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte" const componentMap = { text: DrawerBindableCombobox, @@ -43,6 +44,7 @@ const componentMap = { section: SectionSelect, filter: FilterEditor, url: URLSelect, + fieldConfiguration: FieldConfiguration, columns: ColumnEditor, "columns/basic": BasicColumnEditor, "field/sortable": SortableFieldSelect, diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration.svelte new file mode 100644 index 0000000000..59340d4898 --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration.svelte @@ -0,0 +1,91 @@ + + +Configure columns + + + Configure the columns in your {subject.toLowerCase()}. + + + + diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/CellEditor.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/CellEditor.svelte new file mode 100644 index 0000000000..e70decc035 --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/CellEditor.svelte @@ -0,0 +1,26 @@ + + + + + + "{column.name}" field validation + + + +
+ +
+
+
diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/ColumnDrawer.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/ColumnDrawer.svelte new file mode 100644 index 0000000000..316bf56da3 --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/ColumnDrawer.svelte @@ -0,0 +1,202 @@ + + + +
+ + {#if columns?.length} + +
+
+ + +
+
+
+
+ {#each columns as column (column.id)} +
+
(dragDisabled = false)} + > + +
+ + + removeColumn(column.id)} + disabled={columns.length === 1} + /> +
+ {/each} +
+ + {:else} +
+
+ Add columns to be included in your form below. +
+
+ {/if} +
+
+ + + {#if columns?.length} + + {/if} +
+
+ +
+ + + diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte new file mode 100644 index 0000000000..f9127460e2 --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte @@ -0,0 +1,89 @@ + + +Configure fields + + + Configure the fields in your form. + + + + diff --git a/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationDrawer.svelte b/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationDrawer.svelte index 8d9ca7e0cd..0bff2ccce6 100644 --- a/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationDrawer.svelte +++ b/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationDrawer.svelte @@ -16,6 +16,7 @@ import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import { generate } from "shortid" + export let fieldName = null export let rules = [] export let bindings = [] export let type @@ -124,7 +125,7 @@ } $: dataSourceSchema = getDataSourceSchema($currentAsset, $selectedComponent) - $: field = $selectedComponent?.field + $: field = fieldName || $selectedComponent?.field $: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {}) $: fieldType = type?.split("/")[1] || "string" $: constraintOptions = getConstraintsForType(fieldType) @@ -140,8 +141,12 @@ const formParent = findClosestMatchingComponent( asset.props, component._id, - component => component._component.endsWith("/form") + component => + component._component.endsWith("/form") || + component._component.endsWith("/formblock") || + component._component.endsWith("/tableblock") ) + return getSchemaForDatasource(asset, formParent?.dataSource) } diff --git a/packages/builder/src/pages/builder/portal/overview/[appId]/_layout.svelte b/packages/builder/src/pages/builder/portal/overview/[appId]/_layout.svelte index 74649ff3fd..806589e421 100644 --- a/packages/builder/src/pages/builder/portal/overview/[appId]/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/overview/[appId]/_layout.svelte @@ -33,7 +33,7 @@ import * as routify from "@roxi/routify" import { onDestroy } from "svelte" - // Keep URL and state in sync for selected screen ID + // Keep URL and state in sync for selected app ID const stopSyncing = syncURLToState({ urlParam: "appId", stateKey: "selectedAppId", @@ -47,6 +47,7 @@ let deletionModal let exportPublishedVersion = false let deletionConfirmationAppName + let loaded = false $: app = $overview.selectedApp $: appId = $overview.selectedAppId @@ -56,10 +57,12 @@ $: lockedByYou = $auth.user.email === app?.lockedBy?.email const initialiseApp = async appId => { + loaded = false try { const pkg = await API.fetchAppPackage(appId) await store.actions.initialise(pkg) await API.syncApp(appId) + loaded = true } catch (error) { notifications.error("Error initialising app overview") $goto("../../") @@ -228,7 +231,9 @@ active={$isActive("./version")} /> - + {#if loaded} + + {/if} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index d64f25f497..cca2c91862 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -3,17 +3,17 @@ import { logging } from "@budibase/backend-core" logging.disableLogger() import "./prebuilds" import "./environment" +import { env } from "@budibase/backend-core" import { getCommands } from "./options" import { Command } from "commander" import { getHelpDescription } from "./utils" -const json = require("../package.json") // add hosting config async function init() { const program = new Command() .addHelpCommand("help", getHelpDescription("Help with Budibase commands.")) .helpOption(false) - .version(json.version) + .version(env.VERSION) // add commands for (let command of getCommands()) { command.configure(program) diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 61e83c7c45..0fa0bd8f5a 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -15,7 +15,7 @@ "require": ["tsconfig-paths/register"], "swc": true }, - - "include": ["src/**/*", "package.json"], + "references": [{ "path": "../types" }, { "path": "../backend-core" }], + "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } diff --git a/packages/client/manifest.json b/packages/client/manifest.json index f27d090617..84ffca314c 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -4435,6 +4435,48 @@ "key": "row" } ] + }, + { + "label": "Fields", + "type": "fieldConfiguration", + "key": "sidePanelFields", + "nested": true, + "dependsOn": { + "setting": "clickBehaviour", + "value": "details" + } + }, + { + "label": "Show delete", + "type": "boolean", + "key": "sidePanelShowDelete", + "nested": true, + "dependsOn": { + "setting": "clickBehaviour", + "value": "details" + } + }, + { + "label": "Save label", + "type": "text", + "key": "sidePanelSaveLabel", + "defaultValue": "Save", + "nested": true, + "dependsOn": { + "setting": "clickBehaviour", + "value": "details" + } + }, + { + "label": "Delete label", + "type": "text", + "key": "sidePanelDeleteLabel", + "defaultValue": "Delete", + "nested": true, + "dependsOn": { + "setting": "clickBehaviour", + "value": "details" + } } ] }, @@ -4979,7 +5021,7 @@ "name": "Fields", "settings": [ { - "type": "multifield", + "type": "fieldConfiguration", "label": "Fields", "key": "fields", "selectAllFields": true @@ -5028,6 +5070,17 @@ "invert": true } }, + { + "type": "text", + "key": "saveButtonLabel", + "label": "Save button label", + "nested": true, + "defaultValue": "Save", + "dependsOn": { + "setting": "showSaveButton", + "value": true + } + }, { "type": "boolean", "label": "Allow delete", @@ -5038,6 +5091,17 @@ "value": "Update" } }, + { + "type": "text", + "key": "deleteButtonLabel", + "label": "Delete button label", + "nested": true, + "defaultValue": "Delete", + "dependsOn": { + "setting": "showDeleteButton", + "value": true + } + }, { "type": "url", "label": "Navigate after button press", diff --git a/packages/client/src/components/app/blocks/TableBlock.svelte b/packages/client/src/components/app/blocks/TableBlock.svelte index 26e44922dd..e45b53880d 100644 --- a/packages/client/src/components/app/blocks/TableBlock.svelte +++ b/packages/client/src/components/app/blocks/TableBlock.svelte @@ -26,6 +26,10 @@ export let titleButtonClickBehaviour export let onClickTitleButton export let noRowsMessage + export let sidePanelFields + export let sidePanelShowDelete + export let sidePanelSaveLabel + export let sidePanelDeleteLabel const { fetchDatasourceSchema, API } = getContext("sdk") const stateKey = `ID_${generate()}` @@ -241,10 +245,12 @@ props={{ dataSource, showSaveButton: true, - showDeleteButton: true, + showDeleteButton: sidePanelShowDelete, + saveButtonLabel: sidePanelSaveLabel, + deleteButtonLabel: sidePanelDeleteLabel, actionType: "Update", rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`, - fields: normalFields, + fields: sidePanelFields || normalFields, title: editTitle, labelPosition: "left", }} @@ -266,8 +272,9 @@ dataSource, showSaveButton: true, showDeleteButton: false, + saveButtonLabel: sidePanelSaveLabel, actionType: "Create", - fields: normalFields, + fields: sidePanelFields || normalFields, title: "Create Row", labelPosition: "left", }} diff --git a/packages/client/src/components/app/blocks/form/FormBlock.svelte b/packages/client/src/components/app/blocks/form/FormBlock.svelte index a05a7e141c..6874c23cf4 100644 --- a/packages/client/src/components/app/blocks/form/FormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/FormBlock.svelte @@ -12,6 +12,8 @@ export let fields export let labelPosition export let title + export let saveButtonLabel + export let deleteButtonLabel export let showSaveButton export let showDeleteButton export let rowId @@ -20,10 +22,40 @@ const { fetchDatasourceSchema } = getContext("sdk") + const convertOldFieldFormat = fields => { + if (typeof fields?.[0] === "string") { + return fields.map(field => ({ name: field, displayName: field })) + } + + return fields + } + + const getDefaultFields = (fields, schema) => { + if (schema && (!fields || fields.length === 0)) { + const defaultFields = [] + + Object.values(schema).forEach(field => { + if (field.autocolumn) return + + defaultFields.push({ + name: field.name, + displayName: field.name, + }) + }) + + return defaultFields + } + + return fields + } + let schema let providerId let repeaterId + $: formattedFields = convertOldFieldFormat(fields) + $: fieldsOrDefault = getDefaultFields(formattedFields, schema) + $: fetchSchema(dataSource) $: dataProvider = `{{ literal ${safe(providerId)} }}` $: filter = [ @@ -46,9 +78,11 @@ actionType, size, disabled, - fields, + fields: fieldsOrDefault, labelPosition, title, + saveButtonLabel, + deleteButtonLabel, showSaveButton, showDeleteButton, schema, diff --git a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte index da5345e6ad..b89ec4bcab 100644 --- a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte @@ -11,6 +11,8 @@ export let fields export let labelPosition export let title + export let saveButtonLabel + export let deleteButtonLabel export let showSaveButton export let showDeleteButton export let schema @@ -33,6 +35,12 @@ let formId $: onSave = [ + { + "##eventHandlerType": "Validate Form", + parameters: { + componentId: formId, + }, + }, { "##eventHandlerType": "Save Row", parameters: { @@ -163,7 +171,7 @@ {#each fields as field, idx} - {#if getComponentForField(field)} + {#if getComponentForField(field.name)} diff --git a/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte b/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte index 2eb191e113..4d830723c2 100644 --- a/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/AttachmentCell.svelte @@ -72,6 +72,7 @@ api = { focus: () => open(), blur: () => close(), + isActive: () => isOpen, onKeyDown, } }) diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index d97edabc01..5a2e02340f 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -48,8 +48,9 @@ } const cellAPI = { - focus: () => api?.focus(), - blur: () => api?.blur(), + focus: () => api?.focus?.(), + blur: () => api?.blur?.(), + isActive: () => api?.isActive?.() ?? false, onKeyDown: (...params) => api?.onKeyDown(...params), isReadonly: () => readonly, getType: () => column.schema.type, @@ -67,6 +68,7 @@ {rowIdx} {focused} {selectedUser} + {readonly} error={$error} on:click={() => focusedCellId.set(cellId)} on:contextmenu={e => menu.actions.open(cellId, e)} diff --git a/packages/frontend-core/src/components/grid/cells/GridCell.svelte b/packages/frontend-core/src/components/grid/cells/GridCell.svelte index 52dbed641c..dfc53f6f0c 100644 --- a/packages/frontend-core/src/components/grid/cells/GridCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/GridCell.svelte @@ -8,6 +8,7 @@ export let rowIdx export let defaultHeight = false export let center = false + export let readonly = false $: style = getStyle(width, selectedUser) @@ -27,6 +28,7 @@ class:focused class:error class:center + class:readonly class:default-height={defaultHeight} class:selected-other={selectedUser != null} on:focus @@ -121,7 +123,8 @@ .cell:hover { cursor: default; } - .cell.highlighted:not(.focused) { + .cell.highlighted:not(.focused), + .cell.focused.readonly { --cell-background: var(--cell-background-hover); } .cell.selected:not(.focused) { diff --git a/packages/frontend-core/src/components/grid/cells/GutterCell.svelte b/packages/frontend-core/src/components/grid/cells/GutterCell.svelte new file mode 100644 index 0000000000..d9fd09fb6c --- /dev/null +++ b/packages/frontend-core/src/components/grid/cells/GutterCell.svelte @@ -0,0 +1,127 @@ + + + +
+ {#if $$slots.default} + + {:else} +
+ +
+ {#if !disableNumber} +
+ {row.__idx + 1} +
+ {/if} + {/if} + {#if $config.allowExpandRows} +
+ +
+ {/if} +
+
+ + diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte index 890f314e2e..165711c51f 100644 --- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte @@ -20,6 +20,7 @@ ui, columns, } = getContext("grid") + const bannedDisplayColumnTypes = [ "link", "array", @@ -92,6 +93,16 @@ columns.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() + open = false + }
{/if} -
(open = true)} - > +
(open = true)}> Move right + Hide column @@ -194,6 +202,10 @@ .header-cell { display: flex; } + .header-cell:not(.sticky):hover, + .header-cell:not(.sticky) :global(.cell:hover) { + cursor: grab; + } .header-cell.disabled { pointer-events: none; } @@ -202,9 +214,6 @@ gap: calc(2 * var(--cell-spacing)); background: var(--spectrum-global-color-gray-100); } - .header-cell.sorted :global(.cell) { - background: var(--spectrum-global-color-gray-200); - } .name { flex: 1 1 auto; diff --git a/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte b/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte index f41e206d1d..00e12dc6a3 100644 --- a/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/LongFormCell.svelte @@ -31,7 +31,9 @@ isOpen = true await tick() textarea.focus() - textarea.setSelectionRange(0, 0) + if (value?.length > 100) { + textarea.setSelectionRange(0, 0) + } } const close = () => { @@ -43,6 +45,7 @@ api = { focus: () => open(), blur: () => close(), + isActive: () => isOpen, onKeyDown, } }) diff --git a/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte b/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte index 37c37fcd70..f3b6b9b59d 100644 --- a/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/OptionsCell.svelte @@ -73,6 +73,7 @@ api = { focus: open, blur: close, + isActive: () => isOpen, onKeyDown, } }) diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte index 08dabe0fec..90182e9983 100644 --- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte @@ -235,6 +235,7 @@ api = { focus: open, blur: close, + isActive: () => isOpen, onKeyDown, } }) diff --git a/packages/frontend-core/src/components/grid/cells/TextCell.svelte b/packages/frontend-core/src/components/grid/cells/TextCell.svelte index 73fb83dfbf..533b030b5c 100644 --- a/packages/frontend-core/src/components/grid/cells/TextCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/TextCell.svelte @@ -33,6 +33,7 @@ api = { focus: () => input?.focus(), blur: () => input?.blur(), + isActive: () => active, onKeyDown, } }) diff --git a/packages/frontend-core/src/components/grid/controls/AddColumnButton.svelte b/packages/frontend-core/src/components/grid/controls/AddColumnButton.svelte index e04c1ee627..6ad241eb65 100644 --- a/packages/frontend-core/src/components/grid/controls/AddColumnButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/AddColumnButton.svelte @@ -12,5 +12,5 @@ on:click={() => dispatch("add-column")} disabled={!$config.allowAddColumns} > - Create column + Add column diff --git a/packages/frontend-core/src/components/grid/controls/AddRowButton.svelte b/packages/frontend-core/src/components/grid/controls/AddRowButton.svelte index 6b8e259999..71062d6a1a 100644 --- a/packages/frontend-core/src/components/grid/controls/AddRowButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/AddRowButton.svelte @@ -9,10 +9,10 @@ icon="TableRowAddBottom" quiet size="M" - on:click={() => dispatch("add-row")} + on:click={() => dispatch("add-row-inline")} disabled={!loaded || !$config.allowAddRows || (!$columns.length && !$stickyColumn)} > - Create row + Add row diff --git a/packages/frontend-core/src/components/grid/controls/ColumnWidthButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnWidthButton.svelte new file mode 100644 index 0000000000..754aebbb51 --- /dev/null +++ b/packages/frontend-core/src/components/grid/controls/ColumnWidthButton.svelte @@ -0,0 +1,91 @@ + + +
+ (open = !open)} + selected={open} + disabled={!allCols.length} + > + Width + +
+ + +
+ {#each sizeOptions as option} + changeColumnWidth(option.size)} + selected={option.selected} + > + {option.label} + + {/each} + {#if custom} + Custom + {/if} +
+
+ + diff --git a/packages/frontend-core/src/components/grid/controls/DeleteButton.svelte b/packages/frontend-core/src/components/grid/controls/DeleteButton.svelte index 9380228f2c..8ca5f0920d 100644 --- a/packages/frontend-core/src/components/grid/controls/DeleteButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/DeleteButton.svelte @@ -1,13 +1,8 @@ {#if selectedRowCount}
- Delete {selectedRowCount} row{selectedRowCount === 1 ? "" : "s"} - +
{/if} @@ -57,16 +55,12 @@ diff --git a/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte b/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte index 6c9d1be8ac..30904fbebc 100644 --- a/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte @@ -2,7 +2,7 @@ import { getContext } from "svelte" import { ActionButton, Popover, Toggle } from "@budibase/bbui" - const { columns } = getContext("grid") + const { columns, stickyColumn } = getContext("grid") let open = false let anchor @@ -48,20 +48,24 @@ selected={open || anyHidden} disabled={!$columns.length} > - Hide columns + Columns
+ {#if $stickyColumn} + + {$stickyColumn.label} + {/if} {#each $columns as column} toggleVisibility(column, e.detail)} /> - {column.name} + {column.label} {/each}
diff --git a/packages/frontend-core/src/components/grid/controls/RowHeightButton.svelte b/packages/frontend-core/src/components/grid/controls/RowHeightButton.svelte index 0fcbbed089..e9045ac5c7 100644 --- a/packages/frontend-core/src/components/grid/controls/RowHeightButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/RowHeightButton.svelte @@ -36,13 +36,13 @@
(open = !open)} selected={open} > - Row height + Height
diff --git a/packages/frontend-core/src/components/grid/controls/SortButton.svelte b/packages/frontend-core/src/components/grid/controls/SortButton.svelte index 471e28a2ab..26eba050bf 100644 --- a/packages/frontend-core/src/components/grid/controls/SortButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/SortButton.svelte @@ -2,7 +2,7 @@ import { getContext } from "svelte" import { ActionButton, Popover, Select } from "@budibase/bbui" - const { sort, visibleColumns, stickyColumn } = getContext("grid") + const { sort, columns, stickyColumn } = getContext("grid") const orderOptions = [ { label: "A-Z", value: "ascending" }, { label: "Z-A", value: "descending" }, @@ -11,15 +11,24 @@ let open = false let anchor - $: columnOptions = getColumnOptions($stickyColumn, $visibleColumns) - $: checkValidSortColumn($sort.column, $stickyColumn, $visibleColumns) + $: columnOptions = getColumnOptions($stickyColumn, $columns) + $: checkValidSortColumn($sort.column, $stickyColumn, $columns) const getColumnOptions = (stickyColumn, columns) => { let options = [] if (stickyColumn) { - options.push(stickyColumn.name) + options.push({ + label: stickyColumn.label || stickyColumn.name, + value: stickyColumn.name, + }) } - return [...options, ...columns.map(col => col.name)] + return [ + ...options, + ...columns.map(col => ({ + label: col.label || col.name, + value: col.name, + })), + ] } const updateSortColumn = e => { @@ -37,13 +46,13 @@ } // Ensure we never have a sort column selected that is not visible - const checkValidSortColumn = (sortColumn, stickyColumn, visibleColumns) => { + const checkValidSortColumn = (sortColumn, stickyColumn, columns) => { if (!sortColumn) { return } if ( sortColumn !== stickyColumn?.name && - !visibleColumns.some(col => col.name === sortColumn) + !columns.some(col => col.name === sortColumn) ) { if (stickyColumn) { sort.update(state => ({ @@ -53,7 +62,7 @@ } else { sort.update(state => ({ ...state, - column: visibleColumns[0]?.name, + column: columns[0]?.name, })) } } @@ -66,7 +75,7 @@ quiet size="M" on:click={() => (open = !open)} - selected={open || $sort.column} + selected={open} disabled={!columnOptions.length} > Sort diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte index e6bc3e05e4..36a3802164 100644 --- a/packages/frontend-core/src/components/grid/layout/Grid.svelte +++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte @@ -22,6 +22,8 @@ import HideColumnsButton from "../controls/HideColumnsButton.svelte" import AddRowButton from "../controls/AddRowButton.svelte" import RowHeightButton from "../controls/RowHeightButton.svelte" + import ColumnWidthButton from "../controls/ColumnWidthButton.svelte" + import NewRow from "./NewRow.svelte" import { MaxCellRenderHeight, MaxCellRenderWidthOverflow, @@ -110,6 +112,7 @@ + @@ -127,10 +130,11 @@
+ +
-
diff --git a/packages/frontend-core/src/components/grid/layout/GridBody.svelte b/packages/frontend-core/src/components/grid/layout/GridBody.svelte index 5c61fde3ef..67f5f03898 100644 --- a/packages/frontend-core/src/components/grid/layout/GridBody.svelte +++ b/packages/frontend-core/src/components/grid/layout/GridBody.svelte @@ -2,11 +2,25 @@ import { getContext, onMount } from "svelte" import GridScrollWrapper from "./GridScrollWrapper.svelte" import GridRow from "./GridRow.svelte" + import { BlankRowID } from "../lib/constants" - const { bounds, renderedRows, rowVerticalInversionIndex } = getContext("grid") + const { + bounds, + renderedRows, + renderedColumns, + rowVerticalInversionIndex, + config, + hoveredRowId, + dispatch, + } = getContext("grid") let body + $: renderColumnsWidth = $renderedColumns.reduce( + (total, col) => (total += col.width), + 0 + ) + onMount(() => { // Observe and record the height of the body const observer = new ResizeObserver(() => { @@ -24,6 +38,16 @@ {#each $renderedRows as row, idx} = $rowVerticalInversionIndex} /> {/each} + {#if $config.allowAddRows && $renderedColumns.length} +
($hoveredRowId = BlankRowID)} + on:mouseleave={() => ($hoveredRowId = null)} + on:click={() => dispatch("add-row-inline")} + /> + {/if}
@@ -35,4 +59,15 @@ overflow: hidden; flex: 1 1 auto; } + .blank { + height: var(--row-height); + background: var(--cell-background); + border-bottom: var(--cell-border); + border-right: var(--cell-border); + position: absolute; + } + .blank.highlighted { + background: var(--cell-background-hover); + cursor: pointer; + } diff --git a/packages/frontend-core/src/components/grid/layout/GridScrollWrapper.svelte b/packages/frontend-core/src/components/grid/layout/GridScrollWrapper.svelte index 0d241147a6..04f0960057 100644 --- a/packages/frontend-core/src/components/grid/layout/GridScrollWrapper.svelte +++ b/packages/frontend-core/src/components/grid/layout/GridScrollWrapper.svelte @@ -29,10 +29,7 @@ // Handles a wheel even and updates the scroll offsets const handleWheel = e => { e.preventDefault() - const modifier = e.ctrlKey || e.metaKey - let x = modifier ? e.deltaY : e.deltaX - let y = modifier ? e.deltaX : e.deltaY - debouncedHandleWheel(x, y, e.clientY) + debouncedHandleWheel(e.deltaX, e.deltaY, e.clientY) } const debouncedHandleWheel = domDebounce((deltaX, deltaY, clientY) => { const { top, left } = $scroll diff --git a/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte b/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte index caf8ac280b..2ec186a4d6 100644 --- a/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte +++ b/packages/frontend-core/src/components/grid/layout/HeaderRow.svelte @@ -2,8 +2,17 @@ import { getContext } from "svelte" import GridScrollWrapper from "./GridScrollWrapper.svelte" import HeaderCell from "../cells/HeaderCell.svelte" + import { Icon } from "@budibase/bbui" - const { renderedColumns } = getContext("grid") + const { renderedColumns, dispatch, scroll, hiddenColumnsWidth, width } = + getContext("grid") + + $: columnsWidth = $renderedColumns.reduce( + (total, col) => (total += col.width), + 0 + ) + $: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left + $: left = Math.min($width - 40, end)
@@ -14,6 +23,13 @@ {/each}
+
dispatch("add-column")} + > + +
diff --git a/packages/frontend-core/src/components/grid/layout/NewRow.svelte b/packages/frontend-core/src/components/grid/layout/NewRow.svelte new file mode 100644 index 0000000000..54fef78301 --- /dev/null +++ b/packages/frontend-core/src/components/grid/layout/NewRow.svelte @@ -0,0 +1,265 @@ + + + +{#if isAdding} +
0} + style="--offset:{offset}px; --sticky-width:{width}px;" + > +
+
+
+ + + + {#if $stickyColumn} + {@const cellId = `${NewRowID}-${$stickyColumn.name}`} + + {/if} +
+
+ +
+ {#each $renderedColumns as column, columnIdx} + {@const cellId = `new-${column.name}`} + {#key cellId} + = $columnHorizontalInversionIndex} + {invertY} + /> + {/key} + {/each} +
+
+
+
+ + +
+
+{/if} + + diff --git a/packages/frontend-core/src/components/grid/layout/NewRowTop.svelte b/packages/frontend-core/src/components/grid/layout/NewRowTop.svelte deleted file mode 100644 index 5b92bc30fc..0000000000 --- a/packages/frontend-core/src/components/grid/layout/NewRowTop.svelte +++ /dev/null @@ -1,247 +0,0 @@ - - - -{#if isAdding} -
-
-
($hoveredRowId = "new")} - on:mouseleave={() => ($hoveredRowId = null)} - > -
0} - > - -
-
1
- {#if $config.allowExpandRows} - - {/if} -
-
- {#if $stickyColumn} - {@const cellId = `new-${$stickyColumn.name}`} - - {/if} -
- - -
- {#each $visibleColumns as column} - {@const cellId = `new-${column.name}`} - {#key cellId} - - {/key} - {/each} -
-
-
-
-
- - -
-
-{/if} - - diff --git a/packages/frontend-core/src/components/grid/layout/StickyColumn.svelte b/packages/frontend-core/src/components/grid/layout/StickyColumn.svelte index cbf74a0d22..6f10c30695 100644 --- a/packages/frontend-core/src/components/grid/layout/StickyColumn.svelte +++ b/packages/frontend-core/src/components/grid/layout/StickyColumn.svelte @@ -1,24 +1,26 @@
0} >
- -
-
- {#if $config.allowDeleteRows} -
- -
- {/if} -
- {#if $config.allowExpandRows} -
- -
- {/if} -
-
- + {#if $stickyColumn} {/if} @@ -95,41 +72,7 @@ on:mouseenter={() => ($hoveredRowId = row._id)} on:mouseleave={() => ($hoveredRowId = null)} > - -
-
selectRow(row._id)} - class="checkbox" - class:visible={$config.allowDeleteRows && - (rowSelected || rowHovered || rowFocused)} - > - -
-
- {row.__idx + 1} -
- {#if $config.allowExpandRows} -
- { - dispatch("edit-row", row) - }} - /> -
- {/if} -
-
+ {#if $stickyColumn} {/each} + {#if $config.allowAddRows && ($renderedColumns.length || $stickyColumn)} +
($hoveredRowId = BlankRowID)} + on:mouseleave={() => ($hoveredRowId = null)} + on:click={() => dispatch("add-row-inline")} + > + + + + {#if $stickyColumn} + + {/if} +
+ {/if}
@@ -156,6 +117,7 @@ flex-direction: column; position: relative; z-index: 2; + background: var(--cell-background); } /* Add right border */ @@ -203,43 +165,7 @@ position: relative; flex: 1 1 auto; } - - /* Styles for gutter */ - .gutter { - flex: 1 1 auto; - display: grid; - align-items: center; - padding: var(--cell-padding); - grid-template-columns: 1fr auto; - gap: var(--cell-spacing); - } - .checkbox, - .number { - display: none; - flex-direction: row; - justify-content: center; - align-items: center; - } - .checkbox :global(.spectrum-Checkbox) { - min-height: 0; - height: 20px; - } - .checkbox :global(.spectrum-Checkbox-box) { - margin: 3px 0 0 0; - } - .number { - color: var(--spectrum-global-color-gray-500); - } - .checkbox.visible, - .number.visible { - display: flex; - } - .expand { - opacity: 0; - pointer-events: none; - } - .expand.visible { - opacity: 1; - pointer-events: all; + .row.new :global(*:hover) { + cursor: pointer; } diff --git a/packages/frontend-core/src/components/grid/lib/constants.js b/packages/frontend-core/src/components/grid/lib/constants.js index e5427106a7..a7209d6ea4 100644 --- a/packages/frontend-core/src/components/grid/lib/constants.js +++ b/packages/frontend-core/src/components/grid/lib/constants.js @@ -1,11 +1,14 @@ -export const Padding = 276 +export const Padding = 128 export const MaxCellRenderHeight = 252 export const MaxCellRenderWidthOverflow = 200 export const ScrollBarSize = 8 export const GutterWidth = 72 export const DefaultColumnWidth = 200 -export const MinColumnWidth = 100 +export const MinColumnWidth = 80 export const SmallRowHeight = 36 export const MediumRowHeight = 64 export const LargeRowHeight = 92 export const DefaultRowHeight = SmallRowHeight +export const NewRowID = "new" +export const BlankRowID = "blank" +export const RowPageSize = 100 diff --git a/packages/frontend-core/src/components/grid/lib/utils.js b/packages/frontend-core/src/components/grid/lib/utils.js index 5b1d01ee36..d830d96dba 100644 --- a/packages/frontend-core/src/components/grid/lib/utils.js +++ b/packages/frontend-core/src/components/grid/lib/utils.js @@ -15,7 +15,7 @@ const TypeIconMap = { number: "123", boolean: "Boolean", attachment: "AppleFiles", - link: "Link", + link: "DataCorrelated", formula: "Calculator", json: "Brackets", } diff --git a/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte b/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte index c3ca6103c7..e0e842dc16 100644 --- a/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte +++ b/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte @@ -1,6 +1,7 @@