Refactor routes and start updating core design UI into new components

This commit is contained in:
Andrew Kingston 2022-04-22 16:03:47 +01:00
parent 4126e5884d
commit c35906a831
34 changed files with 292 additions and 168 deletions

View File

@ -17,10 +17,13 @@
export let negative = false export let negative = false
export let disabled = false export let disabled = false
export let active = false export let active = false
export let color = null
</script> </script>
<div <div
class="spectrum-StatusLight spectrum-StatusLight--size{size}" class="spectrum-StatusLight spectrum-StatusLight--size{size}"
class:custom={!!color}
style={`--color: ${color};`}
class:spectrum-StatusLight--celery={celery} class:spectrum-StatusLight--celery={celery}
class:spectrum-StatusLight--yellow={yellow} class:spectrum-StatusLight--yellow={yellow}
class:spectrum-StatusLight--fuchsia={fuchsia} class:spectrum-StatusLight--fuchsia={fuchsia}
@ -39,3 +42,13 @@
> >
<slot /> <slot />
</div> </div>
<style>
.spectrum-StatusLight {
display: grid;
place-items: center;
}
.custom::before {
background: var(--color) !important;
}
</style>

View File

@ -15,7 +15,7 @@ import {
tables, tables,
} from "stores/backend" } from "stores/backend"
import { API } from "api" import { API } from "api"
import { DesignTabs, FrontendTypes } from "constants" import { FrontendTypes } from "constants"
import analytics, { Events } from "analytics" import analytics, { Events } from "analytics"
import { import {
findComponentType, findComponentType,
@ -48,8 +48,6 @@ const INITIAL_FRONTEND_STATE = {
continueIfAction: false, continueIfAction: false,
}, },
currentFrontEndType: "none", currentFrontEndType: "none",
selectedDesignTab: DesignTabs.SCREENS,
selectedScreenId: "",
selectedLayoutId: "", selectedLayoutId: "",
selectedComponentId: "", selectedComponentId: "",
errors: [], errors: [],
@ -61,6 +59,9 @@ const INITIAL_FRONTEND_STATE = {
theme: "", theme: "",
customTheme: {}, customTheme: {},
previewDevice: "desktop", previewDevice: "desktop",
// URL params
selectedScreenId: null,
} }
export const getFrontendStore = () => { export const getFrontendStore = () => {

View File

@ -1,5 +1,5 @@
<script> <script>
import { Icon } from "@budibase/bbui" import { Icon, StatusLight } from "@budibase/bbui"
import { createEventDispatcher, getContext } from "svelte" import { createEventDispatcher, getContext } from "svelte"
export let icon export let icon
@ -13,6 +13,8 @@
export let draggable = false export let draggable = false
export let iconText export let iconText
export let iconColor export let iconColor
export let scrollable = false
export let color
const scrollApi = getContext("scroll") const scrollApi = getContext("scroll")
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -43,7 +45,7 @@
class="nav-item" class="nav-item"
class:border class:border
class:selected class:selected
style={`padding-left: ${20 + indentLevel * 14}px`} style={`padding-left: ${14 + indentLevel * 14}px`}
{draggable} {draggable}
on:dragend on:dragend
on:dragstart on:dragstart
@ -52,6 +54,7 @@
on:click={onClick} on:click={onClick}
ondragover="return false" ondragover="return false"
ondragenter="return false" ondragenter="return false"
class:scrollable
> >
<div class="nav-item-content" bind:this={contentRef}> <div class="nav-item-content" bind:this={contentRef}>
{#if withArrow} {#if withArrow}
@ -76,6 +79,9 @@
<slot /> <slot />
</div> </div>
{/if} {/if}
{#if color}
<StatusLight size="L" {color} />
{/if}
</div> </div>
</div> </div>
@ -85,9 +91,14 @@
color: var(--grey-7); color: var(--grey-7);
transition: background-color transition: background-color
var(--spectrum-global-animation-duration-100, 130ms) ease-in-out; var(--spectrum-global-animation-duration-100, 130ms) ease-in-out;
padding: 0 var(--spacing-m) 0 var(--spacing-xl); padding: 0 var(--spacing-m) 0 var(--spacing-l);
height: 32px; height: 32px;
display: flex; display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.nav-item.scrollable {
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
@ -136,10 +147,13 @@
font-weight: 600; font-weight: 600;
font-size: var(--spectrum-global-dimension-font-size-75); font-size: var(--spectrum-global-dimension-font-size-75);
white-space: nowrap; white-space: nowrap;
max-width: 160px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
flex: 1 1 auto;
}
.scrollable .text {
flex: 0 0 auto; flex: 0 0 auto;
max-width: 160px;
} }
.actions { .actions {

View File

@ -0,0 +1,57 @@
<script>
import ComponentSelectionList from "./ComponentSelectionList.svelte"
import DevicePreviewSelect from "./DevicePreviewSelect.svelte"
import ThemeEditor from "./ThemeEditor.svelte"
import AppThemeSelect from "./AppThemeSelect.svelte"
import AppPreview from "./AppPreview.svelte"
import { store } from "builderStore"
</script>
<div class="app-panel">
<div class="header">
<ComponentSelectionList />
{#if $store.clientFeatures.devicePreview}
<DevicePreviewSelect />
{/if}
{#if $store.clientFeatures.customThemes}
<ThemeEditor />
{:else if $store.clientFeatures.spectrumThemes}
<AppThemeSelect />
{/if}
</div>
<div class="content">
{#key $store.version}
<AppPreview />
{/key}
</div>
</div>
<style>
.app-panel {
flex: 1 1 auto;
overflow-y: auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-m);
padding: var(--spacing-l) 24px;
}
.header {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
gap: 1rem;
margin-left: -6px;
}
.header > :global(*) {
flex: 0 0 auto;
}
.header > :global(*:first-child) {
flex: 1 1 auto;
}
.content {
flex: 1 1 auto;
}
</style>

View File

@ -1,7 +1,7 @@
<script> <script>
import { get } from "svelte/store" import { get } from "svelte/store"
import { onMount, onDestroy } from "svelte" import { onMount, onDestroy } from "svelte"
import { store, currentAsset } from "builderStore" import { store, currentAsset, allScreens } from "builderStore"
import iframeTemplate from "./iframeTemplate" import iframeTemplate from "./iframeTemplate"
import { Screen } from "builderStore/store/screenTemplates/utils/Screen" import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
import { FrontendTypes } from "constants" import { FrontendTypes } from "constants"
@ -49,13 +49,8 @@
// Extract data to pass to the iframe // Extract data to pass to the iframe
$: { $: {
if ($store.currentFrontEndType === FrontendTypes.LAYOUT) { screen = $allScreens.find(x => x._id === $store.selectedScreenId)
layout = $currentAsset layout = $store.layouts.find(layout => layout._id === screen?.layoutId)
screen = screenPlaceholder
} else {
screen = $currentAsset
layout = $store.layouts.find(layout => layout._id === screen?.layoutId)
}
} }
$: selectedComponentId = $store.selectedComponentId ?? "" $: selectedComponentId = $store.selectedComponentId ?? ""
$: previewData = { $: previewData = {
@ -68,7 +63,7 @@
customTheme: $store.customTheme, customTheme: $store.customTheme,
previewDevice: $store.previewDevice, previewDevice: $store.previewDevice,
messagePassing: $store.clientFeatures.messagePassing, messagePassing: $store.clientFeatures.messagePassing,
isBudibaseEvent: true isBudibaseEvent: true,
} }
$: json = JSON.stringify(previewData) $: json = JSON.stringify(previewData)

View File

@ -141,9 +141,6 @@
</Modal> </Modal>
<style> <style>
.container {
padding-right: 8px;
}
.setting { .setting {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -1 +0,0 @@
export { default } from "./CurrentItemPreview.svelte"

View File

@ -7,9 +7,7 @@
} from "builderStore" } from "builderStore"
import instantiateStore from "./dragDropStore" import instantiateStore from "./dragDropStore"
import ComponentTree from "./ComponentTree.svelte" import ComponentTree from "./ComponentTree.svelte"
import NavItem from "components/common/NavItem.svelte"
import PathDropdownMenu from "./PathDropdownMenu.svelte" import PathDropdownMenu from "./PathDropdownMenu.svelte"
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
import { get } from "svelte/store" import { get } from "svelte/store"
const ROUTE_NAME_MAP = { const ROUTE_NAME_MAP = {
@ -32,7 +30,6 @@
$: selectedScreen = $currentAsset $: selectedScreen = $currentAsset
$: allScreens = getAllScreens(route) $: allScreens = getAllScreens(route)
$: filteredScreens = getFilteredScreens(allScreens, $screenSearchString)
$: hasSearchMatch = $screenSearchString && filteredScreens.length > 0 $: hasSearchMatch = $screenSearchString && filteredScreens.length > 0
$: noSearchMatch = $screenSearchString && !filteredScreens.length $: noSearchMatch = $screenSearchString && !filteredScreens.length
$: routeSelected = $: routeSelected =
@ -43,22 +40,6 @@
store.actions.screens.select(screenId) store.actions.screens.select(screenId)
} }
const getAllScreens = route => {
let screens = []
Object.entries(route.subpaths).forEach(([route, subpath]) => {
Object.entries(subpath.screens).forEach(([role, id]) => {
screens.push({ id, route, role })
})
})
return screens
}
const getFilteredScreens = (screens, searchString) => {
return screens.filter(
screen => !searchString || screen.route.includes(searchString)
)
}
const toggleManuallyOpened = () => { const toggleManuallyOpened = () => {
if (get(screenSearchString)) { if (get(screenSearchString)) {
return return
@ -79,28 +60,5 @@
<PathDropdownMenu screens={allScreens} {path} /> <PathDropdownMenu screens={allScreens} {path} />
</NavItem> </NavItem>
{#if routeOpened} {#if routeOpened}{/if}
{#each filteredScreens as screen (screen.id)}
<NavItem
icon="WebPage"
indentLevel={indent || 1}
selected={$store.selectedScreenId === screen.id &&
$store.currentView === "detail"}
opened={$store.selectedScreenId === screen.id}
text={ROUTE_NAME_MAP[screen.route]?.[screen.role] || screen.route}
withArrow={route.subpaths}
on:click={() => changeScreen(screen.id)}
>
<ScreenDropdownMenu screenId={screen.id} />
</NavItem>
{#if selectedScreen?._id === screen.id}
<ComponentTree
level={1}
components={selectedScreen.props._children}
currentComponent={$selectedComponent}
{dragDropStore}
/>
{/if}
{/each}
{/if}
{/if} {/if}

View File

@ -131,21 +131,7 @@
<Tabs {selected} on:select={navigate}> <Tabs {selected} on:select={navigate}>
<Tab title="Screens"> <Tab title="Screens">
<div class="tab-content-padding"> <div class="tab-content-padding">
<BBUILayout noPadding gap="XS"> <BBUILayout noPadding gap="XS" />
<Select
on:change={updateAccessRole}
value={$selectedAccessRole}
label="Filter by Access"
getOptionLabel={role => role.name}
getOptionValue={role => role._id}
options={$roles}
/>
<Search
placeholder="Enter a route to search"
label="Search Screens"
bind:value={$screenSearchString}
/>
</BBUILayout>
<div class="nav-items-container" bind:this={scrollRef}> <div class="nav-items-container" bind:this={scrollRef}>
<ComponentNavigationTree /> <ComponentNavigationTree />
</div> </div>

View File

@ -0,0 +1,70 @@
<script>
import { Icon, Heading } from "@budibase/bbui"
export let title
export let showAddButton
export let onClickAddButton
</script>
<div class="navigation-panel">
<div class="header">
<div class="title">
<Heading size="XS">{title || ""}</Heading>
</div>
{#if showAddButton}
<div class="add-button">
<Icon name="Add" on:click={onClickAddButton} />
</div>
{/if}
</div>
<slot />
</div>
<style>
.navigation-panel {
width: 260px;
background: var(--background);
border-right: var(--border-light);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: var(--spacing-m) var(--spacing-l);
border-bottom: var(--border-light);
gap: var(--spacing-l);
}
.title {
flex: 1 1 auto;
width: 0;
}
.title :global(h1) {
overflow: hidden;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.add-button {
flex: 0 0 32px;
height: 32px;
display: grid;
place-items: center;
border-radius: 4px;
position: relative;
cursor: pointer;
background: var(--spectrum-semantic-cta-color-background-default);
transition: background var(--spectrum-global-animation-duration-100, 130ms)
ease-out;
}
.add-button:hover {
background: var(--spectrum-semantic-cta-color-background-hover);
}
.add-button :global(svg) {
fill: white;
}
</style>

View File

@ -1,13 +1,14 @@
import { Roles } from "./backend"
export const TableNames = { export const TableNames = {
USERS: "ta_users", USERS: "ta_users",
} }
export const DesignTabs = { export const RoleColours = {
SCREENS: "screens", [Roles.ADMIN]: "var(--spectrum-global-color-static-seafoam-400)",
COMPONENTS: "components", [Roles.POWER]: "var(--spectrum-global-color-static-purple-400)",
THEME: "theme", [Roles.BASIC]: "var(--spectrum-global-color-static-magenta-400)",
NAVIGATION: "navigation", [Roles.PUBLIC]: "var(--spectrum-global-color-static-yellow-400)",
LAYOUTS: "layouts",
} }
export const FrontendTypes = { export const FrontendTypes = {

View File

@ -55,7 +55,7 @@ export const syncURLToState = options => {
// Updates the URL with new state values // Updates the URL with new state values
const mapStateToUrl = state => { const mapStateToUrl = state => {
// Determine new URL while checking for changes // Determine new URL while checking for changes
let url = "." let url = ".."
let needsUpdate = false let needsUpdate = false
for (let key of keys) { for (let key of keys) {
const urlValue = cachedParams?.[key.url] const urlValue = cachedParams?.[key.url]

View File

@ -1,23 +1,15 @@
<script> <script>
import { IconSideNav, IconSideNavItem } from "@budibase/bbui" import { IconSideNav, IconSideNavItem } from "@budibase/bbui"
import { params, goto } from "@roxi/routify" import { params, goto, isActive } from "@roxi/routify"
import { DesignTabs } from "constants"
import { store } from "builderStore" import { store } from "builderStore"
import { syncURLToState } from "helpers/urlStateSync" import { syncURLToState } from "helpers/urlStateSync"
import { onDestroy } from "svelte" import { onDestroy } from "svelte"
const updateTab = tab => {
store.update(state => {
state.selectedDesignTab = tab
return state
})
}
const unsync = syncURLToState({ const unsync = syncURLToState({
keys: [ keys: [
{ {
url: "tab", url: "screenId",
state: "selectedDesignTab", state: "selectedScreenId",
}, },
], ],
store, store,
@ -30,37 +22,37 @@
<!-- routify:options index=1 --> <!-- routify:options index=1 -->
<div class="design"> <div class="design">
<div class="side-nav"> <div class="icon-nav">
<IconSideNav> <IconSideNav>
<IconSideNavItem <IconSideNavItem
icon="WebPage" icon="WebPage"
tooltip="Screens" tooltip="Screens"
active={$store.selectedDesignTab === DesignTabs.SCREENS} active={$isActive("./screens")}
on:click={() => updateTab(DesignTabs.SCREENS)} on:click={() => $goto("./screens")}
/> />
<IconSideNavItem <IconSideNavItem
icon="ViewList" icon="ViewList"
tooltip="Components" tooltip="Components"
active={$store.selectedDesignTab === DesignTabs.COMPONENTS} active={$isActive("./components")}
on:click={() => updateTab(DesignTabs.COMPONENTS)} on:click={() => $goto("./components")}
/> />
<IconSideNavItem <IconSideNavItem
icon="Brush" icon="Brush"
tooltip="Theme" tooltip="Theme"
active={$store.selectedDesignTab === DesignTabs.THEME} active={$isActive("./theme")}
on:click={() => updateTab(DesignTabs.THEME)} on:click={() => $goto("./theme")}
/> />
<IconSideNavItem <IconSideNavItem
icon="Link" icon="Link"
tooltip="Navigation" tooltip="Navigation"
active={$store.selectedDesignTab === DesignTabs.NAVIGATION} active={$isActive("./navigation")}
on:click={() => updateTab(DesignTabs.NAVIGATION)} on:click={() => $goto("./navigation")}
/> />
<IconSideNavItem <IconSideNavItem
icon="Experience" icon="Experience"
tooltip="Layouts" tooltip="Layouts"
active={$store.selectedDesignTab === DesignTabs.LAYOUTS} active={$isActive("./layouts")}
on:click={() => updateTab(DesignTabs.LAYOUTS)} on:click={() => $goto("./layouts")}
/> />
</IconSideNav> </IconSideNav>
</div> </div>
@ -77,12 +69,14 @@
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
align-items: stretch; align-items: stretch;
} }
.side-nav { .icon-nav {
background: var(--background); background: var(--background);
border-right: var(--border-light); border-right: var(--border-light);
} }
.content { .content {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
justify-content: flex-start;
align-items: stretch;
} }
</style> </style>

View File

@ -0,0 +1,5 @@
<script>
import { redirect } from "@roxi/routify"
$redirect(`./screens`)
</script>

View File

@ -10,7 +10,7 @@
Helpers, Helpers,
notifications, notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
import ScreenDetailsModal from "../ScreenDetailsModal.svelte" import ScreenDetailsModal from "./ScreenDetailsModal.svelte"
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl" import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
import analytics, { Events } from "analytics" import analytics, { Events } from "analytics"
import { makeComponentUnique } from "builderStore/componentUtils" import { makeComponentUnique } from "builderStore/componentUtils"

View File

@ -156,24 +156,7 @@
</div> </div>
<div class="preview-pane"> <div class="preview-pane">
{#if $currentAsset} {#if $currentAsset}{:else}
<div class="preview-header">
<ComponentSelectionList />
{#if $store.clientFeatures.devicePreview}
<DevicePreviewSelect />
{/if}
{#if $store.clientFeatures.customThemes}
<ThemeEditor />
{:else if $store.clientFeatures.spectrumThemes}
<AppThemeSelect />
{/if}
</div>
<div class="preview-content">
{#key $store.version}
<CurrentItemPreview />
{/key}
</div>
{:else}
<div class="centered"> <div class="centered">
<div class="main"> <div class="main">
<Layout gap="S" justifyItems="center"> <Layout gap="S" justifyItems="center">
@ -247,34 +230,6 @@
border-right: var(--border-light); border-right: var(--border-light);
} }
.preview-pane {
grid-column: 2;
overflow-y: auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-m);
padding: var(--spacing-xl) 40px;
}
.preview-header {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
gap: 1rem;
}
.preview-header > :global(*) {
flex: 0 0 auto;
}
.preview-header > :global(*:first-child) {
flex: 1 1 auto;
}
.preview-content {
flex: 1 1 auto;
}
.components-pane { .components-pane {
grid-column: 3; grid-column: 3;
background-color: var(--background); background-color: var(--background);

View File

@ -0,0 +1,69 @@
<script>
import { Search, Layout, Select } from "@budibase/bbui"
import NavigationPanel from "components/design/NavigationPanel/NavigationPanel.svelte"
import { roles } from "stores/backend"
import { store, allScreens } from "builderStore"
import NavItem from "components/common/NavItem.svelte"
import ScreenDropdownMenu from "./_components/ScreenDropdownMenu.svelte"
import AppPanel from "components/design/AppPanel/AppPanel.svelte"
import { RoleColours } from "constants"
let searchString
let accessRole
$: filteredScreens = getFilteredScreens($allScreens, searchString)
const getFilteredScreens = (screens, searchString) => {
return screens
.filter(
screen => !searchString || screen.routing.route.includes(searchString)
)
.slice()
.sort((a, b) => {
return a.routing.route < b.routing.route ? -1 : 1
})
}
const getRoleColor = roleId => {
return RoleColours[roleId] || "pink"
}
</script>
<NavigationPanel title="Screens" showAddButton onClickAddButton>
<Layout paddingX="L" paddingY="XL" gap="S">
<Search
placeholder="Enter a route to search"
value={searchString}
on:change={e => (searchString = e.detail)}
/>
<Select
bind:value={accessRole}
placeholder="All screens"
getOptionLabel={role => role.name}
getOptionValue={role => role._id}
options={$roles}
/>
</Layout>
{#each filteredScreens as screen (screen._id)}
<NavItem
icon={screen.routing.route === "/" ? "Home" : "WebPage"}
indentLevel={0}
selected={$store.selectedScreenId === screen._id}
text={screen.routing.route}
on:click={() => ($store.selectedScreenId = screen._id)}
color={getRoleColor(screen.routing.roleId)}
>
<ScreenDropdownMenu screenId={screen._id} />
</NavItem>
<!--{#if selectedScreen?._id === screen.id}-->
<!-- <ComponentTree-->
<!-- level={1}-->
<!-- components={selectedScreen.props._children}-->
<!-- currentComponent={$selectedComponent}-->
<!-- {dragDropStore}-->
<!-- />-->
<!--{/if}-->
{/each}
</NavigationPanel>
<AppPanel />

View File

@ -1,2 +0,0 @@
<!-- routify:options index=1 -->
<slot />

View File

@ -1,6 +0,0 @@
<script>
import { goto } from "@roxi/routify"
import { FrontendTypes } from "constants"
$goto(`./${FrontendTypes.SCREEN}`)
</script>

View File

@ -1,6 +1,19 @@
<script> <script>
import { goto } from "@roxi/routify" import { allScreens } from "builderStore"
import { DesignTabs } from "constants" import { onMount } from "svelte"
import { redirect } from "@roxi/routify"
$goto(`./${DesignTabs.SCREENS}`) let loaded = false
onMount(() => {
if ($allScreens?.length) {
$redirect(`./${$allScreens[0]._id}`)
} else {
loaded = true
}
})
</script> </script>
{#if loaded}
You need to create a screen!
{/if}

View File

@ -276,7 +276,7 @@
.preview #app-root { .preview #app-root {
border: 1px solid var(--spectrum-global-color-gray-300); border: 1px solid var(--spectrum-global-color-gray-300);
border-radius: 4px; border-radius: 2px;
} }
/* Print styles */ /* Print styles */