diff --git a/packages/builder/package.json b/packages/builder/package.json index fa64a8acb2..a2b96e4601 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -90,7 +90,7 @@ "@babel/preset-env": "^7.13.12", "@babel/runtime": "^7.13.10", "@rollup/plugin-replace": "^2.4.2", - "@roxi/routify": "2.18.0", + "@roxi/routify": "2.18.5", "@sveltejs/vite-plugin-svelte": "1.0.0-next.19", "@testing-library/jest-dom": "^5.11.10", "@testing-library/svelte": "^3.0.0", diff --git a/packages/builder/src/helpers/urlStateSync.js b/packages/builder/src/helpers/urlStateSync.js index 2e043fbcf5..f6357ae5d9 100644 --- a/packages/builder/src/helpers/urlStateSync.js +++ b/packages/builder/src/helpers/urlStateSync.js @@ -2,13 +2,26 @@ import { get } from "svelte/store" import { isChangingPage } from "@roxi/routify" export const syncURLToState = options => { - const { keys, params, store, goto, redirect, baseUrl = "." } = options || {} + const { + urlParam, + stateKey, + validate, + baseUrl = "..", + fallbackUrl, + store, + routify, + } = options || {} if ( - !keys?.length || - !params?.subscribe || + !urlParam || + !stateKey || + !baseUrl || + !urlParam || !store?.subscribe || - !goto?.subscribe || - !redirect?.subscribe + !routify || + !routify.params?.subscribe || + !routify.goto?.subscribe || + !routify.redirect?.subscribe || + !routify.page?.subscribe ) { console.warn("syncURLToState invoked with missing parameters") return @@ -18,96 +31,70 @@ export const syncURLToState = options => { // to just subscribe and cache the latest versions. // We can grab their initial values as this is during component // initialisation. - let cachedParams = get(params) - let cachedGoto = get(goto) - let cachedRedirect = get(redirect) - let hydrated = false + let cachedParams = get(routify.params) + let cachedGoto = get(routify.goto) + let cachedRedirect = get(routify.redirect) + let cachedPage = get(routify.page) + let previousParamsHash = null let debug = false const log = (...params) => debug && console.log(...params) // Navigate to a certain URL - const gotoUrl = url => { - if (get(isChangingPage) && hydrated) { - return - } - log("Navigating to", url) - cachedGoto(url) + const gotoUrl = (url, params) => { + log("Navigating to", url, "with params", params) + cachedGoto(url, params) } // Redirect to a certain URL const redirectUrl = url => { - if (get(isChangingPage) && hydrated) { - return - } log("Redirecting to", url) cachedRedirect(url) } // Updates state with new URL params const mapUrlToState = params => { - // Determine any required state updates - let stateUpdates = [] - const state = get(store) - for (let key of keys) { - const urlValue = params?.[key.url] - const stateValue = state?.[key.state] - if (urlValue && urlValue !== stateValue) { - log( - `state.${key.state} (${stateValue}) <= url.${key.url} (${urlValue})` - ) - stateUpdates.push(state => { - state[key.state] = urlValue - }) - if (key.validate && key.fallbackUrl) { - if (!key.validate(urlValue)) { - log("Invalid URL param!") - redirectUrl(key.fallbackUrl) - hydrated = true - return - } - } - } - } - - // Mark our initial hydration as completed - hydrated = true - - // Avoid updating the store at all if not necessary to prevent a wasted - // store invalidation - if (!stateUpdates.length) { + // Check if we have new URL params + const paramsHash = JSON.stringify(params) + const newParams = paramsHash !== previousParamsHash + previousParamsHash = paramsHash + const urlValue = params?.[urlParam] + const stateValue = get(store)?.[stateKey] + if (!newParams || !urlValue) { return } - // Apply the required state updates - log("Performing", stateUpdates.length, "state updates") - store.update(state => { - for (let update of stateUpdates) { - update(state) + // Check if new value is valid + if (validate && fallbackUrl) { + if (!validate(urlValue)) { + log("Invalid URL param!") + redirectUrl(fallbackUrl) + return } - return state - }) + } + + // Only update state if we have a new value + if (urlValue !== stateValue) { + log(`state.${stateKey} (${stateValue}) <= url.${urlParam} (${urlValue})`) + store.update(state => { + state[stateKey] = urlValue + return state + }) + } } // Updates the URL with new state values const mapStateToUrl = state => { - // Determine new URL while checking for changes - let url = baseUrl let needsUpdate = false - for (let key of keys) { - const urlValue = cachedParams?.[key.url] - const stateValue = state?.[key.state] - url += `/${stateValue}` - if (stateValue !== urlValue) { - needsUpdate = true - log( - `url.${key.url} (${urlValue}) <= state.${key.state} (${stateValue})` - ) - if (key.validate && key.fallbackUrl) { - if (!key.validate(stateValue)) { - log("Invalid state param!") - redirectUrl(key.fallbackUrl) - return - } + const urlValue = cachedParams?.[urlParam] + const stateValue = state?.[stateKey] + if (stateValue !== urlValue) { + needsUpdate = true + log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`) + if (validate && fallbackUrl) { + if (!validate(stateValue)) { + log("Invalid state param!") + redirectUrl(fallbackUrl) + return } } } @@ -120,7 +107,11 @@ export const syncURLToState = options => { // Navigate to the new URL if (!get(isChangingPage)) { - gotoUrl(url) + const newUrlParams = { + ...cachedParams, + [urlParam]: stateValue, + } + gotoUrl(cachedPage.path, newUrlParams) } } @@ -128,18 +119,21 @@ export const syncURLToState = options => { mapUrlToState(cachedParams) // Subscribe to URL changes and cache them - const unsubscribeParams = params.subscribe($urlParams => { + const unsubscribeParams = routify.params.subscribe($urlParams => { cachedParams = $urlParams mapUrlToState($urlParams) }) // Subscribe to routify store changes and cache them - const unsubscribeGoto = goto.subscribe($goto => { + const unsubscribeGoto = routify.goto.subscribe($goto => { cachedGoto = $goto }) - const unsubscribeRedirect = redirect.subscribe($redirect => { + const unsubscribeRedirect = routify.redirect.subscribe($redirect => { cachedRedirect = $redirect }) + const unsubscribePage = routify.page.subscribe($page => { + cachedPage = $page + }) // Subscribe to store changes and keep URL up to date const unsubscribeStore = store.subscribe(mapStateToUrl) @@ -149,6 +143,7 @@ export const syncURLToState = options => { unsubscribeParams() unsubscribeGoto() unsubscribeRedirect() + unsubscribePage() unsubscribeStore() } } diff --git a/packages/builder/src/pages/builder/app/[application]/_fallback.svelte b/packages/builder/src/pages/builder/app/[application]/_fallback.svelte new file mode 100644 index 0000000000..24c5c56780 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/_fallback.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/AppPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte similarity index 89% rename from packages/builder/src/pages/builder/app/[application]/design/_components/AppPanel.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte index bb5db46e40..f83a5ada0f 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/AppPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte @@ -11,9 +11,6 @@ $: roleColor = getRoleColor(roleId) $: roleName = $roles.find(x => x._id === roleId)?.name || "Unknown" - // Needs to be absolute as we embed this component from multiple different URLs - $: newComponentUrl = `/builder/app/${$store.appId}/design/components/${$selectedScreen?._id}/new` - const getRoleColor = roleId => { return RoleColours[roleId] || "#ffa500" } @@ -42,7 +39,7 @@ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/AppPreview.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/_components/AppPreview.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/DevicePreviewSelect.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/DevicePreviewSelect.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/_components/DevicePreviewSelect.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/DevicePreviewSelect.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/iframeTemplate.js b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/iframeTemplate.js similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/_components/iframeTemplate.js rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/iframeTemplate.js diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_fallback.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_fallback.svelte new file mode 100644 index 0000000000..00165e4ee9 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_fallback.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte similarity index 74% rename from packages/builder/src/pages/builder/app/[application]/design/_layout.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte index 90bdf616c7..79e4fecf52 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_layout.svelte @@ -1,7 +1,23 @@