Merge branch 'chore/npmless-builds' into chore/pipeline_npm_version_updates

This commit is contained in:
Adria Navarro 2023-05-02 14:07:44 +01:00
commit 9ffd43b682
71 changed files with 1461 additions and 557 deletions

View File

@ -79,7 +79,6 @@ jobs:
- name: Build/release Docker images - name: Build/release Docker images
run: | run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
yarn build
yarn build:docker yarn build:docker
env: env:
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}

View File

@ -1,5 +1,5 @@
{ {
"version": "0.0.999-alpha.38", "version": "2.5.6-alpha.28",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/backend-core", "packages/backend-core",

View File

@ -43,7 +43,7 @@
"dev": "yarn run kill-all && lerna link && lerna run --stream --parallel dev:builder --concurrency 1 --stream", "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: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: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", "test": "lerna run --stream test --stream",
"lint:eslint": "eslint packages && eslint qa-core", "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}\"", "lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"",

View File

@ -1,3 +1,5 @@
import { existsSync, readFileSync } from "fs"
function isTest() { function isTest() {
return isCypress() || isJest() return isCypress() || isJest()
} }
@ -45,6 +47,35 @@ function httpLogging() {
return process.env.HTTP_LOGGING 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 = { const environment = {
isTest, isTest,
isJest, isJest,
@ -122,6 +153,7 @@ const environment = {
ENABLE_SSO_MAINTENANCE_MODE: selfHosted ENABLE_SSO_MAINTENANCE_MODE: selfHosted
? process.env.ENABLE_SSO_MAINTENANCE_MODE ? process.env.ENABLE_SSO_MAINTENANCE_MODE
: false, : false,
VERSION: findVersion(),
_set(key: any, value: any) { _set(key: any, value: any) {
process.env[key] = value process.env[key] = value
// @ts-ignore // @ts-ignore

View File

@ -23,8 +23,6 @@ import * as installation from "../installation"
import * as configs from "../configs" import * as configs from "../configs"
import { withCache, TTL, CacheKey } from "../cache/generic" import { withCache, TTL, CacheKey } from "../cache/generic"
const pkg = require("../../package.json")
/** /**
* An identity can be: * An identity can be:
* - account user (Self host) * - account user (Self host)
@ -102,7 +100,7 @@ const identifyInstallationGroup = async (
const id = installId const id = installId
const type = IdentityType.INSTALLATION const type = IdentityType.INSTALLATION
const hosting = getHostingFromEnv() const hosting = getHostingFromEnv()
const version = pkg.version const version = env.VERSION
const environment = getDeploymentEnvironment() const environment = getDeploymentEnvironment()
const group: InstallationGroup = { const group: InstallationGroup = {

View File

@ -4,7 +4,6 @@ import { EventProcessor } from "../types"
import env from "../../../environment" import env from "../../../environment"
import * as context from "../../../context" import * as context from "../../../context"
import * as rateLimiting from "./rateLimiting" import * as rateLimiting from "./rateLimiting"
const pkg = require("../../../../package.json")
const EXCLUDED_EVENTS: Event[] = [ const EXCLUDED_EVENTS: Event[] = [
Event.USER_UPDATED, Event.USER_UPDATED,
@ -49,7 +48,7 @@ export default class PosthogProcessor implements EventProcessor {
properties = this.clearPIIProperties(properties) properties = this.clearPIIProperties(properties)
properties.version = pkg.version properties.version = env.VERSION
properties.service = env.SERVICE properties.service = env.SERVICE
properties.environment = identity.environment properties.environment = identity.environment
properties.hosting = identity.hosting properties.hosting = identity.hosting

View File

@ -6,8 +6,7 @@ import { Installation, IdentityType, Database } from "@budibase/types"
import * as context from "./context" import * as context from "./context"
import semver from "semver" import semver from "semver"
import { bustCache, withCache, TTL, CacheKey } from "./cache/generic" import { bustCache, withCache, TTL, CacheKey } from "./cache/generic"
import environment from "./environment"
const pkg = require("../package.json")
export const getInstall = async (): Promise<Installation> => { export const getInstall = async (): Promise<Installation> => {
return withCache(CacheKey.INSTALLATION, TTL.ONE_DAY, getInstallFromDB, { return withCache(CacheKey.INSTALLATION, TTL.ONE_DAY, getInstallFromDB, {
@ -18,7 +17,7 @@ async function createInstallDoc(platformDb: Database) {
const install: Installation = { const install: Installation = {
_id: StaticDatabases.PLATFORM_INFO.docs.install, _id: StaticDatabases.PLATFORM_INFO.docs.install,
installId: newid(), installId: newid(),
version: pkg.version, version: environment.VERSION,
} }
try { try {
const resp = await platformDb.put(install) const resp = await platformDb.put(install)
@ -80,7 +79,7 @@ export const checkInstallVersion = async (): Promise<void> => {
const install = await getInstall() const install = await getInstall()
const currentVersion = install.version const currentVersion = install.version
const newVersion = pkg.version const newVersion = environment.VERSION
if (currentVersion !== newVersion) { if (currentVersion !== newVersion) {
const isUpgrade = semver.gt(newVersion, currentVersion) const isUpgrade = semver.gt(newVersion, currentVersion)

View File

@ -10,15 +10,11 @@
"incremental": true, "incremental": true,
"sourceMap": true, "sourceMap": true,
"declaration": true, "declaration": true,
"types": [ "node", "jest" ], "types": ["node", "jest"],
"outDir": "dist", "outDir": "dist",
"skipLibCheck": true "skipLibCheck": true
}, },
"include": [ "include": ["**/*.js", "**/*.ts"],
"**/*.js",
"**/*.ts",
"package.json"
],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"dist", "dist",

View File

@ -10,7 +10,10 @@ import { auth } from "./stores/portal"
export const API = createAPIClient({ export const API = createAPIClient({
attachHeaders: headers => { attachHeaders: headers => {
// Attach app ID header from store // 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 // Add csrf token if authenticated
const user = get(auth).user const user = get(auth).user

View File

@ -42,13 +42,14 @@
<GridCreateViewButton /> <GridCreateViewButton />
{/if} {/if}
<GridManageAccessButton /> <GridManageAccessButton />
{#if isUsersTable}
<EditRolesButton />
{/if}
{#if !isInternal} {#if !isInternal}
<GridRelationshipButton /> <GridRelationshipButton />
{/if} {/if}
<GridImportButton disabled={isUsersTable} /> {#if isUsersTable}
<EditRolesButton />
{:else}
<GridImportButton />
{/if}
<GridExportButton /> <GridExportButton />
<GridFilterButton /> <GridFilterButton />
<GridAddColumnModal /> <GridAddColumnModal />

View File

@ -16,7 +16,7 @@
</script> </script>
<ActionButton icon="LockClosed" quiet on:click={openDropdown} {disabled}> <ActionButton icon="LockClosed" quiet on:click={openDropdown} {disabled}>
Manage access Access
</ActionButton> </ActionButton>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<ManageAccessModal <ManageAccessModal

View File

@ -11,7 +11,7 @@
</script> </script>
<ActionButton {disabled} icon="CollectionAdd" quiet on:click={modal.show}> <ActionButton {disabled} icon="CollectionAdd" quiet on:click={modal.show}>
Create view Add view
</ActionButton> </ActionButton>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<CreateViewModal /> <CreateViewModal />

View File

@ -1,7 +1,7 @@
<script> <script>
import CreateEditRow from "../../modals/CreateEditRow.svelte" import CreateEditRow from "../../modals/CreateEditRow.svelte"
import { getContext, onMount } from "svelte" import { getContext, onMount } from "svelte"
import { Modal } from "@budibase/bbui" import { Modal, notifications } from "@budibase/bbui"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
const { subscribe, rows } = getContext("grid") const { subscribe, rows } = getContext("grid")
@ -9,6 +9,11 @@
let modal let modal
let row let row
const deleteRow = e => {
rows.actions.deleteRows([e.detail])
notifications.success("Deleted 1 row")
}
onMount(() => onMount(() =>
subscribe("add-row", () => { subscribe("add-row", () => {
row = {} row = {}
@ -24,5 +29,9 @@
</script> </script>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<CreateEditRow {row} on:updaterows={e => rows.actions.refreshRow(e.detail)} /> <CreateEditRow
{row}
on:updaterows={e => rows.actions.refreshRow(e.detail)}
on:deleteRows={deleteRow}
/>
</Modal> </Modal>

View File

@ -21,6 +21,7 @@ import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCom
import ColumnEditor from "./controls/ColumnEditor/ColumnEditor.svelte" import ColumnEditor from "./controls/ColumnEditor/ColumnEditor.svelte"
import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte" import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte"
import BarButtonList from "./controls/BarButtonList.svelte" import BarButtonList from "./controls/BarButtonList.svelte"
import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte"
const componentMap = { const componentMap = {
text: DrawerBindableCombobox, text: DrawerBindableCombobox,
@ -43,6 +44,7 @@ const componentMap = {
section: SectionSelect, section: SectionSelect,
filter: FilterEditor, filter: FilterEditor,
url: URLSelect, url: URLSelect,
fieldConfiguration: FieldConfiguration,
columns: ColumnEditor, columns: ColumnEditor,
"columns/basic": BasicColumnEditor, "columns/basic": BasicColumnEditor,
"field/sortable": SortableFieldSelect, "field/sortable": SortableFieldSelect,

View File

@ -0,0 +1,91 @@
<script>
import { Button, ActionButton, Drawer } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import ColumnDrawer from "./ColumnDrawer.svelte"
import { cloneDeep } from "lodash/fp"
import {
getDatasourceForProvider,
getSchemaForDatasource,
} from "builderStore/dataBinding"
import { currentAsset } from "builderStore"
import { getFields } from "helpers/searchFields"
export let componentInstance
export let value = []
export let allowCellEditing = true
export let subject = "Table"
const dispatch = createEventDispatcher()
let drawer
let boundValue
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
$: schema = getSchema($currentAsset, datasource)
$: options = allowCellEditing
? Object.keys(schema || {})
: enrichedSchemaFields?.map(field => field.name)
$: sanitisedValue = getValidColumns(value, options)
$: updateBoundValue(sanitisedValue)
$: enrichedSchemaFields = getFields(Object.values(schema || {}), {
allowLinks: true,
})
const getSchema = (asset, datasource) => {
const schema = getSchemaForDatasource(asset, datasource).schema
// Don't show ID and rev in tables
if (schema) {
delete schema._id
delete schema._rev
}
return schema
}
const updateBoundValue = value => {
boundValue = cloneDeep(value)
}
const getValidColumns = (columns, options) => {
if (!Array.isArray(columns) || !columns.length) {
return []
}
// We need to account for legacy configs which would just be an array
// of strings
if (typeof columns[0] === "string") {
columns = columns.map(col => ({
name: col,
displayName: col,
}))
}
return columns.filter(column => {
return options.includes(column.name)
})
}
const open = () => {
updateBoundValue(sanitisedValue)
drawer.show()
}
const save = () => {
dispatch("change", getValidColumns(boundValue, options))
drawer.hide()
}
</script>
<ActionButton on:click={open}>Configure columns</ActionButton>
<Drawer bind:this={drawer} title="{subject} Columns">
<svelte:fragment slot="description">
Configure the columns in your {subject.toLowerCase()}.
</svelte:fragment>
<Button cta slot="buttons" on:click={save}>Save</Button>
<ColumnDrawer
slot="body"
bind:columns={boundValue}
{options}
{schema}
{allowCellEditing}
/>
</Drawer>

View File

@ -0,0 +1,26 @@
<script>
import { DrawerContent, Drawer, Button, Icon } from "@budibase/bbui"
import ValidationDrawer from "components/design/settings/controls/ValidationEditor/ValidationDrawer.svelte"
export let column
export let type
let drawer
</script>
<Icon name="Settings" hoverable size="S" on:click={drawer.show} />
<Drawer bind:this={drawer} title="Field Validation">
<svelte:fragment slot="description">
"{column.name}" field validation
</svelte:fragment>
<Button cta slot="buttons" on:click={drawer.hide}>Save</Button>
<DrawerContent slot="body">
<div class="container">
<ValidationDrawer
slot="body"
bind:rules={column.validation}
fieldName={column.name}
{type}
/>
</div>
</DrawerContent>
</Drawer>

View File

@ -0,0 +1,202 @@
<script>
import {
Button,
Icon,
DrawerContent,
Layout,
Select,
Label,
Body,
Input,
} from "@budibase/bbui"
import { flip } from "svelte/animate"
import { dndzone } from "svelte-dnd-action"
import { generate } from "shortid"
import CellEditor from "./CellEditor.svelte"
export let columns = []
export let options = []
export let schema = {}
const flipDurationMs = 150
let dragDisabled = true
$: unselectedColumns = getUnselectedColumns(options, columns)
$: columns.forEach(column => {
if (!column.id) {
column.id = generate()
}
})
const getUnselectedColumns = (allColumns, selectedColumns) => {
let optionsObj = {}
allColumns.forEach(option => {
optionsObj[option] = true
})
selectedColumns?.forEach(column => {
delete optionsObj[column.name]
})
return Object.keys(optionsObj)
}
const getRemainingColumnOptions = selectedColumn => {
if (!selectedColumn || unselectedColumns.includes(selectedColumn)) {
return unselectedColumns
}
return [selectedColumn, ...unselectedColumns]
}
const addColumn = () => {
columns = [...columns, {}]
}
const removeColumn = id => {
columns = columns.filter(column => column.id !== id)
}
const updateColumnOrder = e => {
columns = e.detail.items
}
const handleFinalize = e => {
updateColumnOrder(e)
dragDisabled = true
}
const addAllColumns = () => {
let newColumns = columns || []
options.forEach(field => {
const fieldSchema = schema[field]
const hasCol = columns && columns.findIndex(x => x.name === field) !== -1
if (!fieldSchema?.autocolumn && !hasCol) {
newColumns.push({
name: field,
displayName: field,
})
}
})
columns = newColumns
}
const reset = () => {
columns = []
}
const getFieldType = column => {
return `validation/${schema[column.name]?.type}`
}
</script>
<DrawerContent>
<div class="container">
<Layout noPadding gap="S">
{#if columns?.length}
<Layout noPadding gap="XS">
<div class="column">
<div />
<Label size="L">Column</Label>
<Label size="L">Label</Label>
<div />
<div />
</div>
<div
class="columns"
use:dndzone={{
items: columns,
flipDurationMs,
dropTargetStyle: { outline: "none" },
dragDisabled,
}}
on:finalize={handleFinalize}
on:consider={updateColumnOrder}
>
{#each columns as column (column.id)}
<div class="column" animate:flip={{ duration: flipDurationMs }}>
<div
class="handle"
aria-label="drag-handle"
style={dragDisabled ? "cursor: grab" : "cursor: grabbing"}
on:mousedown={() => (dragDisabled = false)}
>
<Icon name="DragHandle" size="XL" />
</div>
<Select
bind:value={column.name}
placeholder="Column"
options={getRemainingColumnOptions(column.name)}
on:change={e => (column.displayName = e.detail)}
/>
<Input bind:value={column.displayName} placeholder="Label" />
<CellEditor type={getFieldType(column)} bind:column />
<Icon
name="Close"
hoverable
size="S"
on:click={() => removeColumn(column.id)}
disabled={columns.length === 1}
/>
</div>
{/each}
</div>
</Layout>
{:else}
<div class="column">
<div class="wide">
<Body size="S">Add columns to be included in your form below.</Body>
</div>
</div>
{/if}
<div class="column">
<div class="buttons wide">
<Button secondary icon="Add" on:click={addColumn}>Add column</Button>
<Button secondary quiet on:click={addAllColumns}>
Add all columns
</Button>
{#if columns?.length}
<Button secondary quiet on:click={reset}>Reset columns</Button>
{/if}
</div>
</div>
</Layout>
</div>
</DrawerContent>
<style>
.container {
width: 100%;
max-width: 600px;
margin: 0 auto;
}
.columns {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-m);
}
.column {
gap: var(--spacing-l);
display: grid;
grid-template-columns: 20px 1fr 1fr 16px 16px;
align-items: center;
border-radius: var(--border-radius-s);
transition: background-color ease-in-out 130ms;
}
.column:hover {
background-color: var(--spectrum-global-color-gray-100);
}
.handle {
display: grid;
place-items: center;
}
.wide {
grid-column: 2 / -1;
}
.buttons {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-m);
}
</style>

View File

@ -0,0 +1,89 @@
<script>
import { Button, ActionButton, Drawer } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import ColumnDrawer from "./ColumnDrawer.svelte"
import { cloneDeep } from "lodash/fp"
import {
getDatasourceForProvider,
getSchemaForDatasource,
} from "builderStore/dataBinding"
import { currentAsset } from "builderStore"
import { getFields } from "helpers/searchFields"
export let componentInstance
export let value = []
const convertOldColumnFormat = oldColumns => {
if (typeof oldColumns?.[0] === "string") {
value = oldColumns.map(field => ({ name: field, displayName: field }))
}
}
$: convertOldColumnFormat(value)
const dispatch = createEventDispatcher()
let drawer
let boundValue
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
$: schema = getSchema($currentAsset, datasource)
$: options = Object.keys(schema || {})
$: sanitisedValue = getValidColumns(value, options)
$: updateBoundValue(sanitisedValue)
$: enrichedSchemaFields = getFields(Object.values(schema || {}), {
allowLinks: true,
})
const getSchema = (asset, datasource) => {
const schema = getSchemaForDatasource(asset, datasource).schema
// Don't show ID and rev in tables
if (schema) {
delete schema._id
delete schema._rev
}
return schema
}
const updateBoundValue = value => {
boundValue = cloneDeep(value)
}
const getValidColumns = (columns, options) => {
if (!Array.isArray(columns) || !columns.length) {
return []
}
// We need to account for legacy configs which would just be an array
// of strings
if (typeof columns[0] === "string") {
columns = columns.map(col => ({
name: col,
displayName: col,
}))
}
return columns.filter(column => {
return options.includes(column.name)
})
}
const open = () => {
updateBoundValue(sanitisedValue)
drawer.show()
}
const save = () => {
dispatch("change", getValidColumns(boundValue, options))
drawer.hide()
}
</script>
<ActionButton on:click={open}>Configure fields</ActionButton>
<Drawer bind:this={drawer} title="Form Fields">
<svelte:fragment slot="description">
Configure the fields in your form.
</svelte:fragment>
<Button cta slot="buttons" on:click={save}>Save</Button>
<ColumnDrawer slot="body" bind:columns={boundValue} {options} {schema} />
</Drawer>

View File

@ -16,6 +16,7 @@
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
import { generate } from "shortid" import { generate } from "shortid"
export let fieldName = null
export let rules = [] export let rules = []
export let bindings = [] export let bindings = []
export let type export let type
@ -124,7 +125,7 @@
} }
$: dataSourceSchema = getDataSourceSchema($currentAsset, $selectedComponent) $: dataSourceSchema = getDataSourceSchema($currentAsset, $selectedComponent)
$: field = $selectedComponent?.field $: field = fieldName || $selectedComponent?.field
$: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {}) $: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {})
$: fieldType = type?.split("/")[1] || "string" $: fieldType = type?.split("/")[1] || "string"
$: constraintOptions = getConstraintsForType(fieldType) $: constraintOptions = getConstraintsForType(fieldType)
@ -140,8 +141,12 @@
const formParent = findClosestMatchingComponent( const formParent = findClosestMatchingComponent(
asset.props, asset.props,
component._id, 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) return getSchemaForDatasource(asset, formParent?.dataSource)
} }

View File

@ -33,7 +33,7 @@
import * as routify from "@roxi/routify" import * as routify from "@roxi/routify"
import { onDestroy } from "svelte" 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({ const stopSyncing = syncURLToState({
urlParam: "appId", urlParam: "appId",
stateKey: "selectedAppId", stateKey: "selectedAppId",
@ -47,6 +47,7 @@
let deletionModal let deletionModal
let exportPublishedVersion = false let exportPublishedVersion = false
let deletionConfirmationAppName let deletionConfirmationAppName
let loaded = false
$: app = $overview.selectedApp $: app = $overview.selectedApp
$: appId = $overview.selectedAppId $: appId = $overview.selectedAppId
@ -56,10 +57,12 @@
$: lockedByYou = $auth.user.email === app?.lockedBy?.email $: lockedByYou = $auth.user.email === app?.lockedBy?.email
const initialiseApp = async appId => { const initialiseApp = async appId => {
loaded = false
try { try {
const pkg = await API.fetchAppPackage(appId) const pkg = await API.fetchAppPackage(appId)
await store.actions.initialise(pkg) await store.actions.initialise(pkg)
await API.syncApp(appId) await API.syncApp(appId)
loaded = true
} catch (error) { } catch (error) {
notifications.error("Error initialising app overview") notifications.error("Error initialising app overview")
$goto("../../") $goto("../../")
@ -228,7 +231,9 @@
active={$isActive("./version")} active={$isActive("./version")}
/> />
</SideNav> </SideNav>
{#if loaded}
<slot /> <slot />
{/if}
</Content> </Content>
</Layout> </Layout>
</Page> </Page>

View File

@ -3,17 +3,17 @@ import { logging } from "@budibase/backend-core"
logging.disableLogger() logging.disableLogger()
import "./prebuilds" import "./prebuilds"
import "./environment" import "./environment"
import { env } from "@budibase/backend-core"
import { getCommands } from "./options" import { getCommands } from "./options"
import { Command } from "commander" import { Command } from "commander"
import { getHelpDescription } from "./utils" import { getHelpDescription } from "./utils"
const json = require("../package.json")
// add hosting config // add hosting config
async function init() { async function init() {
const program = new Command() const program = new Command()
.addHelpCommand("help", getHelpDescription("Help with Budibase commands.")) .addHelpCommand("help", getHelpDescription("Help with Budibase commands."))
.helpOption(false) .helpOption(false)
.version(json.version) .version(env.VERSION)
// add commands // add commands
for (let command of getCommands()) { for (let command of getCommands()) {
command.configure(program) command.configure(program)

View File

@ -15,7 +15,7 @@
"require": ["tsconfig-paths/register"], "require": ["tsconfig-paths/register"],
"swc": true "swc": true
}, },
"references": [{ "path": "../types" }, { "path": "../backend-core" }],
"include": ["src/**/*", "package.json"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }

View File

@ -4435,6 +4435,48 @@
"key": "row" "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", "name": "Fields",
"settings": [ "settings": [
{ {
"type": "multifield", "type": "fieldConfiguration",
"label": "Fields", "label": "Fields",
"key": "fields", "key": "fields",
"selectAllFields": true "selectAllFields": true
@ -5028,6 +5070,17 @@
"invert": true "invert": true
} }
}, },
{
"type": "text",
"key": "saveButtonLabel",
"label": "Save button label",
"nested": true,
"defaultValue": "Save",
"dependsOn": {
"setting": "showSaveButton",
"value": true
}
},
{ {
"type": "boolean", "type": "boolean",
"label": "Allow delete", "label": "Allow delete",
@ -5038,6 +5091,17 @@
"value": "Update" "value": "Update"
} }
}, },
{
"type": "text",
"key": "deleteButtonLabel",
"label": "Delete button label",
"nested": true,
"defaultValue": "Delete",
"dependsOn": {
"setting": "showDeleteButton",
"value": true
}
},
{ {
"type": "url", "type": "url",
"label": "Navigate after button press", "label": "Navigate after button press",

View File

@ -26,6 +26,10 @@
export let titleButtonClickBehaviour export let titleButtonClickBehaviour
export let onClickTitleButton export let onClickTitleButton
export let noRowsMessage export let noRowsMessage
export let sidePanelFields
export let sidePanelShowDelete
export let sidePanelSaveLabel
export let sidePanelDeleteLabel
const { fetchDatasourceSchema, API } = getContext("sdk") const { fetchDatasourceSchema, API } = getContext("sdk")
const stateKey = `ID_${generate()}` const stateKey = `ID_${generate()}`
@ -241,10 +245,12 @@
props={{ props={{
dataSource, dataSource,
showSaveButton: true, showSaveButton: true,
showDeleteButton: true, showDeleteButton: sidePanelShowDelete,
saveButtonLabel: sidePanelSaveLabel,
deleteButtonLabel: sidePanelDeleteLabel,
actionType: "Update", actionType: "Update",
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`, rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
fields: normalFields, fields: sidePanelFields || normalFields,
title: editTitle, title: editTitle,
labelPosition: "left", labelPosition: "left",
}} }}
@ -266,8 +272,9 @@
dataSource, dataSource,
showSaveButton: true, showSaveButton: true,
showDeleteButton: false, showDeleteButton: false,
saveButtonLabel: sidePanelSaveLabel,
actionType: "Create", actionType: "Create",
fields: normalFields, fields: sidePanelFields || normalFields,
title: "Create Row", title: "Create Row",
labelPosition: "left", labelPosition: "left",
}} }}

View File

@ -12,6 +12,8 @@
export let fields export let fields
export let labelPosition export let labelPosition
export let title export let title
export let saveButtonLabel
export let deleteButtonLabel
export let showSaveButton export let showSaveButton
export let showDeleteButton export let showDeleteButton
export let rowId export let rowId
@ -20,10 +22,40 @@
const { fetchDatasourceSchema } = getContext("sdk") 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 schema
let providerId let providerId
let repeaterId let repeaterId
$: formattedFields = convertOldFieldFormat(fields)
$: fieldsOrDefault = getDefaultFields(formattedFields, schema)
$: fetchSchema(dataSource) $: fetchSchema(dataSource)
$: dataProvider = `{{ literal ${safe(providerId)} }}` $: dataProvider = `{{ literal ${safe(providerId)} }}`
$: filter = [ $: filter = [
@ -46,9 +78,11 @@
actionType, actionType,
size, size,
disabled, disabled,
fields, fields: fieldsOrDefault,
labelPosition, labelPosition,
title, title,
saveButtonLabel,
deleteButtonLabel,
showSaveButton, showSaveButton,
showDeleteButton, showDeleteButton,
schema, schema,

View File

@ -11,6 +11,8 @@
export let fields export let fields
export let labelPosition export let labelPosition
export let title export let title
export let saveButtonLabel
export let deleteButtonLabel
export let showSaveButton export let showSaveButton
export let showDeleteButton export let showDeleteButton
export let schema export let schema
@ -33,6 +35,12 @@
let formId let formId
$: onSave = [ $: onSave = [
{
"##eventHandlerType": "Validate Form",
parameters: {
componentId: formId,
},
},
{ {
"##eventHandlerType": "Save Row", "##eventHandlerType": "Save Row",
parameters: { parameters: {
@ -163,7 +171,7 @@
<BlockComponent <BlockComponent
type="button" type="button"
props={{ props={{
text: "Delete", text: deleteButtonLabel || "Delete",
onClick: onDelete, onClick: onDelete,
quiet: true, quiet: true,
type: "secondary", type: "secondary",
@ -175,7 +183,7 @@
<BlockComponent <BlockComponent
type="button" type="button"
props={{ props={{
text: "Save", text: saveButtonLabel || "Save",
onClick: onSave, onClick: onSave,
type: "cta", type: "cta",
}} }}
@ -188,14 +196,14 @@
{/if} {/if}
<BlockComponent type="fieldgroup" props={{ labelPosition }} order={1}> <BlockComponent type="fieldgroup" props={{ labelPosition }} order={1}>
{#each fields as field, idx} {#each fields as field, idx}
{#if getComponentForField(field)} {#if getComponentForField(field.name)}
<BlockComponent <BlockComponent
type={getComponentForField(field)} type={getComponentForField(field.name)}
props={{ props={{
field, validation: field.validation,
label: field, field: field.name,
placeholder: field, label: field.displayName,
disabled, placeholder: field.displayName,
}} }}
order={idx} order={idx}
/> />

View File

@ -72,6 +72,7 @@
api = { api = {
focus: () => open(), focus: () => open(),
blur: () => close(), blur: () => close(),
isActive: () => isOpen,
onKeyDown, onKeyDown,
} }
}) })

View File

@ -48,8 +48,9 @@
} }
const cellAPI = { const cellAPI = {
focus: () => api?.focus(), focus: () => api?.focus?.(),
blur: () => api?.blur(), blur: () => api?.blur?.(),
isActive: () => api?.isActive?.() ?? false,
onKeyDown: (...params) => api?.onKeyDown(...params), onKeyDown: (...params) => api?.onKeyDown(...params),
isReadonly: () => readonly, isReadonly: () => readonly,
getType: () => column.schema.type, getType: () => column.schema.type,
@ -67,6 +68,7 @@
{rowIdx} {rowIdx}
{focused} {focused}
{selectedUser} {selectedUser}
{readonly}
error={$error} error={$error}
on:click={() => focusedCellId.set(cellId)} on:click={() => focusedCellId.set(cellId)}
on:contextmenu={e => menu.actions.open(cellId, e)} on:contextmenu={e => menu.actions.open(cellId, e)}

View File

@ -8,6 +8,7 @@
export let rowIdx export let rowIdx
export let defaultHeight = false export let defaultHeight = false
export let center = false export let center = false
export let readonly = false
$: style = getStyle(width, selectedUser) $: style = getStyle(width, selectedUser)
@ -27,6 +28,7 @@
class:focused class:focused
class:error class:error
class:center class:center
class:readonly
class:default-height={defaultHeight} class:default-height={defaultHeight}
class:selected-other={selectedUser != null} class:selected-other={selectedUser != null}
on:focus on:focus
@ -121,7 +123,8 @@
.cell:hover { .cell:hover {
cursor: default; cursor: default;
} }
.cell.highlighted:not(.focused) { .cell.highlighted:not(.focused),
.cell.focused.readonly {
--cell-background: var(--cell-background-hover); --cell-background: var(--cell-background-hover);
} }
.cell.selected:not(.focused) { .cell.selected:not(.focused) {

View File

@ -0,0 +1,127 @@
<script>
import { GutterWidth } from "../lib/constants"
import { getContext } from "svelte"
import { Checkbox, Icon } from "@budibase/bbui"
import GridCell from "./GridCell.svelte"
import { createEventDispatcher } from "svelte"
export let row
export let rowFocused = false
export let rowHovered = false
export let rowSelected = false
export let disableExpand = false
export let disableNumber = false
export let defaultHeight = false
export let disabled = false
const { config, dispatch, selectedRows } = getContext("grid")
const svelteDispatch = createEventDispatcher()
const select = () => {
svelteDispatch("select")
const id = row?._id
if (id) {
selectedRows.update(state => {
let newState = {
...state,
[id]: !state[id],
}
if (!newState[id]) {
delete newState[id]
}
return newState
})
}
}
const expand = () => {
svelteDispatch("expand")
if (row) {
dispatch("edit-row", row)
}
}
</script>
<GridCell
width={GutterWidth}
highlighted={rowFocused || rowHovered}
selected={rowSelected}
{defaultHeight}
>
<div class="gutter">
{#if $$slots.default}
<slot />
{:else}
<div
on:click={select}
class="checkbox"
class:visible={$config.allowDeleteRows &&
(disableNumber || rowSelected || rowHovered || rowFocused)}
>
<Checkbox value={rowSelected} {disabled} />
</div>
{#if !disableNumber}
<div
class="number"
class:visible={!$config.allowDeleteRows ||
!(rowSelected || rowHovered || rowFocused)}
>
{row.__idx + 1}
</div>
{/if}
{/if}
{#if $config.allowExpandRows}
<div
class="expand"
class:visible={!disableExpand && (rowFocused || rowHovered)}
>
<Icon name="Maximize" hoverable size="S" on:click={expand} />
</div>
{/if}
</div>
</GridCell>
<style>
.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;
margin-right: 4px;
}
.expand :global(.spectrum-Icon) {
pointer-events: none;
}
.expand.visible {
opacity: 1;
}
.expand.visible :global(.spectrum-Icon) {
pointer-events: all;
}
</style>

View File

@ -20,6 +20,7 @@
ui, ui,
columns, columns,
} = getContext("grid") } = getContext("grid")
const bannedDisplayColumnTypes = [ const bannedDisplayColumnTypes = [
"link", "link",
"array", "array",
@ -92,6 +93,16 @@
columns.actions.changePrimaryDisplay(column.name) columns.actions.changePrimaryDisplay(column.name)
open = false 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
}
</script> </script>
<div <div
@ -100,7 +111,7 @@
style="flex: 0 0 {column.width}px;" style="flex: 0 0 {column.width}px;"
bind:this={anchor} bind:this={anchor}
class:disabled={$isReordering || $isResizing} class:disabled={$isReordering || $isResizing}
class:sorted={sortedBy} class:sticky={idx === "sticky"}
> >
<GridCell <GridCell
on:mousedown={onMouseDown} on:mousedown={onMouseDown}
@ -128,11 +139,7 @@
/> />
</div> </div>
{/if} {/if}
<div <div class="more" on:click={() => (open = true)}>
class="more"
on:mousedown|stopPropagation
on:click={() => (open = true)}
>
<Icon <Icon
size="S" size="S"
name="MoreVertical" name="MoreVertical"
@ -187,6 +194,7 @@
<MenuItem disabled={!canMoveRight} icon="ChevronRight" on:click={moveRight}> <MenuItem disabled={!canMoveRight} icon="ChevronRight" on:click={moveRight}>
Move right Move right
</MenuItem> </MenuItem>
<MenuItem icon="VisibilityOff" on:click={hideColumn}>Hide column</MenuItem>
</Menu> </Menu>
</Popover> </Popover>
@ -194,6 +202,10 @@
.header-cell { .header-cell {
display: flex; display: flex;
} }
.header-cell:not(.sticky):hover,
.header-cell:not(.sticky) :global(.cell:hover) {
cursor: grab;
}
.header-cell.disabled { .header-cell.disabled {
pointer-events: none; pointer-events: none;
} }
@ -202,9 +214,6 @@
gap: calc(2 * var(--cell-spacing)); gap: calc(2 * var(--cell-spacing));
background: var(--spectrum-global-color-gray-100); background: var(--spectrum-global-color-gray-100);
} }
.header-cell.sorted :global(.cell) {
background: var(--spectrum-global-color-gray-200);
}
.name { .name {
flex: 1 1 auto; flex: 1 1 auto;

View File

@ -31,8 +31,10 @@
isOpen = true isOpen = true
await tick() await tick()
textarea.focus() textarea.focus()
if (value?.length > 100) {
textarea.setSelectionRange(0, 0) textarea.setSelectionRange(0, 0)
} }
}
const close = () => { const close = () => {
textarea?.blur() textarea?.blur()
@ -43,6 +45,7 @@
api = { api = {
focus: () => open(), focus: () => open(),
blur: () => close(), blur: () => close(),
isActive: () => isOpen,
onKeyDown, onKeyDown,
} }
}) })

View File

@ -73,6 +73,7 @@
api = { api = {
focus: open, focus: open,
blur: close, blur: close,
isActive: () => isOpen,
onKeyDown, onKeyDown,
} }
}) })

View File

@ -235,6 +235,7 @@
api = { api = {
focus: open, focus: open,
blur: close, blur: close,
isActive: () => isOpen,
onKeyDown, onKeyDown,
} }
}) })

View File

@ -33,6 +33,7 @@
api = { api = {
focus: () => input?.focus(), focus: () => input?.focus(),
blur: () => input?.blur(), blur: () => input?.blur(),
isActive: () => active,
onKeyDown, onKeyDown,
} }
}) })

View File

@ -12,5 +12,5 @@
on:click={() => dispatch("add-column")} on:click={() => dispatch("add-column")}
disabled={!$config.allowAddColumns} disabled={!$config.allowAddColumns}
> >
Create column Add column
</ActionButton> </ActionButton>

View File

@ -9,10 +9,10 @@
icon="TableRowAddBottom" icon="TableRowAddBottom"
quiet quiet
size="M" size="M"
on:click={() => dispatch("add-row")} on:click={() => dispatch("add-row-inline")}
disabled={!loaded || disabled={!loaded ||
!$config.allowAddRows || !$config.allowAddRows ||
(!$columns.length && !$stickyColumn)} (!$columns.length && !$stickyColumn)}
> >
Create row Add row
</ActionButton> </ActionButton>

View File

@ -0,0 +1,91 @@
<script>
import { getContext } from "svelte"
import { ActionButton, Popover } from "@budibase/bbui"
import { DefaultColumnWidth } from "../lib/constants"
const { stickyColumn, columns } = getContext("grid")
const smallSize = 120
const mediumSize = DefaultColumnWidth
const largeSize = DefaultColumnWidth * 1.5
let open = false
let anchor
$: allCols = $columns.concat($stickyColumn ? [$stickyColumn] : [])
$: allSmall = allCols.every(col => col.width === smallSize)
$: allMedium = allCols.every(col => col.width === mediumSize)
$: allLarge = allCols.every(col => col.width === largeSize)
$: custom = !allSmall && !allMedium && !allLarge
$: sizeOptions = [
{
label: "Small",
size: smallSize,
selected: allSmall,
},
{
label: "Medium",
size: mediumSize,
selected: allMedium,
},
{
label: "Large",
size: largeSize,
selected: allLarge,
},
]
const changeColumnWidth = async width => {
columns.update(state => {
state.forEach(column => {
column.width = width
})
return state
})
if ($stickyColumn) {
stickyColumn.update(state => ({
...state,
width,
}))
}
await columns.actions.saveChanges()
}
</script>
<div bind:this={anchor}>
<ActionButton
icon="MoveLeftRight"
quiet
size="M"
on:click={() => (open = !open)}
selected={open}
disabled={!allCols.length}
>
Width
</ActionButton>
</div>
<Popover bind:open {anchor} align="left">
<div class="content">
{#each sizeOptions as option}
<ActionButton
quiet
on:click={() => changeColumnWidth(option.size)}
selected={option.selected}
>
{option.label}
</ActionButton>
{/each}
{#if custom}
<ActionButton selected={custom} quiet>Custom</ActionButton>
{/if}
</div>
</Popover>
<style>
.content {
padding: 12px;
display: flex;
align-items: center;
gap: 8px;
}
</style>

View File

@ -1,13 +1,8 @@
<script> <script>
import { import { Modal, ModalContent, Button, notifications } from "@budibase/bbui"
Modal, import { getContext, onMount } from "svelte"
ModalContent,
ActionButton,
notifications,
} from "@budibase/bbui"
import { getContext } from "svelte"
const { selectedRows, rows, config } = getContext("grid") const { selectedRows, rows, config, subscribe } = getContext("grid")
let modal let modal
@ -28,18 +23,21 @@
await rows.actions.deleteRows(rowsToDelete) await rows.actions.deleteRows(rowsToDelete)
notifications.success(`Deleted ${count} row${count === 1 ? "" : "s"}`) notifications.success(`Deleted ${count} row${count === 1 ? "" : "s"}`)
} }
onMount(() => subscribe("request-bulk-delete", () => modal?.show()))
</script> </script>
{#if selectedRowCount} {#if selectedRowCount}
<div class="delete-button" data-ignore-click-outside="true"> <div class="delete-button" data-ignore-click-outside="true">
<ActionButton <Button
icon="Delete" icon="Delete"
size="S" size="M"
on:click={modal.show} on:click={modal.show}
disabled={!$config.allowEditRows} disabled={!$config.allowEditRows}
cta
> >
Delete {selectedRowCount} row{selectedRowCount === 1 ? "" : "s"} Delete {selectedRowCount} row{selectedRowCount === 1 ? "" : "s"}
</ActionButton> </Button>
</div> </div>
{/if} {/if}
@ -57,16 +55,12 @@
</Modal> </Modal>
<style> <style>
.delete-button :global(.spectrum-ActionButton:not(:disabled) *) { .delete-button :global(.spectrum-Button:not(:disabled)) {
color: var(--spectrum-global-color-red-400); background-color: var(--spectrum-global-color-red-400);
}
.delete-button :global(.spectrum-ActionButton:not(:disabled)) {
border-color: var(--spectrum-global-color-red-400); border-color: var(--spectrum-global-color-red-400);
} }
/*.delete-button.disabled :global(.spectrum-ActionButton *) {*/ .delete-button :global(.spectrum-Button:not(:disabled):hover) {
/* color: var(--spectrum-global-color-gray-600);*/ background-color: var(--spectrum-global-color-red-500);
/*}*/ border-color: var(--spectrum-global-color-red-500);
/*.delete-button.disabled :global(.spectrum-ActionButton) {*/ }
/* border-color: var(--spectrum-global-color-gray-400);*/
/*}*/
</style> </style>

View File

@ -2,7 +2,7 @@
import { getContext } from "svelte" import { getContext } from "svelte"
import { ActionButton, Popover, Toggle } from "@budibase/bbui" import { ActionButton, Popover, Toggle } from "@budibase/bbui"
const { columns } = getContext("grid") const { columns, stickyColumn } = getContext("grid")
let open = false let open = false
let anchor let anchor
@ -48,20 +48,24 @@
selected={open || anyHidden} selected={open || anyHidden}
disabled={!$columns.length} disabled={!$columns.length}
> >
Hide columns Columns
</ActionButton> </ActionButton>
</div> </div>
<Popover bind:open {anchor} align="left"> <Popover bind:open {anchor} align="left">
<div class="content"> <div class="content">
<div class="columns"> <div class="columns">
{#if $stickyColumn}
<Toggle disabled size="S" value={true} />
<span>{$stickyColumn.label}</span>
{/if}
{#each $columns as column} {#each $columns as column}
<Toggle <Toggle
size="S" size="S"
value={column.visible} value={column.visible}
on:change={e => toggleVisibility(column, e.detail)} on:change={e => toggleVisibility(column, e.detail)}
/> />
<span>{column.name}</span> <span>{column.label}</span>
{/each} {/each}
</div> </div>
<div class="buttons"> <div class="buttons">

View File

@ -36,13 +36,13 @@
<div bind:this={anchor}> <div bind:this={anchor}>
<ActionButton <ActionButton
icon="LineHeight" icon="MoveUpDown"
quiet quiet
size="M" size="M"
on:click={() => (open = !open)} on:click={() => (open = !open)}
selected={open} selected={open}
> >
Row height Height
</ActionButton> </ActionButton>
</div> </div>

View File

@ -2,7 +2,7 @@
import { getContext } from "svelte" import { getContext } from "svelte"
import { ActionButton, Popover, Select } from "@budibase/bbui" import { ActionButton, Popover, Select } from "@budibase/bbui"
const { sort, visibleColumns, stickyColumn } = getContext("grid") const { sort, columns, stickyColumn } = getContext("grid")
const orderOptions = [ const orderOptions = [
{ label: "A-Z", value: "ascending" }, { label: "A-Z", value: "ascending" },
{ label: "Z-A", value: "descending" }, { label: "Z-A", value: "descending" },
@ -11,15 +11,24 @@
let open = false let open = false
let anchor let anchor
$: columnOptions = getColumnOptions($stickyColumn, $visibleColumns) $: columnOptions = getColumnOptions($stickyColumn, $columns)
$: checkValidSortColumn($sort.column, $stickyColumn, $visibleColumns) $: checkValidSortColumn($sort.column, $stickyColumn, $columns)
const getColumnOptions = (stickyColumn, columns) => { const getColumnOptions = (stickyColumn, columns) => {
let options = [] let options = []
if (stickyColumn) { 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 => { const updateSortColumn = e => {
@ -37,13 +46,13 @@
} }
// Ensure we never have a sort column selected that is not visible // Ensure we never have a sort column selected that is not visible
const checkValidSortColumn = (sortColumn, stickyColumn, visibleColumns) => { const checkValidSortColumn = (sortColumn, stickyColumn, columns) => {
if (!sortColumn) { if (!sortColumn) {
return return
} }
if ( if (
sortColumn !== stickyColumn?.name && sortColumn !== stickyColumn?.name &&
!visibleColumns.some(col => col.name === sortColumn) !columns.some(col => col.name === sortColumn)
) { ) {
if (stickyColumn) { if (stickyColumn) {
sort.update(state => ({ sort.update(state => ({
@ -53,7 +62,7 @@
} else { } else {
sort.update(state => ({ sort.update(state => ({
...state, ...state,
column: visibleColumns[0]?.name, column: columns[0]?.name,
})) }))
} }
} }
@ -66,7 +75,7 @@
quiet quiet
size="M" size="M"
on:click={() => (open = !open)} on:click={() => (open = !open)}
selected={open || $sort.column} selected={open}
disabled={!columnOptions.length} disabled={!columnOptions.length}
> >
Sort Sort

View File

@ -22,6 +22,8 @@
import HideColumnsButton from "../controls/HideColumnsButton.svelte" import HideColumnsButton from "../controls/HideColumnsButton.svelte"
import AddRowButton from "../controls/AddRowButton.svelte" import AddRowButton from "../controls/AddRowButton.svelte"
import RowHeightButton from "../controls/RowHeightButton.svelte" import RowHeightButton from "../controls/RowHeightButton.svelte"
import ColumnWidthButton from "../controls/ColumnWidthButton.svelte"
import NewRow from "./NewRow.svelte"
import { import {
MaxCellRenderHeight, MaxCellRenderHeight,
MaxCellRenderWidthOverflow, MaxCellRenderWidthOverflow,
@ -110,6 +112,7 @@
<AddRowButton /> <AddRowButton />
<AddColumnButton /> <AddColumnButton />
<slot name="controls" /> <slot name="controls" />
<ColumnWidthButton />
<RowHeightButton /> <RowHeightButton />
<HideColumnsButton /> <HideColumnsButton />
<SortButton /> <SortButton />
@ -127,10 +130,11 @@
<HeaderRow /> <HeaderRow />
<GridBody /> <GridBody />
</div> </div>
<BetaButton />
<NewRow />
<div class="overlays"> <div class="overlays">
<ResizeOverlay /> <ResizeOverlay />
<ReorderOverlay /> <ReorderOverlay />
<BetaButton />
<ScrollOverlay /> <ScrollOverlay />
<MenuOverlay /> <MenuOverlay />
</div> </div>

View File

@ -2,11 +2,25 @@
import { getContext, onMount } from "svelte" import { getContext, onMount } from "svelte"
import GridScrollWrapper from "./GridScrollWrapper.svelte" import GridScrollWrapper from "./GridScrollWrapper.svelte"
import GridRow from "./GridRow.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 let body
$: renderColumnsWidth = $renderedColumns.reduce(
(total, col) => (total += col.width),
0
)
onMount(() => { onMount(() => {
// Observe and record the height of the body // Observe and record the height of the body
const observer = new ResizeObserver(() => { const observer = new ResizeObserver(() => {
@ -24,6 +38,16 @@
{#each $renderedRows as row, idx} {#each $renderedRows as row, idx}
<GridRow {row} {idx} invertY={idx >= $rowVerticalInversionIndex} /> <GridRow {row} {idx} invertY={idx >= $rowVerticalInversionIndex} />
{/each} {/each}
{#if $config.allowAddRows && $renderedColumns.length}
<div
class="blank"
class:highlighted={$hoveredRowId === BlankRowID}
style="width:{renderColumnsWidth}px"
on:mouseenter={() => ($hoveredRowId = BlankRowID)}
on:mouseleave={() => ($hoveredRowId = null)}
on:click={() => dispatch("add-row-inline")}
/>
{/if}
</GridScrollWrapper> </GridScrollWrapper>
</div> </div>
@ -35,4 +59,15 @@
overflow: hidden; overflow: hidden;
flex: 1 1 auto; 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;
}
</style> </style>

View File

@ -29,10 +29,7 @@
// Handles a wheel even and updates the scroll offsets // Handles a wheel even and updates the scroll offsets
const handleWheel = e => { const handleWheel = e => {
e.preventDefault() e.preventDefault()
const modifier = e.ctrlKey || e.metaKey debouncedHandleWheel(e.deltaX, e.deltaY, e.clientY)
let x = modifier ? e.deltaY : e.deltaX
let y = modifier ? e.deltaX : e.deltaY
debouncedHandleWheel(x, y, e.clientY)
} }
const debouncedHandleWheel = domDebounce((deltaX, deltaY, clientY) => { const debouncedHandleWheel = domDebounce((deltaX, deltaY, clientY) => {
const { top, left } = $scroll const { top, left } = $scroll

View File

@ -2,8 +2,17 @@
import { getContext } from "svelte" import { getContext } from "svelte"
import GridScrollWrapper from "./GridScrollWrapper.svelte" import GridScrollWrapper from "./GridScrollWrapper.svelte"
import HeaderCell from "../cells/HeaderCell.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)
</script> </script>
<div class="header"> <div class="header">
@ -14,6 +23,13 @@
{/each} {/each}
</div> </div>
</GridScrollWrapper> </GridScrollWrapper>
<div
class="add"
style="left:{left}px"
on:click={() => dispatch("add-column")}
>
<Icon name="Add" />
</div>
</div> </div>
<style> <style>
@ -27,4 +43,20 @@
.row { .row {
display: flex; display: flex;
} }
.add {
height: var(--default-row-height);
display: grid;
place-items: center;
width: 40px;
position: absolute;
top: 0;
border-left: var(--cell-border);
border-right: var(--cell-border);
border-bottom: var(--cell-border);
background: var(--spectrum-global-color-gray-100);
}
.add:hover {
background: var(--spectrum-global-color-gray-200);
cursor: pointer;
}
</style> </style>

View File

@ -0,0 +1,265 @@
<script>
import { getContext, onDestroy, onMount, tick } from "svelte"
import { Icon, Button } from "@budibase/bbui"
import GridScrollWrapper from "./GridScrollWrapper.svelte"
import DataCell from "../cells/DataCell.svelte"
import { fade } from "svelte/transition"
import { GutterWidth } from "../lib/constants"
import { NewRowID } from "../lib/constants"
import GutterCell from "../cells/GutterCell.svelte"
const {
hoveredRowId,
focusedCellId,
stickyColumn,
scroll,
dispatch,
rows,
focusedCellAPI,
tableId,
subscribe,
renderedRows,
renderedColumns,
rowHeight,
hasNextPage,
maxScrollTop,
rowVerticalInversionIndex,
columnHorizontalInversionIndex,
} = getContext("grid")
let isAdding = false
let newRow = {}
let offset = 0
$: firstColumn = $stickyColumn || $renderedColumns[0]
$: width = GutterWidth + ($stickyColumn?.width || 0)
$: $tableId, (isAdding = false)
$: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
const shouldInvertY = (offset, inversionIndex, rows) => {
if (offset === 0) {
return false
}
return rows.length >= inversionIndex
}
const addRow = async () => {
// Blur the active cell and tick to let final value updates propagate
$focusedCellAPI?.blur()
await tick()
// Create row
const newRowIndex = offset ? undefined : 0
const savedRow = await rows.actions.addRow(newRow, newRowIndex)
if (savedRow) {
// Reset state
clear()
// Select the first cell if possible
if (firstColumn) {
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
}
}
}
const clear = () => {
isAdding = false
$focusedCellId = null
$hoveredRowId = null
document.removeEventListener("keydown", handleKeyPress)
}
const startAdding = async () => {
if (isAdding) {
return
}
// If we have a next page of data then we aren't truly at the bottom, so we
// render the add row component at the top
if ($hasNextPage) {
offset = 0
}
// If we don't have a next page then we're at the bottom and can scroll to
// the max available offset
else {
scroll.update(state => ({
...state,
top: $maxScrollTop,
}))
offset = $renderedRows.length * $rowHeight - ($maxScrollTop % $rowHeight)
if ($renderedRows.length !== 0) {
offset -= 1
}
}
// Update state and select initial cell
newRow = {}
isAdding = true
$hoveredRowId = NewRowID
if (firstColumn) {
$focusedCellId = `${NewRowID}-${firstColumn.name}`
}
// Attach key listener
document.addEventListener("keydown", handleKeyPress)
}
const updateValue = (rowId, columnName, val) => {
newRow[columnName] = val
}
const addViaModal = () => {
clear()
dispatch("add-row")
}
const handleKeyPress = e => {
if (!isAdding) {
return
}
if (e.key === "Escape") {
// Only close the new row component if we aren't actively inside a cell
if (!$focusedCellAPI?.isActive()) {
e.preventDefault()
clear()
}
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
addRow()
}
}
onMount(() => subscribe("add-row-inline", startAdding))
onDestroy(() => {
document.removeEventListener("keydown", handleKeyPress)
})
</script>
<!-- Only show new row functionality if we have any columns -->
{#if isAdding}
<div
class="container"
class:floating={offset > 0}
style="--offset:{offset}px; --sticky-width:{width}px;"
>
<div class="underlay sticky" transition:fade={{ duration: 130 }} />
<div class="underlay" transition:fade={{ duration: 130 }} />
<div class="sticky-column" transition:fade={{ duration: 130 }}>
<GutterCell on:expand={addViaModal} rowHovered>
<Icon name="Add" color="var(--spectrum-global-color-gray-500)" />
</GutterCell>
{#if $stickyColumn}
{@const cellId = `${NewRowID}-${$stickyColumn.name}`}
<DataCell
{cellId}
rowFocused
column={$stickyColumn}
row={newRow}
focused={$focusedCellId === cellId}
width={$stickyColumn.width}
{updateValue}
rowIdx={0}
{invertY}
/>
{/if}
</div>
<div class="normal-columns" transition:fade={{ duration: 130 }}>
<GridScrollWrapper scrollHorizontally wheelInteractive>
<div class="row">
{#each $renderedColumns as column, columnIdx}
{@const cellId = `new-${column.name}`}
{#key cellId}
<DataCell
{cellId}
{column}
{updateValue}
rowFocused
row={newRow}
focused={$focusedCellId === cellId}
width={column.width}
rowIdx={0}
invertX={columnIdx >= $columnHorizontalInversionIndex}
{invertY}
/>
{/key}
{/each}
</div>
</GridScrollWrapper>
</div>
<div class="buttons" transition:fade={{ duration: 130 }}>
<Button size="M" cta on:click={addRow}>Save</Button>
<Button size="M" secondary newStyles on:click={clear}>Cancel</Button>
</div>
</div>
{/if}
<style>
.container {
position: absolute;
top: var(--default-row-height);
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
align-items: stretch;
}
.container :global(.cell) {
--cell-background: var(--spectrum-global-color-gray-75) !important;
}
.container.floating :global(.cell) {
height: calc(var(--row-height) + 1px);
border-top: var(--cell-border);
}
/* Underlay sits behind everything */
.underlay {
position: absolute;
content: "";
left: 0;
top: 0;
height: 100%;
width: 100%;
background: var(--cell-background);
opacity: 0.8;
}
.underlay.sticky {
z-index: 2;
width: var(--sticky-width);
}
/* Floating buttons which sit on top of the underlay but below the sticky column */
.buttons {
display: flex;
flex-direction: row;
gap: 8px;
pointer-events: all;
z-index: 3;
position: absolute;
top: calc(var(--row-height) + var(--offset) + 24px);
left: var(--gutter-width);
}
/* Sticky column styles */
.sticky-column {
display: flex;
z-index: 4;
position: relative;
align-self: flex-start;
flex: 0 0 var(--sticky-width);
}
.sticky-column :global(.cell:not(:last-child)) {
border-right: none;
}
.sticky-column,
.normal-columns {
margin-top: var(--offset);
}
/* Normal column styles */
.row {
width: 0;
display: flex;
}
</style>

View File

@ -1,247 +0,0 @@
<script>
import GridCell from "../cells/GridCell.svelte"
import { getContext, onMount } from "svelte"
import { Icon, Button } from "@budibase/bbui"
import GridScrollWrapper from "./GridScrollWrapper.svelte"
import DataCell from "../cells/DataCell.svelte"
import { fly } from "svelte/transition"
import { GutterWidth } from "../lib/constants"
const {
hoveredRowId,
focusedCellId,
stickyColumn,
scroll,
config,
dispatch,
visibleColumns,
rows,
showHScrollbar,
tableId,
subscribe,
scrollLeft,
} = getContext("grid")
let isAdding = false
let newRow = {}
let touched = false
$: firstColumn = $stickyColumn || $visibleColumns[0]
$: rowHovered = $hoveredRowId === "new"
$: rowFocused = $focusedCellId?.startsWith("new-")
$: width = GutterWidth + ($stickyColumn?.width || 0)
$: $tableId, (isAdding = false)
const addRow = async () => {
// Create row
const savedRow = await rows.actions.addRow(newRow, 0)
if (savedRow) {
// Select the first cell if possible
if (firstColumn) {
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
}
// Reset state
isAdding = false
scroll.set({
left: 0,
top: 0,
})
}
}
const cancel = () => {
isAdding = false
}
const startAdding = () => {
newRow = {}
isAdding = true
if (firstColumn) {
$focusedCellId = `new-${firstColumn.name}`
}
}
const updateValue = (rowId, columnName, val) => {
touched = true
newRow[columnName] = val
}
const addViaModal = () => {
isAdding = false
dispatch("add-row")
}
onMount(() => subscribe("add-row-inline", startAdding))
</script>
<!-- Only show new row functionality if we have any columns -->
{#if isAdding}
<div class="container" transition:fly={{ y: 20, duration: 130 }}>
<div class="content" class:above-scrollbar={$showHScrollbar}>
<div
class="new-row"
on:mouseenter={() => ($hoveredRowId = "new")}
on:mouseleave={() => ($hoveredRowId = null)}
>
<div
class="sticky-column"
style="flex: 0 0 {width}px"
class:scrolled={$scrollLeft > 0}
>
<GridCell width={GutterWidth} {rowHovered} {rowFocused}>
<div class="gutter">
<div class="number">1</div>
{#if $config.allowExpandRows}
<Icon
name="Maximize"
size="S"
hoverable
on:click={addViaModal}
/>
{/if}
</div>
</GridCell>
{#if $stickyColumn}
{@const cellId = `new-${$stickyColumn.name}`}
<DataCell
{cellId}
column={$stickyColumn}
row={newRow}
{rowHovered}
focused={$focusedCellId === cellId}
{rowFocused}
width={$stickyColumn.width}
{updateValue}
rowIdx={0}
/>
{/if}
</div>
<GridScrollWrapper scrollHorizontally wheelInteractive>
<div class="row">
{#each $visibleColumns as column}
{@const cellId = `new-${column.name}`}
{#key cellId}
<DataCell
{cellId}
{column}
row={newRow}
{rowHovered}
focused={$focusedCellId === cellId}
{rowFocused}
width={column.width}
{updateValue}
rowIdx={0}
/>
{/key}
{/each}
</div>
</GridScrollWrapper>
</div>
</div>
<div class="buttons">
<Button size="M" cta on:click={addRow}>Save</Button>
<Button size="M" secondary newStyles on:click={cancel}>Cancel</Button>
</div>
</div>
{/if}
<style>
.container {
pointer-events: none;
position: absolute;
top: var(--row-height);
left: 0;
width: 100%;
padding-bottom: 800px;
display: flex;
flex-direction: column;
align-items: stretch;
}
.container:before {
position: absolute;
content: "";
left: 0;
top: 0;
height: 100%;
width: 100%;
background: var(--cell-background);
opacity: 0.8;
z-index: -1;
}
.content {
pointer-events: all;
background: var(--background);
border-bottom: var(--cell-border);
}
.new-row {
display: flex;
bottom: 0;
left: 0;
width: 100%;
transition: margin-bottom 130ms ease-out;
}
.new-row :global(.cell) {
--cell-background: var(--background) !important;
border-bottom: none;
}
.sticky-column {
display: flex;
z-index: 1;
position: relative;
}
/* Don't show borders between cells in the sticky column */
.sticky-column :global(.cell:not(:last-child)) {
border-right: none;
}
.row {
width: 0;
display: flex;
}
/* Add shadow when scrolled */
.sticky-column.scrolled {
/*box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1);*/
}
.sticky-column.scrolled:after {
content: "";
width: 10px;
height: 100%;
background: linear-gradient(to right, rgba(0, 0, 0, 0.05), transparent);
left: 100%;
top: 0;
position: absolute;
}
/* 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);
}
/* Floating buttons */
.buttons {
display: flex;
flex-direction: row;
gap: 8px;
margin: 24px 0 0 var(--gutter-width);
pointer-events: all;
align-self: flex-start;
}
.number {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
color: var(--spectrum-global-color-gray-500);
}
</style>

View File

@ -1,24 +1,26 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { Checkbox, Icon } from "@budibase/bbui" import { Icon } from "@budibase/bbui"
import GridCell from "../cells/GridCell.svelte" import GridCell from "../cells/GridCell.svelte"
import DataCell from "../cells/DataCell.svelte" import DataCell from "../cells/DataCell.svelte"
import GridScrollWrapper from "./GridScrollWrapper.svelte" import GridScrollWrapper from "./GridScrollWrapper.svelte"
import HeaderCell from "../cells/HeaderCell.svelte" import HeaderCell from "../cells/HeaderCell.svelte"
import { GutterWidth } from "../lib/constants" import { GutterWidth, BlankRowID } from "../lib/constants"
import GutterCell from "../cells/GutterCell.svelte"
const { const {
rows, rows,
selectedRows, selectedRows,
stickyColumn, stickyColumn,
renderedColumns,
renderedRows, renderedRows,
focusedCellId, focusedCellId,
hoveredRowId, hoveredRowId,
config, config,
selectedCellMap, selectedCellMap,
focusedRow, focusedRow,
dispatch,
scrollLeft, scrollLeft,
dispatch,
} = getContext("grid") } = getContext("grid")
$: rowCount = $rows.length $: rowCount = $rows.length
@ -37,19 +39,6 @@
$selectedRows = allRows $selectedRows = allRows
} }
} }
const selectRow = id => {
selectedRows.update(state => {
let newState = {
...state,
[id]: !state[id],
}
if (!newState[id]) {
delete newState[id]
}
return newState
})
}
</script> </script>
<div <div
@ -58,26 +47,14 @@
class:scrolled={$scrollLeft > 0} class:scrolled={$scrollLeft > 0}
> >
<div class="header row"> <div class="header row">
<GridCell width={GutterWidth} defaultHeight center> <GutterCell
<div class="gutter"> disableExpand
<div class="checkbox visible"> disableNumber
{#if $config.allowDeleteRows} on:select={selectAll}
<div on:click={selectAll}> defaultHeight
<Checkbox rowSelected={selectedRowCount && selectedRowCount === rowCount}
value={rowCount && selectedRowCount === rowCount}
disabled={!$renderedRows.length} disabled={!$renderedRows.length}
/> />
</div>
{/if}
</div>
{#if $config.allowExpandRows}
<div class="expand">
<Icon name="Maximize" size="S" />
</div>
{/if}
</div>
</GridCell>
{#if $stickyColumn} {#if $stickyColumn}
<HeaderCell column={$stickyColumn} orderable={false} idx="sticky" /> <HeaderCell column={$stickyColumn} orderable={false} idx="sticky" />
{/if} {/if}
@ -95,41 +72,7 @@
on:mouseenter={() => ($hoveredRowId = row._id)} on:mouseenter={() => ($hoveredRowId = row._id)}
on:mouseleave={() => ($hoveredRowId = null)} on:mouseleave={() => ($hoveredRowId = null)}
> >
<GridCell <GutterCell {row} {rowFocused} {rowHovered} {rowSelected} />
width={GutterWidth}
highlighted={rowFocused || rowHovered}
selected={rowSelected}
>
<div class="gutter">
<div
on:click={() => selectRow(row._id)}
class="checkbox"
class:visible={$config.allowDeleteRows &&
(rowSelected || rowHovered || rowFocused)}
>
<Checkbox value={rowSelected} />
</div>
<div
class="number"
class:visible={!$config.allowDeleteRows ||
!(rowSelected || rowHovered || rowFocused)}
>
{row.__idx + 1}
</div>
{#if $config.allowExpandRows}
<div class="expand" class:visible={rowFocused || rowHovered}>
<Icon
name="Maximize"
hoverable
size="S"
on:click={() => {
dispatch("edit-row", row)
}}
/>
</div>
{/if}
</div>
</GridCell>
{#if $stickyColumn} {#if $stickyColumn}
<DataCell <DataCell
{row} {row}
@ -146,6 +89,24 @@
{/if} {/if}
</div> </div>
{/each} {/each}
{#if $config.allowAddRows && ($renderedColumns.length || $stickyColumn)}
<div
class="row new"
on:mouseenter={() => ($hoveredRowId = BlankRowID)}
on:mouseleave={() => ($hoveredRowId = null)}
on:click={() => dispatch("add-row-inline")}
>
<GutterCell disableExpand rowHovered={$hoveredRowId === BlankRowID}>
<Icon name="Add" color="var(--spectrum-global-color-gray-500)" />
</GutterCell>
{#if $stickyColumn}
<GridCell
width={$stickyColumn.width}
highlighted={$hoveredRowId === BlankRowID}
/>
{/if}
</div>
{/if}
</GridScrollWrapper> </GridScrollWrapper>
</div> </div>
</div> </div>
@ -156,6 +117,7 @@
flex-direction: column; flex-direction: column;
position: relative; position: relative;
z-index: 2; z-index: 2;
background: var(--cell-background);
} }
/* Add right border */ /* Add right border */
@ -203,43 +165,7 @@
position: relative; position: relative;
flex: 1 1 auto; flex: 1 1 auto;
} }
.row.new :global(*:hover) {
/* Styles for gutter */ cursor: pointer;
.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;
} }
</style> </style>

View File

@ -1,11 +1,14 @@
export const Padding = 276 export const Padding = 128
export const MaxCellRenderHeight = 252 export const MaxCellRenderHeight = 252
export const MaxCellRenderWidthOverflow = 200 export const MaxCellRenderWidthOverflow = 200
export const ScrollBarSize = 8 export const ScrollBarSize = 8
export const GutterWidth = 72 export const GutterWidth = 72
export const DefaultColumnWidth = 200 export const DefaultColumnWidth = 200
export const MinColumnWidth = 100 export const MinColumnWidth = 80
export const SmallRowHeight = 36 export const SmallRowHeight = 36
export const MediumRowHeight = 64 export const MediumRowHeight = 64
export const LargeRowHeight = 92 export const LargeRowHeight = 92
export const DefaultRowHeight = SmallRowHeight export const DefaultRowHeight = SmallRowHeight
export const NewRowID = "new"
export const BlankRowID = "blank"
export const RowPageSize = 100

View File

@ -15,7 +15,7 @@ const TypeIconMap = {
number: "123", number: "123",
boolean: "Boolean", boolean: "Boolean",
attachment: "AppleFiles", attachment: "AppleFiles",
link: "Link", link: "DataCorrelated",
formula: "Calculator", formula: "Calculator",
json: "Brackets", json: "Brackets",
} }

View File

@ -1,6 +1,7 @@
<script> <script>
import { getContext, onMount } from "svelte" import { getContext, onMount } from "svelte"
import { debounce } from "../../../utils/utils" import { debounce } from "../../../utils/utils"
import { NewRowID } from "../lib/constants"
const { const {
enrichedRows, enrichedRows,
@ -10,15 +11,24 @@
stickyColumn, stickyColumn,
focusedCellAPI, focusedCellAPI,
clipboard, clipboard,
dispatch,
selectedRows,
} = getContext("grid") } = getContext("grid")
// Global key listener which intercepts all key events // Global key listener which intercepts all key events
const handleKeyDown = e => { const handleKeyDown = e => {
// If nothing selected avoid processing further key presses // If nothing selected avoid processing further key presses
if (!$focusedCellId) { if (!$focusedCellId) {
if (e.key === "Tab") { if (e.key === "Tab" || e.key?.startsWith("Arrow")) {
e.preventDefault() e.preventDefault()
focusFirstCell() focusFirstCell()
} else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
dispatch("add-row-inline")
} else if (e.key === "Delete" || e.key === "Backspace") {
if (Object.keys($selectedRows).length) {
dispatch("request-bulk-delete")
}
} }
return return
} }
@ -26,10 +36,20 @@
// Always intercept certain key presses // Always intercept certain key presses
const api = $focusedCellAPI const api = $focusedCellAPI
if (e.key === "Escape") { if (e.key === "Escape") {
api?.blur?.() // By setting a tiny timeout here we can ensure that other listeners
// which depend on being able to read cell state on an escape keypress
// get a chance to observe the true state before we blur
if (api?.isActive()) {
setTimeout(api?.blur, 10)
} else {
$focusedCellId = null
}
return
} else if (e.key === "Tab") { } else if (e.key === "Tab") {
e.preventDefault()
api?.blur?.() api?.blur?.()
changeFocusedColumn(1) changeFocusedColumn(1)
return
} }
// Pass the key event to the selected cell and let it decide whether to // Pass the key event to the selected cell and let it decide whether to
@ -54,8 +74,12 @@
clipboard.actions.copy() clipboard.actions.copy()
break break
case "v": case "v":
if (!api?.isReadonly()) {
clipboard.actions.paste() clipboard.actions.paste()
}
break break
case "Enter":
dispatch("add-row-inline")
} }
} else { } else {
switch (e.key) { switch (e.key) {
@ -73,11 +97,19 @@
break break
case "Delete": case "Delete":
case "Backspace": case "Backspace":
if (Object.keys($selectedRows).length) {
dispatch("request-bulk-delete")
} else {
deleteSelectedCell() deleteSelectedCell()
}
break break
case "Enter": case "Enter":
focusCell() focusCell()
break break
case " ":
case "Space":
toggleSelectRow()
break
default: default:
startEnteringValue(e.key, e.which) startEnteringValue(e.key, e.which)
} }
@ -156,7 +188,7 @@
// Focuses the cell and starts entering a new value // Focuses the cell and starts entering a new value
const startEnteringValue = (key, keyCode) => { const startEnteringValue = (key, keyCode) => {
if ($focusedCellAPI) { if ($focusedCellAPI && !$focusedCellAPI.isReadonly()) {
const type = $focusedCellAPI.getType() const type = $focusedCellAPI.getType()
if (type === "number" && keyCodeIsNumber(keyCode)) { if (type === "number" && keyCodeIsNumber(keyCode)) {
$focusedCellAPI.setValue(parseInt(key)) $focusedCellAPI.setValue(parseInt(key))
@ -171,6 +203,17 @@
} }
} }
const toggleSelectRow = () => {
const id = $focusedRow?._id
if (!id || id === NewRowID) {
return
}
selectedRows.update(state => {
state[id] = !state[id]
return state
})
}
onMount(() => { onMount(() => {
document.addEventListener("keydown", handleKeyDown) document.addEventListener("keydown", handleKeyDown)
return () => { return () => {

View File

@ -13,6 +13,7 @@
copiedCell, copiedCell,
clipboard, clipboard,
dispatch, dispatch,
focusedCellAPI,
} = getContext("grid") } = getContext("grid")
$: style = makeStyle($menu) $: style = makeStyle($menu)
@ -49,7 +50,7 @@
</MenuItem> </MenuItem>
<MenuItem <MenuItem
icon="Paste" icon="Paste"
disabled={$copiedCell == null} disabled={$copiedCell == null || $focusedCellAPI?.isReadonly()}
on:click={clipboard.actions.paste} on:click={clipboard.actions.paste}
on:click={menu.actions.close} on:click={menu.actions.close}
> >

View File

@ -1,6 +1,7 @@
import { writable, derived, get } from "svelte/store" import { writable, derived, get } from "svelte/store"
import { fetchData } from "../../../fetch/fetchData" import { fetchData } from "../../../fetch/fetchData"
import { notifications } from "@budibase/bbui" import { notifications } from "@budibase/bbui"
import { NewRowID, RowPageSize } from "../lib/constants"
const initialSortState = { const initialSortState = {
column: null, column: null,
@ -16,6 +17,7 @@ export const createStores = () => {
const sort = writable(initialSortState) const sort = writable(initialSortState)
const rowChangeCache = writable({}) const rowChangeCache = writable({})
const inProgressChanges = writable({}) const inProgressChanges = writable({})
const hasNextPage = writable(false)
// Generate a lookup map to quick find a row by ID // Generate a lookup map to quick find a row by ID
const rowLookupMap = derived( const rowLookupMap = derived(
@ -50,6 +52,7 @@ export const createStores = () => {
sort, sort,
rowChangeCache, rowChangeCache,
inProgressChanges, inProgressChanges,
hasNextPage,
} }
} }
@ -70,6 +73,7 @@ export const deriveStores = context => {
rowChangeCache, rowChangeCache,
inProgressChanges, inProgressChanges,
previousFocusedRowId, previousFocusedRowId,
hasNextPage,
} = context } = context
const instanceLoaded = writable(false) const instanceLoaded = writable(false)
const fetch = writable(null) const fetch = writable(null)
@ -114,7 +118,7 @@ export const deriveStores = context => {
filter: [], filter: [],
sortColumn: initialSortState.column, sortColumn: initialSortState.column,
sortOrder: initialSortState.order, sortOrder: initialSortState.order,
limit: 100, limit: RowPageSize,
paginate: true, paginate: true,
}, },
}) })
@ -122,6 +126,7 @@ export const deriveStores = context => {
// Subscribe to changes of this fetch model // Subscribe to changes of this fetch model
unsubscribe = newFetch.subscribe($fetch => { unsubscribe = newFetch.subscribe($fetch => {
if ($fetch.loaded && !$fetch.loading) { if ($fetch.loaded && !$fetch.loading) {
hasNextPage.set($fetch.hasNextPage)
const $instanceLoaded = get(instanceLoaded) const $instanceLoaded = get(instanceLoaded)
const resetRows = $fetch.resetKey !== lastResetKey const resetRows = $fetch.resetKey !== lastResetKey
lastResetKey = $fetch.resetKey lastResetKey = $fetch.resetKey
@ -230,7 +235,7 @@ export const deriveStores = context => {
if (bubble) { if (bubble) {
throw error throw error
} else { } else {
handleValidationError("new", error) handleValidationError(NewRowID, error)
} }
} }
} }

View File

@ -1,6 +1,6 @@
import { writable, derived, get } from "svelte/store" import { writable, derived, get } from "svelte/store"
import { tick } from "svelte" import { tick } from "svelte"
import { Padding, GutterWidth, DefaultRowHeight } from "../lib/constants" import { Padding, GutterWidth } from "../lib/constants"
export const createStores = () => { export const createStores = () => {
const scroll = writable({ const scroll = writable({
@ -29,7 +29,7 @@ export const deriveStores = context => {
// Derive vertical limits // Derive vertical limits
const contentHeight = derived( const contentHeight = derived(
[rows, rowHeight], [rows, rowHeight],
([$rows, $rowHeight]) => $rows.length * $rowHeight + Padding, ([$rows, $rowHeight]) => ($rows.length + 1) * $rowHeight + Padding,
0 0
) )
const maxScrollTop = derived( const maxScrollTop = derived(
@ -138,7 +138,7 @@ export const initialise = context => {
const $scroll = get(scroll) const $scroll = get(scroll)
const $bounds = get(bounds) const $bounds = get(bounds)
const $rowHeight = get(rowHeight) const $rowHeight = get(rowHeight)
const verticalOffset = DefaultRowHeight * 1.5 const verticalOffset = 60
// Ensure vertical position is viewable // Ensure vertical position is viewable
if ($focusedRow) { if ($focusedRow) {

View File

@ -1,8 +1,10 @@
import { writable, get, derived } from "svelte/store" import { writable, get, derived } from "svelte/store"
import { tick } from "svelte"
import { import {
DefaultRowHeight, DefaultRowHeight,
LargeRowHeight, LargeRowHeight,
MediumRowHeight, MediumRowHeight,
NewRowID,
} from "../lib/constants" } from "../lib/constants"
export const createStores = () => { export const createStores = () => {
@ -49,14 +51,14 @@ export const deriveStores = context => {
([$focusedCellId, $rowLookupMap, $enrichedRows]) => { ([$focusedCellId, $rowLookupMap, $enrichedRows]) => {
const rowId = $focusedCellId?.split("-")[0] const rowId = $focusedCellId?.split("-")[0]
if (rowId === "new") { // Edge case for new rows
// Edge case for new row if (rowId === NewRowID) {
return { _id: rowId } return { _id: NewRowID }
} else { }
// All normal rows // All normal rows
const index = $rowLookupMap[rowId] const index = $rowLookupMap[rowId]
return $enrichedRows[index] return $enrichedRows[index]
}
}, },
null null
) )
@ -101,7 +103,10 @@ export const initialise = context => {
} = context } = context
// Ensure we clear invalid rows from state if they disappear // Ensure we clear invalid rows from state if they disappear
rows.subscribe(() => { rows.subscribe(async () => {
// We tick here to ensure other derived stores have properly updated.
// We depend on the row lookup map which is a derived store,
await tick()
const $focusedCellId = get(focusedCellId) const $focusedCellId = get(focusedCellId)
const $selectedRows = get(selectedRows) const $selectedRows = get(selectedRows)
const $hoveredRowId = get(hoveredRowId) const $hoveredRowId = get(hoveredRowId)
@ -140,20 +145,6 @@ export const initialise = context => {
lastFocusedRowId = id lastFocusedRowId = id
}) })
// Reset selected rows when selected cell changes
focusedCellId.subscribe(id => {
if (id) {
selectedRows.set({})
}
})
// Unset selected cell when rows are selected
selectedRows.subscribe(rows => {
if (Object.keys(rows || {}).length) {
focusedCellId.set(null)
}
})
// Remove hovered row when a cell is selected // Remove hovered row when a cell is selected
focusedCellId.subscribe(cell => { focusedCellId.subscribe(cell => {
if (cell && get(hoveredRowId)) { if (cell && get(hoveredRowId)) {

View File

@ -2,6 +2,7 @@ import { derived, get } from "svelte/store"
import { import {
MaxCellRenderHeight, MaxCellRenderHeight,
MaxCellRenderWidthOverflow, MaxCellRenderWidthOverflow,
MinColumnWidth,
ScrollBarSize, ScrollBarSize,
} from "../lib/constants" } from "../lib/constants"
@ -45,11 +46,16 @@ export const deriveStores = context => {
) )
// Derive visible columns // Derive visible columns
const scrollLeftRounded = derived(scrollLeft, $scrollLeft => {
const interval = MinColumnWidth
return Math.round($scrollLeft / interval) * interval
})
const renderedColumns = derived( const renderedColumns = derived(
[visibleColumns, scrollLeft, width], [visibleColumns, scrollLeftRounded, width],
([$visibleColumns, $scrollLeft, $width]) => { ([$visibleColumns, $scrollLeft, $width], set) => {
if (!$visibleColumns.length) { if (!$visibleColumns.length) {
return [] set([])
return
} }
let startColIdx = 0 let startColIdx = 0
let rightEdge = $visibleColumns[0].width let rightEdge = $visibleColumns[0].width
@ -69,19 +75,17 @@ export const deriveStores = context => {
leftEdge += $visibleColumns[endColIdx].width leftEdge += $visibleColumns[endColIdx].width
endColIdx++ endColIdx++
} }
const nextRenderedColumns = $visibleColumns.slice(startColIdx, endColIdx) // Render an additional column on either side to account for
// debounce column updates based on scroll position
// Cautiously shrink the number of rendered columns. const next = $visibleColumns.slice(
// This is to avoid rapidly shrinking and growing the visible column count Math.max(0, startColIdx - 1),
// which results in remounting cells endColIdx + 1
const currentCount = get(renderedColumns).length )
if (currentCount === nextRenderedColumns.length + 1) { const current = get(renderedColumns)
return $visibleColumns.slice(startColIdx, endColIdx + 1) if (JSON.stringify(next) !== JSON.stringify(current)) {
} else { set(next)
return nextRenderedColumns }
} }
},
[]
) )
const hiddenColumnsWidth = derived( const hiddenColumnsWidth = derived(

View File

@ -1,5 +1,4 @@
import env from "../../environment" import env from "../../environment"
import packageJson from "../../../package.json"
import { import {
createLinkView, createLinkView,
createRoutingView, createRoutingView,
@ -24,6 +23,7 @@ import {
migrations, migrations,
objectStore, objectStore,
ErrorCode, ErrorCode,
env as envCore,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import { USERS_TABLE_SCHEMA } from "../../constants" import { USERS_TABLE_SCHEMA } from "../../constants"
import { buildDefaultDocs } from "../../db/defaultData/datasource_bb_default" import { buildDefaultDocs } from "../../db/defaultData/datasource_bb_default"
@ -264,7 +264,7 @@ async function performAppCreate(ctx: UserCtx) {
_rev: undefined, _rev: undefined,
appId, appId,
type: "app", type: "app",
version: packageJson.version, version: envCore.VERSION,
componentLibraries: ["@budibase/standard-components"], componentLibraries: ["@budibase/standard-components"],
name: name, name: name,
url: url, url: url,
@ -433,7 +433,7 @@ export async function updateClient(ctx: UserCtx) {
} }
// Update versions in app package // Update versions in app package
const updatedToVersion = packageJson.version const updatedToVersion = envCore.VERSION
const appPackageUpdates = { const appPackageUpdates = {
version: updatedToVersion, version: updatedToVersion,
revertableVersion: currentVersion, revertableVersion: currentVersion,

View File

@ -4,7 +4,7 @@ import { checkSlashesInUrl } from "../../utilities"
import { request } from "../../utilities/workerRequests" import { request } from "../../utilities/workerRequests"
import { clearLock as redisClearLock } from "../../utilities/redis" import { clearLock as redisClearLock } from "../../utilities/redis"
import { DocumentType } from "../../db/utils" import { DocumentType } from "../../db/utils"
import { context } from "@budibase/backend-core" import { context, env as envCore } from "@budibase/backend-core"
import { events, db as dbCore, cache } from "@budibase/backend-core" import { events, db as dbCore, cache } from "@budibase/backend-core"
async function redirect(ctx: any, method: string, path: string = "global") { async function redirect(ctx: any, method: string, path: string = "global") {
@ -121,7 +121,7 @@ export async function revert(ctx: any) {
} }
export async function getBudibaseVersion(ctx: any) { export async function getBudibaseVersion(ctx: any) {
const version = require("../../../package.json").version const version = envCore.VERSION
ctx.body = { ctx.body = {
version, version,
} }

View File

@ -1,31 +1,32 @@
import { import {
Datasource,
FieldSchema,
FieldType,
FilterType, FilterType,
IncludeRelationship, IncludeRelationship,
Operation, Operation,
PaginationJson, PaginationJson,
RelationshipsJson, RelationshipsJson,
RelationshipTypes,
Row,
SearchFilters, SearchFilters,
SortJson, SortJson,
Datasource,
FieldSchema,
Row,
Table,
RelationshipTypes,
FieldType,
SortType, SortType,
Table,
} from "@budibase/types" } from "@budibase/types"
import { import {
breakExternalTableId,
breakRowIdField, breakRowIdField,
convertRowId,
generateRowIdField, generateRowIdField,
isRowId, isRowId,
convertRowId, isSQL,
} from "../../../integrations/utils" } from "../../../integrations/utils"
import { getDatasourceAndQuery } from "./utils" import { getDatasourceAndQuery } from "./utils"
import { FieldTypes } from "../../../constants" import { FieldTypes } from "../../../constants"
import { breakExternalTableId, isSQL } from "../../../integrations/utils"
import { processObjectSync } from "@budibase/string-templates" import { processObjectSync } from "@budibase/string-templates"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { processFormulas, processDates } from "../../../utilities/rowProcessor" import { processDates, processFormulas } from "../../../utilities/rowProcessor"
import { db as dbCore } from "@budibase/backend-core" import { db as dbCore } from "@budibase/backend-core"
import sdk from "../../../sdk" import sdk from "../../../sdk"
@ -382,10 +383,18 @@ export class ExternalRequest {
} }
const display = linkedTable.primaryDisplay const display = linkedTable.primaryDisplay
for (let key of Object.keys(row[relationship.column])) { for (let key of Object.keys(row[relationship.column])) {
const related: Row = row[relationship.column][key] let relatedRow: Row = row[relationship.column][key]
// add this row as context for the relationship
for (let col of Object.values(linkedTable.schema)) {
if (col.type === FieldType.LINK && col.tableId === table._id) {
relatedRow[col.name] = [row]
}
}
relatedRow = processFormulas(linkedTable, relatedRow)
const relatedDisplay = display ? relatedRow[display] : undefined
row[relationship.column][key] = { row[relationship.column][key] = {
primaryDisplay: display ? related[display] : undefined, primaryDisplay: relatedDisplay || "Invalid display column",
_id: related._id, _id: relatedRow._id,
} }
} }
} }

View File

@ -1,9 +1,8 @@
import Router from "@koa/router" import Router from "@koa/router"
import { auth, middleware } from "@budibase/backend-core" import { auth, middleware, env as envCore } from "@budibase/backend-core"
import currentApp from "../middleware/currentapp" import currentApp from "../middleware/currentapp"
import zlib from "zlib" import zlib from "zlib"
import { mainRoutes, staticRoutes, publicRoutes } from "./routes" import { mainRoutes, staticRoutes, publicRoutes } from "./routes"
import pkg from "../../package.json"
import { middleware as pro } from "@budibase/pro" import { middleware as pro } from "@budibase/pro"
export { shutdown } from "./routes/public" export { shutdown } from "./routes/public"
const compress = require("koa-compress") const compress = require("koa-compress")
@ -11,7 +10,7 @@ const compress = require("koa-compress")
export const router: Router = new Router() export const router: Router = new Router()
router.get("/health", ctx => (ctx.status = 200)) router.get("/health", ctx => (ctx.status = 200))
router.get("/version", ctx => (ctx.body = pkg.version)) router.get("/version", ctx => (ctx.body = envCore.VERSION))
router.use(middleware.errorHandling) router.use(middleware.errorHandling)

View File

@ -921,6 +921,7 @@ describe("row api - postgres", () => {
[m2mFieldName]: [ [m2mFieldName]: [
{ {
_id: row._id, _id: row._id,
primaryDisplay: "Invalid display column",
}, },
], ],
}) })
@ -929,6 +930,7 @@ describe("row api - postgres", () => {
[m2mFieldName]: [ [m2mFieldName]: [
{ {
_id: row._id, _id: row._id,
primaryDisplay: "Invalid display column",
}, },
], ],
}) })

View File

@ -79,10 +79,17 @@ function generateSchema(
if (!relatedTable) { if (!relatedTable) {
throw "Referenced table doesn't exist" throw "Referenced table doesn't exist"
} }
const relatedPrimary = relatedTable.primary[0]
const externalType = relatedTable.schema[relatedPrimary].externalType
if (externalType) {
schema.specificType(column.foreignKey, externalType)
} else {
schema.integer(column.foreignKey).unsigned() schema.integer(column.foreignKey).unsigned()
}
schema schema
.foreign(column.foreignKey) .foreign(column.foreignKey)
.references(`${tableName}.${relatedTable.primary[0]}`) .references(`${tableName}.${relatedPrimary}`)
} }
break break
} }

View File

@ -1,11 +1,11 @@
import { import {
FieldTypes,
FormulaTypes,
AutoFieldDefaultNames, AutoFieldDefaultNames,
AutoFieldSubTypes, AutoFieldSubTypes,
FieldTypes,
FormulaTypes,
} from "../../constants" } from "../../constants"
import { processStringSync } from "@budibase/string-templates" import { processStringSync } from "@budibase/string-templates"
import { FieldSchema, Table, Row } from "@budibase/types" import { FieldSchema, FieldType, Row, Table } from "@budibase/types"
/** /**
* If the subtype has been lost for any reason this works out what * If the subtype has been lost for any reason this works out what
@ -50,6 +50,7 @@ export function processFormulas(
const isStatic = schema.formulaType === FormulaTypes.STATIC const isStatic = schema.formulaType === FormulaTypes.STATIC
if ( if (
schema.type !== FieldTypes.FORMULA || schema.type !== FieldTypes.FORMULA ||
schema.formula == null ||
(dynamic && isStatic) || (dynamic && isStatic) ||
(!dynamic && !isStatic) (!dynamic && !isStatic)
) { ) {
@ -57,7 +58,6 @@ export function processFormulas(
} }
// iterate through rows and process formula // iterate through rows and process formula
for (let i = 0; i < rowArray.length; i++) { for (let i = 0; i < rowArray.length; i++) {
if (schema.formula) {
let row = rowArray[i] let row = rowArray[i]
let context = contextRows ? contextRows[i] : row let context = contextRows ? contextRows[i] : row
rowArray[i] = { rowArray[i] = {
@ -66,7 +66,6 @@ export function processFormulas(
} }
} }
} }
}
return single ? rowArray[0] : rowArray return single ? rowArray[0] : rowArray
} }

View File

@ -8,13 +8,11 @@
"esModuleInterop": true, "esModuleInterop": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"incremental": true, "incremental": true,
"types": [ "node", "jest" ], "types": ["node", "jest"],
"outDir": "dist", "outDir": "dist/src",
"skipLibCheck": true "skipLibCheck": true
}, },
"include": [ "include": ["src/**/*"],
"src/**/*"
],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"dist", "dist",

View File

@ -5,6 +5,7 @@
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,
"baseUrl": ".", "baseUrl": ".",
"outDir": "dist",
"paths": { "paths": {
"@budibase/types": ["../types/src"], "@budibase/types": ["../types/src"],
"@budibase/backend-core": ["../backend-core/src"], "@budibase/backend-core": ["../backend-core/src"],
@ -17,6 +18,12 @@
"require": ["tsconfig-paths/register"], "require": ["tsconfig-paths/register"],
"swc": true "swc": true
}, },
"include": ["src/**/*", "specs", "package.json"], "references": [
{ "path": "../types" },
{ "path": "../backend-core" },
{ "path": "../shared-core" },
{ "path": "../../../budibase-pro/packages/pro" }
],
"include": ["src/**/*", "specs"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }

View File

@ -14,7 +14,7 @@
"outDir": "dist", "outDir": "dist",
"skipLibCheck": true "skipLibCheck": true
}, },
"include": ["**/*.js", "**/*.ts", "package.json"], "include": ["**/*.js", "**/*.ts"],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"dist", "dist",

View File

@ -2,7 +2,7 @@
"extends": "./tsconfig-base.build.json", "extends": "./tsconfig-base.build.json",
"compilerOptions": { "compilerOptions": {
"module": "commonjs", "module": "commonjs",
"outDir": "dist/cjs", "outDir": "dist/cjs/src",
"target": "es2015" "target": "es2015"
} }
} }

View File

@ -2,7 +2,7 @@
"extends": "./tsconfig-base.build.json", "extends": "./tsconfig-base.build.json",
"compilerOptions": { "compilerOptions": {
"module": "esnext", "module": "esnext",
"outDir": "dist/mjs", "outDir": "dist/mjs/src",
"target": "esnext" "target": "esnext"
} }
} }

View File

@ -16,6 +16,11 @@
"require": ["tsconfig-paths/register"], "require": ["tsconfig-paths/register"],
"swc": true "swc": true
}, },
"include": ["src/**/*", "package.json"], "references": [
{ "path": "../types" },
{ "path": "../backend-core" },
{ "path": "../../../budibase-pro/packages/pro" }
],
"include": ["src/**/*"],
"exclude": ["dist"] "exclude": ["dist"]
} }

View File

@ -22,6 +22,10 @@
"ts-node": { "ts-node": {
"require": ["tsconfig-paths/register"] "require": ["tsconfig-paths/register"]
}, },
"include": ["src/**/*", "package.json"], "references": [
{ "path": "../packages/types" },
{ "path": "../packages/backend-core" }
],
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }