Starting work on builder, very broken.

This commit is contained in:
mike12345567 2020-11-24 18:11:34 +00:00
parent e26baa6faf
commit e1314b0d88
16 changed files with 76 additions and 204 deletions

View File

@ -1,43 +0,0 @@
export const generate_screen_css = component_arr => {
let styles = ""
for (const { _styles, _id, _children, _component } of component_arr) {
let [componentName] = _component.match(/[a-z]*$/)
Object.keys(_styles).forEach(selector => {
const cssString = generate_css(_styles[selector])
if (cssString) {
styles += apply_class(_id, componentName, cssString, selector)
}
})
if (_children && _children.length) {
styles += generate_screen_css(_children) + "\n"
}
}
return styles.trim()
}
export const generate_css = style => {
let cssString = Object.entries(style).reduce((str, [key, value]) => {
if (typeof value === "string") {
if (value) {
return (str += `${key}: ${value};\n`)
}
} else if (Array.isArray(value)) {
if (value.length > 0 && !value.every(v => v === "")) {
return (str += `${key}: ${value.join(" ")};\n`)
}
}
return str
}, "")
return (cssString || "").trim()
}
export const apply_class = (id, name = "element", styles, selector) => {
if (selector === "normal") {
return `.${name}-${id} {\n${styles}\n}`
} else {
let sel = selector === "selected" ? "::selection" : `:${selector}`
return `.${name}-${id}${sel} {\n${styles}\n}`
}
}

View File

@ -10,19 +10,29 @@ export const backendUiStore = getBackendUiStore()
export const automationStore = getAutomationStore() export const automationStore = getAutomationStore()
export const themeStore = getThemeStore() export const themeStore = getThemeStore()
export const currentAsset = derived(store, $store => {
const layout = $store.layouts ? $store.layouts.find(layout => layout._id === $store.currentAssetId) : null
if (layout) {
return layout
}
const screen = $store.screens ? $store.screens.find(screen => screen._id === $store.currentAssetId) : null
if (screen) {
return screen
}
return null
})
export const currentAssetName = derived(store, () => {
return currentAsset.name
})
// leave this as before for consistency
export const allScreens = derived(store, $store => { export const allScreens = derived(store, $store => {
let screens = [] return $store.screens
if ($store.pages == null) {
return screens
}
for (let page of Object.values($store.pages)) {
screens = screens.concat(page._screens)
}
return screens
}) })
export const currentScreens = derived(store, $store => { export const currentScreens = derived(store, $store => {
const currentScreens = $store.pages[$store.currentPageName]?._screens const currentScreens = $store.layouts[currentAssetName]?._screens
if (currentScreens == null) { if (currentScreens == null) {
return [] return []
} }
@ -31,12 +41,6 @@ export const currentScreens = derived(store, $store => {
: Object.values(currentScreens) : Object.values(currentScreens)
}) })
export const selectedPage = derived(store, $store => {
if (!$store.pages) return null
return $store.pages[$store.currentPageName || "main"]
})
export const initialise = async () => { export const initialise = async () => {
try { try {
await analytics.activate() await analytics.activate()

View File

@ -5,8 +5,7 @@ import {
getBuiltin, getBuiltin,
makePropsSafe, makePropsSafe,
} from "components/userInterface/pagesParsing/createProps" } from "components/userInterface/pagesParsing/createProps"
import { allScreens, backendUiStore, selectedPage } from "builderStore" import { allScreens, backendUiStore, currentAsset } from "builderStore"
import { generate_screen_css } from "../generate_css"
import { fetchComponentLibDefinitions } from "../loadComponentLibraries" import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
import api from "../api" import api from "../api"
import { DEFAULT_PAGES_OBJECT } from "../../constants" import { DEFAULT_PAGES_OBJECT } from "../../constants"
@ -23,14 +22,15 @@ const INITIAL_FRONTEND_STATE = {
apps: [], apps: [],
name: "", name: "",
description: "", description: "",
pages: DEFAULT_PAGES_OBJECT, layouts: DEFAULT_PAGES_OBJECT,
screens: [],
mainUi: {}, mainUi: {},
unauthenticatedUi: {}, unauthenticatedUi: {},
components: [], components: [],
currentPreviewItem: null, currentPreviewItem: null,
currentComponentInfo: null, currentComponentInfo: null,
currentFrontEndType: "none", currentFrontEndType: "none",
currentPageName: "", currentAssetId: "",
currentComponentProps: null, currentComponentProps: null,
errors: [], errors: [],
hasAppPackage: false, hasAppPackage: false,
@ -43,52 +43,12 @@ export const getFrontendStore = () => {
const store = writable({ ...INITIAL_FRONTEND_STATE }) const store = writable({ ...INITIAL_FRONTEND_STATE })
store.actions = { store.actions = {
// TODO: REFACTOR
initialise: async pkg => { initialise: async pkg => {
const layouts = pkg.layouts, screens = pkg.screens, application = pkg.application
store.update(state => { store.update(state => {
state.appId = pkg.application._id state.appId = application._id
return state return state
}) })
const screens = await api.get("/api/screens").then(r => r.json())
const mainScreens = screens.filter(screen =>
screen._id.includes(pkg.pages.main._id)
),
unauthScreens = screens.filter(screen =>
screen._id.includes(pkg.pages.unauthenticated._id)
)
pkg.pages = {
main: {
...pkg.pages.main,
_screens: mainScreens,
},
unauthenticated: {
...pkg.pages.unauthenticated,
_screens: unauthScreens,
},
}
// if the app has just been created
// we need to build the CSS and save
if (pkg.justCreated) {
for (let pageName of ["main", "unauthenticated"]) {
const page = pkg.pages[pageName]
store.actions.screens.regenerateCss(page)
for (let screen of page._screens) {
store.actions.screens.regenerateCss(screen)
}
await api.post(`/api/pages/${page._id}`, {
page: {
componentLibraries: pkg.application.componentLibraries,
...page,
},
screens: page._screens,
})
}
}
pkg.justCreated = false
const components = await fetchComponentLibDefinitions(pkg.application._id) const components = await fetchComponentLibDefinitions(pkg.application._id)
@ -99,7 +59,8 @@ export const getFrontendStore = () => {
name: pkg.application.name, name: pkg.application.name,
description: pkg.application.description, description: pkg.application.description,
appId: pkg.application._id, appId: pkg.application._id,
pages: pkg.pages, layouts,
screens,
hasAppPackage: true, hasAppPackage: true,
builtins: [getBuiltin("##builtin/screenslot")], builtins: [getBuiltin("##builtin/screenslot")],
appInstance: pkg.application.instance, appInstance: pkg.application.instance,
@ -111,7 +72,7 @@ export const getFrontendStore = () => {
store.update(state => { store.update(state => {
state.currentFrontEndType = type state.currentFrontEndType = type
const page = get(selectedPage) const page = get(currentAsset)
const pageOrScreen = type === "page" ? page : page._screens[0] const pageOrScreen = type === "page" ? page : page._screens[0]
@ -168,7 +129,7 @@ export const getFrontendStore = () => {
await savePromise await savePromise
}, },
save: async screen => { save: async screen => {
const page = get(selectedPage) const page = get(currentAsset)
const currentPageScreens = page._screens const currentPageScreens = page._screens
const creatingNewScreen = screen._id === undefined const creatingNewScreen = screen._id === undefined
@ -197,14 +158,15 @@ export const getFrontendStore = () => {
state.currentComponentInfo = safeProps state.currentComponentInfo = safeProps
screen.props = safeProps screen.props = safeProps
} }
savePromise = store.actions.pages.save() savePromise = store.actions.layouts.save()
return state return state
}) })
if (savePromise) await savePromise if (savePromise) await savePromise
}, },
regenerateCss: screen => { regenerateCss: async asset => {
screen._css = generate_screen_css([screen.props]) const response = await api.post("/api/css/generate", asset)
asset._css = await response.json()
}, },
regenerateCssForCurrentScreen: () => { regenerateCssForCurrentScreen: () => {
const { currentPreviewItem } = get(store) const { currentPreviewItem } = get(store)
@ -218,7 +180,7 @@ export const getFrontendStore = () => {
const screensToDelete = Array.isArray(screens) ? screens : [screens] const screensToDelete = Array.isArray(screens) ? screens : [screens]
store.update(state => { store.update(state => {
const currentPage = get(selectedPage) const currentPage = get(currentAsset)
for (let screenToDelete of screensToDelete) { for (let screenToDelete of screensToDelete) {
// Remove screen from current page as well // Remove screen from current page as well
@ -242,17 +204,17 @@ export const getFrontendStore = () => {
if (state.currentFrontEndType !== "page") { if (state.currentFrontEndType !== "page") {
await store.actions.screens.save(state.currentPreviewItem) await store.actions.screens.save(state.currentPreviewItem)
} }
await store.actions.pages.save() await store.actions.layouts.save()
}, },
}, },
pages: { layouts: {
select: pageName => { select: pageName => {
store.update(state => { store.update(state => {
const currentPage = state.pages[pageName] const currentPage = state.layouts[pageName]
state.currentFrontEndType = "page" state.currentFrontEndType = "page"
state.currentView = "detail" state.currentView = "detail"
state.currentPageName = pageName state.currentAssetId = pageName
// This is the root of many problems. // This is the root of many problems.
// Uncaught (in promise) TypeError: Cannot read property '_component' of undefined // Uncaught (in promise) TypeError: Cannot read property '_component' of undefined
@ -264,11 +226,11 @@ export const getFrontendStore = () => {
) )
state.currentComponentInfo = safeProps state.currentComponentInfo = safeProps
currentPage.props = safeProps currentPage.props = safeProps
state.currentPreviewItem = state.pages[pageName] state.currentPreviewItem = state.layouts[pageName]
store.actions.screens.regenerateCssForCurrentScreen() store.actions.screens.regenerateCssForCurrentScreen()
for (let screen of get(allScreens)) { for (let screen of get(allScreens)) {
screen._css = generate_screen_css([screen.props]) screen._css = store.actions.screens.regenerateCss(screen)
} }
return state return state
@ -276,7 +238,7 @@ export const getFrontendStore = () => {
}, },
save: async page => { save: async page => {
const storeContents = get(store) const storeContents = get(store)
const pageName = storeContents.currentPageName || "main" const pageName = storeContents.currentAssetId || "main"
const pageToSave = page || storeContents.pages[pageName] const pageToSave = page || storeContents.pages[pageName]
// TODO: revisit. This sends down a very weird payload // TODO: revisit. This sends down a very weird payload
@ -293,7 +255,7 @@ export const getFrontendStore = () => {
if (!json.ok) throw new Error("Error updating page") if (!json.ok) throw new Error("Error updating page")
store.update(state => { store.update(state => {
state.pages[pageName]._rev = json.rev state.layouts[pageName]._rev = json.rev
return state return state
}) })
}, },
@ -324,7 +286,7 @@ export const getFrontendStore = () => {
if ( if (
componentToAdd.startsWith("##") && componentToAdd.startsWith("##") &&
findSlot(state.pages[state.currentPageName].props._children) findSlot(state.layouts[state.currentAssetId].props._children)
) { ) {
return state return state
} }
@ -480,7 +442,7 @@ export const getFrontendStore = () => {
store.update(state => { store.update(state => {
// Try to extract a nav component from the master screen // Try to extract a nav component from the master screen
const nav = findChildComponentType( const nav = findChildComponentType(
state.pages.main, state.layouts.main,
"@budibase/standard-components/Navigation" "@budibase/standard-components/Navigation"
) )
if (nav) { if (nav) {
@ -515,12 +477,12 @@ export const getFrontendStore = () => {
// Save page and regenerate all CSS because otherwise weird things happen // Save page and regenerate all CSS because otherwise weird things happen
nav._children = [...nav._children, newLink] nav._children = [...nav._children, newLink]
state.currentPageName = "main" state.currentAssetId = "main"
store.actions.screens.regenerateCss(state.pages.main) store.actions.screens.regenerateCss(state.layouts.main)
for (let screen of state.pages.main._screens) { for (let screen of state.layouts.main._screens) {
store.actions.screens.regenerateCss(screen) store.actions.screens.regenerateCss(screen)
} }
savePromise = store.actions.pages.save() savePromise = store.actions.layouts.save()
} }
return state return state
}) })

View File

@ -51,7 +51,7 @@
const screens = screenTemplates($store, [table]) const screens = screenTemplates($store, [table])
.filter(template => defaultScreens.includes(template.id)) .filter(template => defaultScreens.includes(template.id))
.map(template => template.create()) .map(template => template.create())
store.actions.pages.select("main") store.actions.layouts.select("main")
for (let screen of screens) { for (let screen of screens) {
// Record the table that created this screen so we can link it later // Record the table that created this screen so we can link it later
screen.autoTableId = table._id screen.autoTableId = table._id

View File

@ -155,9 +155,8 @@
const pkg = await applicationPkg.json() const pkg = await applicationPkg.json()
if (applicationPkg.ok) { if (applicationPkg.ok) {
backendUiStore.actions.reset() backendUiStore.actions.reset()
pkg.justCreated = true
await store.actions.initialise(pkg) await store.actions.initialise(pkg)
automationStore.actions.fetch() await automationStore.actions.fetch()
} else { } else {
throw new Error(pkg) throw new Error(pkg)
} }

View File

@ -56,7 +56,7 @@
screenPlaceholder.props._id = "screenslot-placeholder" screenPlaceholder.props._id = "screenslot-placeholder"
// Extract data to pass to the iframe // Extract data to pass to the iframe
$: page = $store.pages[$store.currentPageName] $: page = $store.layouts[$store.currentPageName]
$: screen = $: screen =
$store.currentFrontEndType === "page" $store.currentFrontEndType === "page"
? screenPlaceholder ? screenPlaceholder

View File

@ -19,7 +19,7 @@
// update the page if required // update the page if required
store.update(state => { store.update(state => {
if (state.currentPreviewItem._id === screen) { if (state.currentPreviewItem._id === screen) {
store.actions.pages.select($store.currentPageName) store.actions.layouts.select($store.currentPageName)
notifier.success(`Screen ${screenToDelete.name} deleted successfully.`) notifier.success(`Screen ${screenToDelete.name} deleted successfully.`)
$goto(`./:page/page-layout`) $goto(`./:page/page-layout`)
} }

View File

@ -1,6 +1,6 @@
<script> <script>
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store } from "builderStore" import { store, currentAssetName } from "builderStore"
import components from "./temporaryPanelStructure.js" import components from "./temporaryPanelStructure.js"
import { DropdownMenu } from "@budibase/bbui" import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
@ -52,7 +52,7 @@
align="left"> align="left">
<DropdownContainer> <DropdownContainer>
{#each categories[selectedIndex].children as item} {#each categories[selectedIndex].children as item}
{#if !item.showOnPages || item.showOnPages.includes($store.currentPageName)} {#if !item.showOnAsset || item.showOnAsset.includes($currentAssetName)}
<DropdownItem <DropdownItem
icon={item.icon} icon={item.icon}
title={item.name} title={item.name}

View File

@ -4,7 +4,7 @@
import api from "builderStore/api" import api from "builderStore/api"
import ComponentNavigationTree from "components/userInterface/ComponentNavigationTree/index.svelte" import ComponentNavigationTree from "components/userInterface/ComponentNavigationTree/index.svelte"
import PageLayout from "components/userInterface/PageLayout.svelte" import PageLayout from "components/userInterface/PageLayout.svelte"
import PagesList from "components/userInterface/PagesList.svelte" import PagesList from "components/userInterface/LayoutsList.svelte"
import NewScreenModal from "components/userInterface/NewScreenModal.svelte" import NewScreenModal from "components/userInterface/NewScreenModal.svelte"
import { Modal } from "@budibase/bbui" import { Modal } from "@budibase/bbui"
@ -23,7 +23,7 @@
</div> </div>
<PagesList /> <PagesList />
<div class="nav-items-container"> <div class="nav-items-container">
<PageLayout layout={$store.pages[$store.currentPageName]} /> <PageLayout layout={$store.layouts[$store.currentPageName]} />
<ComponentNavigationTree /> <ComponentNavigationTree />
</div> </div>
<Modal bind:this={modal}> <Modal bind:this={modal}>

View File

@ -7,7 +7,7 @@
return { name, props } return { name, props }
} }
const pages = [ const layouts = [
{ {
title: "Private", title: "Private",
id: "main", id: "main",
@ -18,18 +18,18 @@
}, },
] ]
if (!$store.currentPageName) if (!$store.currentAssetId)
store.actions.pages.select($params.page ? $params.page : "main") store.actions.layouts.select($params.layout ? $params.layout : "main")
const changePage = id => { const changeLayout = id => {
store.actions.pages.select(id) store.actions.layouts.select(id)
$goto(`./${id}/page-layout`) $goto(`./${id}/page-layout`)
} }
</script> </script>
<div class="root"> <div class="root">
{#each pages as { title, id }} {#each layouts as { title, id }}
<button class:active={id === $params.page} on:click={() => changePage(id)}> <button class:active={id === $params.layout} on:click={() => changeLayout(id)}>
{title} {title}
</button> </button>
{/each} {/each}

View File

@ -4,7 +4,7 @@
import Input from "./PropertyPanelControls/Input.svelte" import Input from "./PropertyPanelControls/Input.svelte"
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { excludeProps } from "./propertyCategories.js" import { excludeProps } from "./propertyCategories.js"
import { store, allScreens } from "builderStore" import { store, allScreens, currentAsset } from "builderStore"
import { walkProps } from "builderStore/storeUtils" import { walkProps } from "builderStore/storeUtils"
export let panelDefinition = [] export let panelDefinition = []
@ -58,15 +58,15 @@
} }
}) })
} }
// check page first // check against layouts
lookForDuplicate($store.pages[$store.currentPageName].props) for (let layout of $store.layouts) {
if (duplicate) return true lookForDuplicate(layout.props)
}
// if viewing screen, check current screen for duplicate // if viewing screen, check current screen for duplicate
if ($store.currentFrontEndType === "screen") { if ($store.currentFrontEndType === "screen") {
lookForDuplicate($store.currentPreviewItem.props) lookForDuplicate($store.currentPreviewItem.props)
} else { } else {
// viewing master page - need to dedupe against all screens // need to dedupe against all screens
for (let screen of $allScreens) { for (let screen of $allScreens) {
lookForDuplicate(screen.props) lookForDuplicate(screen.props)
} }

View File

@ -1185,6 +1185,7 @@ export default {
settings: [{ label: "Logo URL", key: "logoUrl", control: Input }], settings: [{ label: "Logo URL", key: "logoUrl", control: Input }],
}, },
}, },
// TODO: need to deal with this
{ {
name: "Login", name: "Login",
_component: "@budibase/standard-components/login", _component: "@budibase/standard-components/login",
@ -1192,7 +1193,7 @@ export default {
"A component that automatically generates a login screen for your app.", "A component that automatically generates a login screen for your app.",
icon: "ri-login-box-line", icon: "ri-login-box-line",
children: [], children: [],
showOnPages: ["unauthenticated"], showOnAsset: ["login-screen"],
properties: { properties: {
design: { ...all }, design: { ...all },
settings: [ settings: [

View File

@ -1,4 +1,4 @@
<script> <script>
import { params } from "@sveltech/routify" import { params } from "@sveltech/routify"
store.actions.pages.select($params.page) store.actions.layouts.select($params.page)
</script> </script>

View File

@ -14,7 +14,7 @@
if (!validScreen) { if (!validScreen) {
// Go to main layout if URL set to invalid screen // Go to main layout if URL set to invalid screen
store.actions.pages.select("main") store.actions.layouts.select("main")
$goto("../../main") $goto("../../main")
} else { } else {
// Otherwise proceed to set screen // Otherwise proceed to set screen
@ -23,7 +23,7 @@
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it. // There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
if ($leftover) { if ($leftover) {
// Get the correct screen children. // Get the correct screen children.
const screenChildren = $store.pages[$params.page]._screens.find( const screenChildren = $store.layouts[$params.page]._screens.find(
screen => screen =>
screen._id === $params.screen || screen._id === $params.screen ||
screen._id === decodeURIComponent($params.screen) screen._id === decodeURIComponent($params.screen)
@ -37,7 +37,7 @@
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it. // There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
if ($leftover) { if ($leftover) {
findComponent(componentIds, $store.pages[$params.page].props._children) findComponent(componentIds, $store.layouts[$params.page].props._children)
} }
} }

View File

@ -2,7 +2,7 @@
import { params } from "@sveltech/routify" import { params } from "@sveltech/routify"
import { store } from "builderStore" import { store } from "builderStore"
store.actions.pages.select($params.page) store.actions.layouts.select($params.page)
</script> </script>
<slot /> <slot />

View File

@ -1,51 +0,0 @@
import {
generate_css,
generate_screen_css,
} from "../src/builderStore/generate_css.js"
describe("generate_css", () => {
test("Check how array styles are output", () => {
expect(generate_css({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0px 10px 0px 15px;")
})
test("Check handling of an array with empty string values", () => {
expect(generate_css({ padding: ["", "", "", ""] })).toBe("")
})
test("Check handling of an empty array", () => {
expect(generate_css({ margin: [] })).toBe("")
})
test("Check handling of valid font property", () => {
expect(generate_css({ "font-size": "10px" })).toBe("font-size: 10px;")
})
})
describe("generate_screen_css", () => {
const normalComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: { "font-size": "16px" }, hover: {}, active: {}, selected: {} } }
test("Test generation of normal css styles", () => {
expect(generate_screen_css([normalComponent])).toBe(".header-123-456 {\nfont-size: 16px;\n}")
})
const hoverComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {"font-size": "16px"}, active: {}, selected: {} } }
test("Test generation of hover css styles", () => {
expect(generate_screen_css([hoverComponent])).toBe(".header-123-456:hover {\nfont-size: 16px;\n}")
})
const selectedComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: { "font-size": "16px" } } }
test("Test generation of selection css styles", () => {
expect(generate_screen_css([selectedComponent])).toBe(".header-123-456::selection {\nfont-size: 16px;\n}")
})
const emptyComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: {} } }
test.only("Testing handling of empty component styles", () => {
expect(generate_screen_css([emptyComponent])).toBe("")
})
})