Update routify structure to prevent remounting full page when changing URL params and update nav item wrapping
This commit is contained in:
parent
f4da31e029
commit
a1a4dca420
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 ?? ""
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<script>
|
||||
import { redirect } from "@roxi/routify"
|
||||
|
||||
$redirect(`./screens`)
|
||||
</script>
|
|
@ -1 +0,0 @@
|
|||
<slot />
|
|
@ -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>
|
|
@ -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}
|
||||
|
|
|
@ -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} />
|
|
@ -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
|
|
@ -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()
|
|
@ -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"
|
|
@ -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}
|
Loading…
Reference in New Issue