From b522726afcb3a9a519a513832bf75d86a37a006d Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 28 Oct 2021 12:43:31 +0100 Subject: [PATCH 01/30] Allow in-preview editing of paragraphs and headings --- packages/client/manifest.json | 2 ++ .../client/src/components/Component.svelte | 25 ++++++++++++----- .../client/src/components/app/Heading.svelte | 19 ++++++++++--- .../client/src/components/app/Text.svelte | 19 ++++++++++--- .../src/components/preview/Indicator.svelte | 3 +-- .../preview/SelectionIndicator.svelte | 6 ++++- packages/client/src/stores/builder.js | 20 +++++++------- packages/client/src/utils/styleable.js | 27 +++++++++++++------ 8 files changed, 88 insertions(+), 33 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 2adfd96626..63ac7b1c7f 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -589,6 +589,7 @@ "icon": "TextParagraph", "illegalChildren": ["section"], "showSettingsBar": true, + "editable": true, "settings": [ { "type": "text", @@ -695,6 +696,7 @@ "description": "A component for displaying heading text", "illegalChildren": ["section"], "showSettingsBar": true, + "editable": true, "settings": [ { "type": "text", diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index faf0226604..8200812477 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -60,21 +60,32 @@ $: instanceKey = JSON.stringify(rawProps) $: updateComponentProps(rawProps, instanceKey, $context) $: selected = - $builderStore.inBuilder && - $builderStore.selectedComponentId === instance._id + $builderStore.inBuilder && $builderStore.selectedComponentId === id $: inSelectedPath = $builderStore.selectedComponentPath?.includes(id) $: evaluateConditions(enrichedSettings?._conditions) $: componentSettings = { ...enrichedSettings, ...conditionalSettings } $: renderKey = `${propsHash}-${emptyState}` + $: editable = definition.editable + $: editing = editable && selected && $builderStore.editMode + $: draggable = interactive && !isLayout && !isScreen && !editing + $: droppable = interactive && !isLayout && !isScreen // Update component context $: componentStore.set({ id, children: children.length, - styles: { ...instance._styles, id, empty: emptyState, interactive }, + styles: { + ...instance._styles, + id, + empty: emptyState, + interactive, + draggable, + editable, + }, empty: emptyState, selected, name, + editing, }) const getRawProps = instance => { @@ -171,10 +182,6 @@ conditionalSettings = result.settingUpdates visible = nextVisible } - - // Drag and drop helper tags - $: draggable = interactive && !isLayout && !isScreen - $: droppable = interactive && !isLayout && !isScreen {#key renderKey} @@ -187,6 +194,7 @@ class:droppable class:empty class:interactive + class:editing data-id={id} data-name={name} > @@ -213,4 +221,7 @@ .draggable :global(*:hover) { cursor: grab; } + .editing :global(*:hover) { + cursor: auto; + } diff --git a/packages/client/src/components/app/Heading.svelte b/packages/client/src/components/app/Heading.svelte index bf4d902017..f3e283cf97 100644 --- a/packages/client/src/components/app/Heading.svelte +++ b/packages/client/src/components/app/Heading.svelte @@ -36,21 +36,34 @@ }, } } + + // Convert contenteditable HTML to text and save + const updateText = e => { + const html = e.target.innerHTML + const sanitized = html + .replace(/<\/div>
/gi, "\n") + .replace(/
/gi, "") + .replace(/<\/div>/gi, "") + .replace(/
/gi, "") + builderStore.actions.updateProp("text", sanitized) + } -

{componentText} -

+
From 4e4e07d1f267ee86c166d03e29207709d7630594 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 13:12:05 +0000 Subject: [PATCH 11/30] Fix race condition crash on initial builder data page load --- .../src/pages/builder/app/[application]/data/index.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/pages/builder/app/[application]/data/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/index.svelte index 5e5c1e2957..de9a392f9c 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/index.svelte @@ -7,7 +7,7 @@ let modal $: setupComplete = - $datasources.list.find(x => (x._id = "bb_internal")).entities.length > 1 || + $datasources.list.find(x => (x._id = "bb_internal"))?.entities.length > 1 || $datasources.list.length > 1 onMount(() => { From f14e6d8e22807bbdfd52c88d8f7ff788f89df330 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 13:15:31 +0000 Subject: [PATCH 12/30] Remove unused client event for iframe-loaded --- .../components/design/AppPreview/CurrentItemPreview.svelte | 5 ++++- .../src/components/design/AppPreview/iframeTemplate.js | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte index 25dd74e0d1..8e3e2dc21d 100644 --- a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte @@ -36,7 +36,6 @@ // Messages that can be sent from the iframe preview to the builder // Budibase events are and initalisation events const MessageTypes = { - IFRAME_LOADED: "iframe-loaded", READY: "ready", ERROR: "error", BUDIBASE: "type", @@ -157,6 +156,10 @@ const handleBudibaseEvent = event => { const { type, data } = event.data || event.detail + if (!type) { + return + } + if (type === "select-component" && data.id) { store.actions.components.select({ _id: data.id }) } else if (type === "update-prop") { diff --git a/packages/builder/src/components/design/AppPreview/iframeTemplate.js b/packages/builder/src/components/design/AppPreview/iframeTemplate.js index 05192fa4d3..e8c563db9f 100644 --- a/packages/builder/src/components/design/AppPreview/iframeTemplate.js +++ b/packages/builder/src/components/design/AppPreview/iframeTemplate.js @@ -84,7 +84,6 @@ export default ` if (window.loadBudibase) { window.loadBudibase() document.documentElement.classList.add("loaded") - window.parent.postMessage({ type: "iframe-loaded" }) } else { throw "The client library couldn't be loaded" } From ce75b8c9a75968611b470ba541c837b81c83f81e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 13:17:34 +0000 Subject: [PATCH 13/30] Disable drag and drop for components in the selected path when edit mode is enabled to fix firefox issue with text selection when draggable attribute is set --- packages/client/src/components/Component.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 5ea7a5ea84..e3e4502a10 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -63,6 +63,7 @@ $: selected = $builderStore.inBuilder && $builderStore.selectedComponentId === id $: inSelectedPath = $builderStore.selectedComponentPath?.includes(id) + $: inDragPath = inSelectedPath && $builderStore.editMode // Interactive components can be selected, dragged and highlighted inside // the builder preview @@ -72,7 +73,7 @@ !isBlock $: editable = definition.editable $: editing = editable && selected && $builderStore.editMode - $: draggable = !editing && interactive && !isLayout && !isScreen + $: draggable = !inDragPath && interactive && !isLayout && !isScreen $: droppable = interactive && !isLayout && !isScreen // Empty components are those which accept children but do not have any. From 5021f3df47c5b2d9cf9b28f50638e440d7aaf570 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 13:22:31 +0000 Subject: [PATCH 14/30] Ensure button is focused when starting to edit text --- packages/client/src/components/app/Button.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/app/Button.svelte b/packages/client/src/components/app/Button.svelte index 418e005287..f00b114b2b 100644 --- a/packages/client/src/components/app/Button.svelte +++ b/packages/client/src/components/app/Button.svelte @@ -35,9 +35,9 @@ {disabled} use:styleable={$component.styles} on:click={onClick} - class:editing={$component.editing} contenteditable={$component.editing} on:blur={$component.editing ? updateText : null} + bind:this={node} > {componentText} From b1f57829849215842db60dfc8a1eaa0b9a19fd6e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 13:35:20 +0000 Subject: [PATCH 15/30] Precent unecessary client app builder store updates to improve performance --- packages/client/src/stores/builder.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js index 22ee7b43b5..08ac6ba906 100644 --- a/packages/client/src/stores/builder.js +++ b/packages/client/src/stores/builder.js @@ -80,9 +80,15 @@ const createBuilderStore = () => { }) }, setDragging: dragging => { + if (dragging === get(writableStore).isDragging) { + return + } writableStore.update(state => ({ ...state, isDragging: dragging })) }, setEditMode: enabled => { + if (enabled === get(writableStore).editMode) { + return + } writableStore.update(state => ({ ...state, editMode: enabled })) }, } From 9728c6c9479595e671530fe538530c3fed738f36 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 13:38:47 +0000 Subject: [PATCH 16/30] Prevent unecessary updates to component settings to improve performance --- packages/builder/src/builderStore/store/frontend.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 7bbd474a92..1f1fb035a4 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -620,6 +620,9 @@ export const getFrontendStore = () => { if (!name || !component) { return } + if (component[name] === value) { + return + } component[name] = value store.update(state => { state.selectedComponentId = component._id From 6ca6eb0d4bcb52cabda34ce4e6da7a814099c39a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 16:28:48 +0000 Subject: [PATCH 17/30] Refactor how client app actions programmatically mutate data provider queries to allow for correctly handling removal of query extensions --- .../src/components/app/DataProvider.svelte | 47 ++++++++++++++++-- .../src/components/app/DateRangePicker.svelte | 49 ++++++++----------- packages/client/src/constants.js | 3 +- 3 files changed, 67 insertions(+), 32 deletions(-) diff --git a/packages/client/src/components/app/DataProvider.svelte b/packages/client/src/components/app/DataProvider.svelte index 68f6cc6c07..61f985bce9 100644 --- a/packages/client/src/components/app/DataProvider.svelte +++ b/packages/client/src/components/app/DataProvider.svelte @@ -34,12 +34,16 @@ let bookmarks = [null] let pageNumber = 0 let query = null + let queryExtensions = {} // Sorting can be overridden at run time, so we can't use the prop directly let currentSortColumn = sortColumn let currentSortOrder = sortOrder + $: currentSortColumn = sortColumn + $: currentSortOrder = sortOrder - $: query = buildLuceneQuery(filter) + $: defaultQuery = buildLuceneQuery(filter) + $: extendQuery(defaultQuery, queryExtensions) $: internalTable = dataSource?.type === "table" $: nestedProvider = dataSource?.type === "provider" $: hasNextPage = bookmarks[pageNumber + 1] != null @@ -91,8 +95,12 @@ metadata: { dataSource }, }, { - type: ActionTypes.SetDataProviderQuery, - callback: newQuery => (query = newQuery), + type: ActionTypes.AddDataProviderQueryExtension, + callback: addQueryExtension, + }, + { + type: ActionTypes.RemoveDataProviderQueryExtension, + callback: removeQueryExtension, }, { type: ActionTypes.SetDataProviderSorting, @@ -164,6 +172,7 @@ const sort = schema?.[sortColumn] ? sortColumn : undefined // For internal tables we use server-side processing + console.log("SEARCH!") const res = await API.searchTable({ tableId: dataSource.tableId, query, @@ -264,6 +273,38 @@ pageNumber-- allRows = res.rows } + + const addQueryExtension = (key, operator, field, value) => { + if (!key || !operator || !field) { + return + } + const extension = { operator, field, value } + queryExtensions = { ...queryExtensions, [key]: extension } + } + + const removeQueryExtension = key => { + if (!key) { + return + } + const newQueryExtensions = { ...queryExtensions } + delete newQueryExtensions[key] + queryExtensions = newQueryExtensions + } + + const extendQuery = (defaultQuery, extensions) => { + const extensionValues = Object.values(extensions || {}) + let extendedQuery = { ...query } + extensionValues.forEach(({ operator, field, value }) => { + extendedQuery[operator] = { + ...extendedQuery[operator], + [field]: value, + } + }) + + if (JSON.stringify(query) !== JSON.stringify(extendedQuery)) { + query = extendedQuery + } + }
diff --git a/packages/client/src/components/app/DateRangePicker.svelte b/packages/client/src/components/app/DateRangePicker.svelte index 9d725c634c..651a19abc4 100644 --- a/packages/client/src/components/app/DateRangePicker.svelte +++ b/packages/client/src/components/app/DateRangePicker.svelte @@ -3,7 +3,7 @@ import { getContext } from "svelte" import dayjs from "dayjs" import utc from "dayjs/plugin/utc" - import { onMount } from "svelte" + import { onDestroy } from "svelte" dayjs.extend(utc) @@ -14,7 +14,14 @@ const component = getContext("component") const { styleable, ActionTypes, getAction } = getContext("sdk") - const setQuery = getAction(dataProvider?.id, ActionTypes.SetDataProviderQuery) + $: addExtension = getAction( + dataProvider?.id, + ActionTypes.AddDataProviderQueryExtension + ) + $: removeExtension = getAction( + dataProvider?.id, + ActionTypes.RemoveDataProviderQueryExtension + ) const options = [ "Last 1 day", "Last 7 days", @@ -25,44 +32,30 @@ ] let value = options.includes(defaultValue) ? defaultValue : "Last 30 days" - const updateDateRange = option => { - const query = dataProvider?.state?.query - if (!query || !setQuery) { - return - } + $: queryExtension = getQueryExtension(value) + $: addExtension?.($component.id, "range", field, queryExtension) - value = option + const getQueryExtension = value => { let low = dayjs.utc().subtract(1, "year") let high = dayjs.utc().add(1, "day") - if (option === "Last 1 day") { + if (value === "Last 1 day") { low = dayjs.utc().subtract(1, "day") - } else if (option === "Last 7 days") { + } else if (value === "Last 7 days") { low = dayjs.utc().subtract(7, "days") - } else if (option === "Last 30 days") { + } else if (value === "Last 30 days") { low = dayjs.utc().subtract(30, "days") - } else if (option === "Last 3 months") { + } else if (value === "Last 3 months") { low = dayjs.utc().subtract(3, "months") - } else if (option === "Last 6 months") { + } else if (value === "Last 6 months") { low = dayjs.utc().subtract(6, "months") } - // Update data provider query with the new filter - setQuery({ - ...query, - range: { - ...query.range, - [field]: { - high: high.format(), - low: low.format(), - }, - }, - }) + return { low: low.format(), high: high.format() } } - // Update the range on mount to the initial value - onMount(() => { - updateDateRange(value) + onDestroy(() => { + removeExtension?.($component.id) }) @@ -71,6 +64,6 @@ placeholder={null} {options} {value} - on:change={e => updateDateRange(e.detail)} + on:change={e => (value = e.detail)} />
diff --git a/packages/client/src/constants.js b/packages/client/src/constants.js index 214a954929..740f279b36 100644 --- a/packages/client/src/constants.js +++ b/packages/client/src/constants.js @@ -25,7 +25,8 @@ export const UnsortableTypes = [ export const ActionTypes = { ValidateForm: "ValidateForm", RefreshDatasource: "RefreshDatasource", - SetDataProviderQuery: "SetDataProviderQuery", + AddDataProviderQueryExtension: "AddDataProviderQueryExtension", + RemoveDataProviderQueryExtension: "RemoveDataProviderQueryExtension", SetDataProviderSorting: "SetDataProviderSorting", ClearForm: "ClearForm", ChangeFormStep: "ChangeFormStep", From f61d89436b5f4793095deac2ab67a63e2265ee26 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 16:29:31 +0000 Subject: [PATCH 18/30] Optimise core memoization of client component props to improve performance significantly --- .../client/src/components/Component.svelte | 194 +++++++++--------- 1 file changed, 100 insertions(+), 94 deletions(-) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index e3e4502a10..d0029f5b17 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -4,7 +4,7 @@ -{#key renderKey} - {#if constructor && settings && (visible || inSelectedPath)} - - -
- - {#if children.length} - {#each children as child (child._id)} - - {/each} - {:else if emptyState} - - {:else if isBlock} - - {/if} - -
- {/if} -{/key} +{#if constructor && cachedSettings && (visible || inSelectedPath)} + + +
+ + {#if children.length} + {#each children as child (child._id)} + + {/each} + {:else if emptyState} + + {:else if isBlock} + + {/if} + +
+{/if}