From 999b2ffb19ea25dbdf4b8a7a9f9b154c0ee0794a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 23 Nov 2020 14:27:45 +0000 Subject: [PATCH 1/7] Add actual component for screenslot placeholder --- .../AppPreview/CurrentItemPreview.svelte | 48 ++----------------- .../AppPreview/iframeTemplate.js | 20 +------- .../src/ScreenSlotPlaceholder.svelte | 31 ++++++++++++ packages/standard-components/src/index.js | 1 + 4 files changed, 38 insertions(+), 62 deletions(-) create mode 100644 packages/standard-components/src/ScreenSlotPlaceholder.svelte diff --git a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte index c9fe02e786..243817dfda 100644 --- a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte @@ -3,57 +3,17 @@ import { store } from "builderStore" import iframeTemplate from "./iframeTemplate" import { Screen } from "builderStore/store/screenTemplates/utils/Screen" - import { Component } from "builderStore/store/screenTemplates/utils/Component" let iframe - // Styles for screenslot placeholder - const headingStyle = { - width: "500px", - padding: "8px", - } - const textStyle = { - ...headingStyle, - "max-width": "", - "text-align": "left", - } - - const heading = new Component("@budibase/standard-components/heading") - .normalStyle(headingStyle) - .type("h1") - .text("Screen Slot") - .instanceName("Heading") - const textScreenDisplay = new Component("@budibase/standard-components/text") - .normalStyle(textStyle) - .instanceName("Text") - .type("none") - .text( - "The screens that you create will be displayed inside this box. This box is just a placeholder, to show you the position of screens." - ) - const container = new Component("@budibase/standard-components/container") - .normalStyle({ - display: "flex", - "flex-direction": "column", - "align-items": "center", - flex: "1 1 auto", - }) - .type("div") - .instanceName("Container") - .addChild(heading) - .addChild(textScreenDisplay) + // Create screen slot placeholder for use when a page is selected rather + // than a screen const screenPlaceholder = new Screen() .name("Screen Placeholder") .route("*") - .component("@budibase/standard-components/container") - .mainType("div") + .component("@budibase/standard-components/screenslotplaceholder") .instanceName("Content Placeholder") - .normalStyle({ - flex: "1 1 auto", - }) - .addChild(container) .json() - // TODO: this ID is attached to how the screen slot is rendered, confusing, would be better a type etc - screenPlaceholder.props._id = "screenslot-placeholder" // Extract data to pass to the iframe $: page = $store.pages[$store.currentPageName] @@ -75,7 +35,7 @@ } } - // Refrech the preview when required + // Refresh the preview when required $: refreshContent(previewData) // Initialise the app when mounted diff --git a/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js b/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js index 2b12267a85..2d25aea92b 100644 --- a/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js +++ b/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js @@ -4,29 +4,13 @@ export default ` + +
+

Screen Slot

+ + The screens that you create will be displayed inside this box. +
+ This box is just a placeholder, to show you the position of screens. +
+
+ + diff --git a/packages/standard-components/src/index.js b/packages/standard-components/src/index.js index bc1fab539a..51f53956fa 100644 --- a/packages/standard-components/src/index.js +++ b/packages/standard-components/src/index.js @@ -23,4 +23,5 @@ export { default as rowdetail } from "./RowDetail.svelte" export { default as newrow } from "./NewRow.svelte" export { default as datepicker } from "./DatePicker.svelte" export { default as icon } from "./Icon.svelte" +export { default as screenslotplaceholder } from "./ScreenSlotPlaceholder.svelte" export * from "./charts" From 99c167c54fd15cc6240d055006c7328c871dbf79 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 23 Nov 2020 14:28:02 +0000 Subject: [PATCH 2/7] Fix reinstantiating app when builder props change --- packages/client/src/index.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/client/src/index.js b/packages/client/src/index.js index cb00d4b708..e925c3e273 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -1,9 +1,14 @@ import ClientApp from "./components/ClientApp.svelte" -// Initialise client app +let app + const loadBudibase = () => { - window.document.body.innerHTML = "" - new ClientApp({ + // Destroy old app if one exists + if (app) { + app.$destroy() + } + // Create new app + app = new ClientApp({ target: window.document.body, }) } From 18a0f3888b5b4b83a5fcb5647e14a1818e1fd86e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 24 Nov 2020 09:31:54 +0000 Subject: [PATCH 3/7] Add proper hot reloading of app preview when styles change --- .../client/src/components/Component.svelte | 21 +++--- .../client/src/components/DataProvider.svelte | 14 ++-- packages/client/src/components/Router.svelte | 4 +- packages/client/src/components/Screen.svelte | 9 ++- packages/client/src/index.js | 20 ++++-- packages/client/src/store/builder.js | 12 ++++ packages/client/src/store/index.js | 1 + packages/client/src/store/screens.js | 52 +++++++------- packages/client/src/utils/styleable.js | 69 ++++++++++++------- .../standard-components/src/Button.svelte | 2 +- packages/standard-components/src/Card.svelte | 2 +- .../src/CardHorizontal.svelte | 2 +- .../standard-components/src/Container.svelte | 26 +++---- .../standard-components/src/DatePicker.svelte | 2 +- packages/standard-components/src/Embed.svelte | 2 +- packages/standard-components/src/Form.svelte | 2 +- .../standard-components/src/Heading.svelte | 12 ++-- packages/standard-components/src/Icon.svelte | 2 +- packages/standard-components/src/Image.svelte | 2 +- packages/standard-components/src/Input.svelte | 2 +- packages/standard-components/src/Link.svelte | 2 +- packages/standard-components/src/List.svelte | 2 +- packages/standard-components/src/Login.svelte | 2 +- .../standard-components/src/Navigation.svelte | 2 +- .../standard-components/src/RichText.svelte | 2 +- .../src/ScreenSlotPlaceholder.svelte | 2 +- .../src/StackedList.svelte | 2 +- packages/standard-components/src/Text.svelte | 24 +++---- .../src/charts/ApexChart.svelte | 4 +- .../src/grid/Component.svelte | 10 ++- 30 files changed, 180 insertions(+), 130 deletions(-) create mode 100644 packages/client/src/store/builder.js diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 801080fea2..b17dd0d7d2 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -1,5 +1,6 @@ {#if constructor} {#if children && children.length} - {#each children as child} + {#each children as child (child._id)} {/each} {/if} diff --git a/packages/client/src/components/DataProvider.svelte b/packages/client/src/components/DataProvider.svelte index 48465599f2..08defe93b6 100644 --- a/packages/client/src/components/DataProvider.svelte +++ b/packages/client/src/components/DataProvider.svelte @@ -1,25 +1,19 @@ -{#if loaded} - -{/if} + diff --git a/packages/client/src/components/Router.svelte b/packages/client/src/components/Router.svelte index 693ec2dd28..4921e65c87 100644 --- a/packages/client/src/components/Router.svelte +++ b/packages/client/src/components/Router.svelte @@ -1,7 +1,7 @@ {#if routerConfig} -
+
{/if} diff --git a/packages/client/src/components/Screen.svelte b/packages/client/src/components/Screen.svelte index ac17556113..6a65e89afd 100644 --- a/packages/client/src/components/Screen.svelte +++ b/packages/client/src/components/Screen.svelte @@ -11,8 +11,11 @@ // Redirect to home page if no matching route $: screenDefinition == null && routeStore.actions.navigate("/") + + // Make a screen array so we can use keying to properly re-render each screen + $: screens = screenDefinition ? [screenDefinition] : [] -{#if screenDefinition} - -{/if} +{#each screens as screen (screen._id)} + +{/each} diff --git a/packages/client/src/index.js b/packages/client/src/index.js index e925c3e273..2925e950f6 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -1,16 +1,22 @@ import ClientApp from "./components/ClientApp.svelte" +import { builderStore } from "./store" let app const loadBudibase = () => { - // Destroy old app if one exists - if (app) { - app.$destroy() - } - // Create new app - app = new ClientApp({ - target: window.document.body, + // Update builder store with any builder flags + builderStore.set({ + inBuilder: !!window["##BUDIBASE_IN_BUILDER##"], + page: window["##BUDIBASE_PREVIEW_PAGE##"], + screen: window["##BUDIBASE_PREVIEW_SCREEN##"], }) + + // Create app if one hasn't been created yet + if (!app) { + app = new ClientApp({ + target: window.document.body, + }) + } } // Attach to window so the HTML template can call this when it loads diff --git a/packages/client/src/store/builder.js b/packages/client/src/store/builder.js new file mode 100644 index 0000000000..4b197596be --- /dev/null +++ b/packages/client/src/store/builder.js @@ -0,0 +1,12 @@ +import { writable } from "svelte/store" + +const createBuilderStore = () => { + const initialState = { + inBuilder: false, + page: null, + screen: null, + } + return writable(initialState) +} + +export const builderStore = createBuilderStore() diff --git a/packages/client/src/store/index.js b/packages/client/src/store/index.js index a0683ba47e..4416ca809a 100644 --- a/packages/client/src/store/index.js +++ b/packages/client/src/store/index.js @@ -2,3 +2,4 @@ export { authStore } from "./auth" export { routeStore } from "./routes" export { screenStore } from "./screens" export { createDataContextStore } from "./dataContext" +export { builderStore } from "./builder" diff --git a/packages/client/src/store/screens.js b/packages/client/src/store/screens.js index a39a67e3d4..f19736756a 100644 --- a/packages/client/src/store/screens.js +++ b/packages/client/src/store/screens.js @@ -1,5 +1,6 @@ import { writable, derived } from "svelte/store" import { routeStore } from "./routes" +import { builderStore } from "./builder" import * as API from "../api" import { getAppId } from "../utils" @@ -8,36 +9,37 @@ const createScreenStore = () => { screens: [], page: {}, }) - const store = derived([config, routeStore], ([$config, $routeStore]) => { - const { screens, page } = $config - const activeScreen = - screens.length === 1 - ? screens[0] - : screens.find( + const store = derived( + [config, routeStore, builderStore], + ([$config, $routeStore, $builderStore]) => { + let page + let activeScreen + if ($builderStore.inBuilder) { + // Use builder defined definitions if inside the builder preview + page = $builderStore.page + activeScreen = $builderStore.screen + } else { + // Otherwise find the correct screen by matching the current route + page = $config.page + const { screens } = $config + if (screens.length === 1) { + activeScreen = screens[0] + } else { + activeScreen = screens.find( screen => screen.routing.route === $routeStore.activeRoute ) - return { - screens, - page, - activeScreen, + } + } + return { page, activeScreen } } - }) + ) const fetchScreens = async () => { - let screens - let page - const inBuilder = !!window["##BUDIBASE_IN_BUILDER##"] - if (inBuilder) { - // Load screen and page from the window object if in the builder - screens = [window["##BUDIBASE_PREVIEW_SCREEN##"]] - page = window["##BUDIBASE_PREVIEW_PAGE##"] - } else { - // Otherwise load from API - const appDefinition = await API.fetchAppDefinition(getAppId()) - screens = appDefinition.screens - page = appDefinition.page - } - config.set({ screens, page }) + const appDefinition = await API.fetchAppDefinition(getAppId()) + config.set({ + screens: appDefinition.screens, + page: appDefinition.page, + }) } return { diff --git a/packages/client/src/utils/styleable.js b/packages/client/src/utils/styleable.js index 8f1d7ac3e1..fa81308cb2 100644 --- a/packages/client/src/utils/styleable.js +++ b/packages/client/src/utils/styleable.js @@ -1,7 +1,13 @@ +import { getContext } from "svelte" +import { get } from "svelte/store" + +/** + * Helper to build a CSS string from a style object + */ const buildStyleString = styles => { let str = "" Object.entries(styles).forEach(([style, value]) => { - if (style && value) { + if (style && value != null) { str += `${style}: ${value}; ` } }) @@ -12,35 +18,52 @@ const buildStyleString = styles => { * Svelte action to apply correct component styles. */ export const styleable = (node, styles = {}) => { - const normalStyles = styles.normal || {} - const hoverStyles = { - ...normalStyles, - ...styles.hover, + let applyNormalStyles + let applyHoverStyles + + // Creates event listeners and applies initial styles + const setupStyles = newStyles => { + const normalStyles = newStyles.normal || {} + const hoverStyles = { + ...normalStyles, + ...newStyles.hover, + } + + applyNormalStyles = () => { + node.style = buildStyleString(normalStyles) + } + + applyHoverStyles = () => { + node.style = buildStyleString(hoverStyles) + } + + // Add listeners to toggle hover styles + node.addEventListener("mouseover", applyHoverStyles) + node.addEventListener("mouseout", applyNormalStyles) + node.setAttribute("data-bb-id", newStyles.id) + + // Apply initial normal styles + applyNormalStyles() } - function applyNormalStyles() { - node.style = buildStyleString(normalStyles) + // Removes the current event listeners + const removeListeners = () => { + node.removeEventListener("mouseover", applyHoverStyles) + node.removeEventListener("mouseout", applyNormalStyles) } - function applyHoverStyles() { - node.style = buildStyleString(hoverStyles) - } - - // Add listeners to toggle hover styles - node.addEventListener("mouseover", applyHoverStyles) - node.addEventListener("mouseout", applyNormalStyles) - - // Apply normal styles initially - applyNormalStyles() - - // Also apply data tags so we know how to reference each component - node.setAttribute("data-bb-id", styles.id) + // Apply initial styles + setupStyles(styles) return { - // Clean up event listeners when component is destroyed + // Clean up old listeners and apply new ones on update + update: newStyles => { + removeListeners() + setupStyles(newStyles) + }, + // Clean up listeners when component is destroyed destroy: () => { - node.removeEventListener("mouseover", applyHoverStyles) - node.removeEventListener("mouseout", applyNormalStyles) + removeListeners() }, } } diff --git a/packages/standard-components/src/Button.svelte b/packages/standard-components/src/Button.svelte index e23adf6c15..6dbbcfc4cf 100644 --- a/packages/standard-components/src/Button.svelte +++ b/packages/standard-components/src/Button.svelte @@ -9,7 +9,7 @@ export let text - diff --git a/packages/standard-components/src/Card.svelte b/packages/standard-components/src/Card.svelte index cce45c0bf0..e4fcd4a7d6 100644 --- a/packages/standard-components/src/Card.svelte +++ b/packages/standard-components/src/Card.svelte @@ -26,7 +26,7 @@ $: showImage = !!imageUrl -
+
{#if showImage}{/if}

{heading}

diff --git a/packages/standard-components/src/CardHorizontal.svelte b/packages/standard-components/src/CardHorizontal.svelte index e681f08823..ea4fb3734e 100644 --- a/packages/standard-components/src/CardHorizontal.svelte +++ b/packages/standard-components/src/CardHorizontal.svelte @@ -29,7 +29,7 @@ $: showImage = !!imageUrl -
+
{#if showImage}{/if}
diff --git a/packages/standard-components/src/Container.svelte b/packages/standard-components/src/Container.svelte index 2a9f469701..546787dc5d 100644 --- a/packages/standard-components/src/Container.svelte +++ b/packages/standard-components/src/Container.svelte @@ -9,55 +9,55 @@ {#if type === 'div'} -
+
{:else if type === 'header'} -
+
{:else if type === 'main'} -
+
{:else if type === 'footer'} -