Update routify structure to prevent remounting full page when changing URL params and update nav item wrapping

This commit is contained in:
Andrew Kingston 2022-04-25 19:33:43 +01:00
parent f4da31e029
commit a1a4dca420
29 changed files with 166 additions and 115 deletions

View File

@ -39,6 +39,7 @@
class:spectrum-StatusLight--negative={negative}
class:spectrum-StatusLight--disabled={disabled}
class:spectrum-StatusLight--active={active}
class:withText={!!$$slots.default}
>
<slot />
</div>
@ -49,6 +50,10 @@
flex-direction: row;
justify-content: center;
align-items: center;
--spectrum-statuslight-info-text-gap: 4px;
}
.spectrum-StatusLight.withText::before {
margin-right: 12px;
}
.custom::before {
background: var(--color) !important;

View File

@ -9,10 +9,13 @@ export const store = getFrontendStore()
export const automationStore = getAutomationStore()
export const themeStore = getThemeStore()
export const selectedScreen = derived(store, $store => {
return $store.screens.find(screen => screen._id === $store.selectedScreenId)
})
export const currentAsset = derived(store, $store => {
const type = $store.currentFrontEndType
if (type === FrontendTypes.SCREEN) {
return $store.screens.find(screen => screen._id === $store.selectedScreenId)
} else if (type === FrontendTypes.LAYOUT) {
return $store.layouts.find(layout => layout._id === $store.selectedLayoutId)
}
@ -39,21 +42,10 @@ export const selectedComponentPath = derived(
}
)
export const currentAssetId = derived(store, $store => {
return $store.currentFrontEndType === FrontendTypes.SCREEN
? $store.selectedScreenId
: $store.selectedLayoutId
})
export const currentAssetName = derived(currentAsset, $currentAsset => {
return $currentAsset?.name
})
// leave this as before for consistency
export const allScreens = derived(store, $store => {
return $store.screens
})
export const mainLayout = derived(store, $store => {
return $store.layouts?.find(
layout => layout._id === LAYOUT_NAMES.MASTER.PRIVATE

View File

@ -1,7 +1,6 @@
import { get, writable } from "svelte/store"
import { cloneDeep } from "lodash/fp"
import {
allScreens,
currentAsset,
mainLayout,
selectedComponent,
@ -148,7 +147,7 @@ export const getFrontendStore = () => {
screens: {
select: screenId => {
store.update(state => {
let screens = get(allScreens)
let screens = state.screens
let screen =
screens.find(screen => screen._id === screenId) || screens[0]
if (!screen) return state

View File

@ -1,6 +1,6 @@
<script>
import { goto } from "@roxi/routify"
import { allScreens, store } from "builderStore"
import { store } from "builderStore"
import { tables, datasources } from "stores/backend"
import {
ActionMenu,
@ -27,7 +27,7 @@
$: allowDeletion = !external || table?.created
function showDeleteModal() {
templateScreens = $allScreens.filter(
templateScreens = $store.screens.filter(
screen => screen.autoTableId === table._id
)
willBeDeleted = ["All table data"].concat(

View File

@ -45,6 +45,7 @@
class="nav-item"
class:border
class:selected
class:withActions
style={`padding-left: ${14 + indentLevel * 14}px`}
{draggable}
on:dragend
@ -80,7 +81,9 @@
</div>
{/if}
{#if color}
<StatusLight size="L" {color} />
<div class="light">
<StatusLight size="L" {color} />
</div>
{/if}
</div>
</div>
@ -91,7 +94,7 @@
color: var(--grey-7);
transition: background-color
var(--spectrum-global-animation-duration-100, 130ms) ease-in-out;
padding: 0 var(--spacing-m) 0 var(--spacing-l);
padding: 0 var(--spacing-l) 0;
height: 32px;
display: flex;
flex-direction: row;
@ -111,9 +114,8 @@
background-color: var(--grey-3);
}
.nav-item:hover .actions {
visibility: visible;
display: grid;
}
.nav-item-content {
flex: 1 1 auto;
display: flex;
@ -122,6 +124,7 @@
align-items: center;
gap: var(--spacing-xs);
width: max-content;
overflow: hidden;
}
.icon {
@ -157,19 +160,23 @@
}
.actions {
visibility: hidden;
width: 20px;
height: 20px;
cursor: pointer;
position: relative;
display: grid;
margin-left: var(--spacing-s);
display: none;
place-items: center;
}
.actions,
.light :global(.spectrum-StatusLight) {
width: 20px;
height: 20px;
margin-left: var(--spacing-s);
}
.iconText {
margin-top: 1px;
font-size: var(--spectrum-global-dimension-font-size-50);
flex: 0 0 34px;
}
.nav-item.withActions:hover .light {
display: none;
}
</style>

View File

@ -1,7 +1,7 @@
<script>
import { get } from "svelte/store"
import { onMount, onDestroy } from "svelte"
import { store, currentAsset, allScreens } from "builderStore"
import { store, selectedScreen, currentAsset } from "builderStore"
import iframeTemplate from "./iframeTemplate"
import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
import { FrontendTypes } from "constants"
@ -49,7 +49,7 @@
// Extract data to pass to the iframe
$: {
screen = $allScreens.find(x => x._id === $store.selectedScreenId)
screen = $selectedScreen
layout = $store.layouts.find(layout => layout._id === screen?.layoutId)
}
$: selectedComponentId = $store.selectedComponentId ?? ""

View File

@ -1,12 +1,7 @@
<script>
import { onMount, setContext } from "svelte"
import { goto, params } from "@roxi/routify"
import {
store,
allScreens,
selectedAccessRole,
screenSearchString,
} from "builderStore"
import { store, selectedAccessRole, screenSearchString } from "builderStore"
import { roles } from "stores/backend"
import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte"
import Layout from "components/design/NavigationPanel/Layout.svelte"
@ -99,7 +94,7 @@
// Select a valid screen with this new role - otherwise we'll not be
// able to change role at all because ComponentNavigationTree will kick us
// back the current role again because the same screen ID is still selected
const firstValidScreenId = $allScreens.find(
const firstValidScreenId = $store.screens.find(
screen => screen.routing.roleId === role
)?._id
if (firstValidScreenId) {

View File

@ -31,11 +31,12 @@
align-items: stretch;
}
.header {
height: 55px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: var(--spacing-m) var(--spacing-l);
padding: 0 var(--spacing-l);
border-bottom: var(--border-light);
gap: var(--spacing-l);
}

View File

@ -8,7 +8,7 @@
import { currentAsset, store } from "builderStore"
import { FrontendTypes } from "constants"
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
import { allScreens, selectedAccessRole } from "builderStore"
import { store, selectedAccessRole } from "builderStore"
export let componentInstance
export let bindings
@ -17,7 +17,7 @@
const routeTaken = url => {
const roleId = get(selectedAccessRole) || "BASIC"
return get(allScreens).some(
return get(store).screens.some(
screen =>
screen.routing.route.toLowerCase() === url.toLowerCase() &&
screen.routing.roleId === roleId
@ -26,7 +26,7 @@
const roleTaken = roleId => {
const url = get(currentAsset)?.routing.route
return get(allScreens).some(
return get(store).screens.some(
screen =>
screen.routing.route.toLowerCase() === url.toLowerCase() &&
screen.routing.roleId === roleId

View File

@ -0,0 +1,55 @@
<script>
import { Icon, Heading } from "@budibase/bbui"
export let title
export let icon
</script>
<div class="settings-panel">
{#if title}
<div class="header">
{#if icon}
<Icon name={icon} />
{/if}
<div class="title">
<Heading size="XS">{title || ""}</Heading>
</div>
</div>
{/if}
<slot />
</div>
<style>
.settings-panel {
width: 260px;
background: var(--background);
border-left: var(--border-light);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
.header {
height: 55px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
padding: 0 var(--spacing-l);
border-bottom: var(--border-light);
gap: var(--spacing-m);
}
.header :global(*) {
color: var(--spectrum-global-color-gray-700);
}
.title {
flex: 1 1 auto;
width: 0;
}
.title :global(h1) {
overflow: hidden;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@ -2,7 +2,7 @@ import { get } from "svelte/store"
import { isChangingPage } from "@roxi/routify"
export const syncURLToState = options => {
const { keys, params, store, goto, redirect } = options || {}
const { keys, params, store, goto, redirect, baseUrl = "." } = options || {}
if (
!keys?.length ||
!params?.subscribe ||
@ -22,13 +22,15 @@ export const syncURLToState = options => {
let cachedGoto = get(goto)
let cachedRedirect = get(redirect)
let hydrated = false
let debug = false
const log = (...params) => debug && console.log(...params)
// Navigate to a certain URL
const gotoUrl = url => {
if (get(isChangingPage) && hydrated) {
return
}
console.log("Navigating to", url)
log("Navigating to", url)
cachedGoto(url)
}
@ -37,7 +39,7 @@ export const syncURLToState = options => {
if (get(isChangingPage) && hydrated) {
return
}
console.log("Redirecting to", url)
log("Redirecting to", url)
cachedRedirect(url)
}
@ -50,7 +52,7 @@ export const syncURLToState = options => {
const urlValue = params?.[key.url]
const stateValue = state?.[key.state]
if (urlValue && urlValue !== stateValue) {
console.log(
log(
`state.${key.state} (${stateValue}) <= url.${key.url} (${urlValue})`
)
stateUpdates.push(state => {
@ -58,7 +60,7 @@ export const syncURLToState = options => {
})
if (key.validate && key.fallbackUrl) {
if (!key.validate(urlValue)) {
console.log("Invalid URL param!")
log("Invalid URL param!")
redirectUrl(key.fallbackUrl)
hydrated = true
return
@ -77,7 +79,7 @@ export const syncURLToState = options => {
}
// Apply the required state updates
console.log("Performing", stateUpdates.length, "state updates")
log("Performing", stateUpdates.length, "state updates")
store.update(state => {
for (let update of stateUpdates) {
update(state)
@ -89,7 +91,7 @@ export const syncURLToState = options => {
// Updates the URL with new state values
const mapStateToUrl = state => {
// Determine new URL while checking for changes
let url = ".."
let url = baseUrl
let needsUpdate = false
for (let key of keys) {
const urlValue = cachedParams?.[key.url]
@ -97,12 +99,12 @@ export const syncURLToState = options => {
url += `/${stateValue}`
if (stateValue !== urlValue) {
needsUpdate = true
console.log(
log(
`url.${key.url} (${urlValue}) <= state.${key.state} (${stateValue})`
)
if (key.validate && key.fallbackUrl) {
if (!key.validate(stateValue)) {
console.log("Invalid state param!")
log("Invalid state param!")
redirectUrl(key.fallbackUrl)
return
}

View File

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

View File

@ -1,30 +1,8 @@
<script>
import { IconSideNav, IconSideNavItem } from "@budibase/bbui"
import { params, goto, redirect, isActive } from "@roxi/routify"
import { store, allScreens } from "builderStore"
import { syncURLToState } from "helpers/urlStateSync"
import { onDestroy } from "svelte"
// Keep URL and state in sync for selected screen ID
const unsync = syncURLToState({
keys: [
{
url: "screenId",
state: "selectedScreenId",
validate: screenId => $allScreens.some(x => x._id === screenId),
fallbackUrl: "../",
},
],
store,
params,
goto,
redirect,
})
onDestroy(unsync)
import { goto, isActive } from "@roxi/routify"
</script>
<!-- routify:options index=1 -->
<div class="design">
<div class="icon-nav">
<IconSideNav>

View File

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

View File

@ -2,18 +2,26 @@
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 { store, selectedScreen } 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"
import ScreenWizard from "./_components/ScreenWizard.svelte"
import { onDestroy } from "svelte"
import { syncURLToState } from "helpers/urlStateSync"
import { goto, params, redirect } from "@roxi/routify"
import AppPanel from "components/design/AppPanel/AppPanel.svelte"
import SettingsPanel from "components/design/SettingsPanel/SettingsPanel.svelte"
let searchString
let accessRole = "all"
let showNewScreenModal
$: filteredScreens = getFilteredScreens($allScreens, searchString, accessRole)
$: filteredScreens = getFilteredScreens(
$store.screens,
searchString,
accessRole
)
const getFilteredScreens = (screens, search, role) => {
return screens
@ -32,6 +40,24 @@
const getRoleColor = roleId => {
return RoleColours[roleId] || "pink"
}
// Keep URL and state in sync for selected screen ID
const stopSyncing = syncURLToState({
keys: [
{
url: "screenId",
state: "selectedScreenId",
validate: screenId => $store.screens.some(x => x._id === screenId),
fallbackUrl: "../",
},
],
store,
params,
goto,
redirect,
})
onDestroy(stopSyncing)
</script>
<NavigationPanel
@ -64,17 +90,14 @@
>
<ScreenDropdownMenu screenId={screen._id} />
</NavItem>
<!--{#if selectedScreen?._id === screen.id}-->
<!-- <ComponentTree-->
<!-- level={1}-->
<!-- components={selectedScreen.props._children}-->
<!-- currentComponent={$selectedComponent}-->
<!-- {dragDropStore}-->
<!-- />-->
<!--{/if}-->
{/each}
</NavigationPanel>
<ScreenWizard bind:showModal={showNewScreenModal} />
<AppPanel />
<SettingsPanel
title={$selectedScreen?.routing.route}
icon={$selectedScreen.routing.route === "/" ? "Home" : "WebPage"}
/>
<ScreenWizard bind:showModal={showNewScreenModal} />

View File

@ -1,7 +1,7 @@
<script>
import { ModalContent, Input } from "@budibase/bbui"
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
import { selectedAccessRole, allScreens } from "builderStore"
import { store, selectedAccessRole } from "builderStore"
import { get } from "svelte/store"
export let onConfirm
@ -35,7 +35,7 @@
const routeExists = url => {
const roleId = get(selectedAccessRole) || "BASIC"
return get(allScreens).some(
return get(store).screens.some(
screen =>
screen.routing.route.toLowerCase() === url.toLowerCase() &&
screen.routing.roleId === roleId

View File

@ -1,6 +1,6 @@
<script>
import { goto } from "@roxi/routify"
import { store, allScreens } from "builderStore"
import { store } from "builderStore"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import {
ActionMenu,
@ -20,7 +20,7 @@
let confirmDeleteDialog
let screenDetailsModal
$: screen = $allScreens.find(screen => screen._id === screenId)
$: screen = $store.screens.find(screen => screen._id === screenId)
const duplicateScreen = () => {
screenDetailsModal.show()

View File

@ -1,10 +1,5 @@
<script>
import {
store,
currentAsset,
selectedComponent,
allScreens,
} from "builderStore"
import { store, currentAsset, selectedComponent } from "builderStore"
import { Detail, Layout, Button, Icon } from "@budibase/bbui"
import CurrentItemPreview from "components/design/AppPreview"

View File

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