Improve urlStateSync utility, improve routing structure, improve component tree
This commit is contained in:
parent
1d9b053efc
commit
f50ba7ab4f
|
@ -90,7 +90,7 @@
|
||||||
"@babel/preset-env": "^7.13.12",
|
"@babel/preset-env": "^7.13.12",
|
||||||
"@babel/runtime": "^7.13.10",
|
"@babel/runtime": "^7.13.10",
|
||||||
"@rollup/plugin-replace": "^2.4.2",
|
"@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",
|
"@sveltejs/vite-plugin-svelte": "1.0.0-next.19",
|
||||||
"@testing-library/jest-dom": "^5.11.10",
|
"@testing-library/jest-dom": "^5.11.10",
|
||||||
"@testing-library/svelte": "^3.0.0",
|
"@testing-library/svelte": "^3.0.0",
|
||||||
|
|
|
@ -2,13 +2,26 @@ import { get } from "svelte/store"
|
||||||
import { isChangingPage } from "@roxi/routify"
|
import { isChangingPage } from "@roxi/routify"
|
||||||
|
|
||||||
export const syncURLToState = options => {
|
export const syncURLToState = options => {
|
||||||
const { keys, params, store, goto, redirect, baseUrl = "." } = options || {}
|
const {
|
||||||
|
urlParam,
|
||||||
|
stateKey,
|
||||||
|
validate,
|
||||||
|
baseUrl = "..",
|
||||||
|
fallbackUrl,
|
||||||
|
store,
|
||||||
|
routify,
|
||||||
|
} = options || {}
|
||||||
if (
|
if (
|
||||||
!keys?.length ||
|
!urlParam ||
|
||||||
!params?.subscribe ||
|
!stateKey ||
|
||||||
|
!baseUrl ||
|
||||||
|
!urlParam ||
|
||||||
!store?.subscribe ||
|
!store?.subscribe ||
|
||||||
!goto?.subscribe ||
|
!routify ||
|
||||||
!redirect?.subscribe
|
!routify.params?.subscribe ||
|
||||||
|
!routify.goto?.subscribe ||
|
||||||
|
!routify.redirect?.subscribe ||
|
||||||
|
!routify.page?.subscribe
|
||||||
) {
|
) {
|
||||||
console.warn("syncURLToState invoked with missing parameters")
|
console.warn("syncURLToState invoked with missing parameters")
|
||||||
return
|
return
|
||||||
|
@ -18,96 +31,70 @@ export const syncURLToState = options => {
|
||||||
// to just subscribe and cache the latest versions.
|
// to just subscribe and cache the latest versions.
|
||||||
// We can grab their initial values as this is during component
|
// We can grab their initial values as this is during component
|
||||||
// initialisation.
|
// initialisation.
|
||||||
let cachedParams = get(params)
|
let cachedParams = get(routify.params)
|
||||||
let cachedGoto = get(goto)
|
let cachedGoto = get(routify.goto)
|
||||||
let cachedRedirect = get(redirect)
|
let cachedRedirect = get(routify.redirect)
|
||||||
let hydrated = false
|
let cachedPage = get(routify.page)
|
||||||
|
let previousParamsHash = null
|
||||||
let debug = false
|
let debug = false
|
||||||
const log = (...params) => debug && console.log(...params)
|
const log = (...params) => debug && console.log(...params)
|
||||||
|
|
||||||
// Navigate to a certain URL
|
// Navigate to a certain URL
|
||||||
const gotoUrl = url => {
|
const gotoUrl = (url, params) => {
|
||||||
if (get(isChangingPage) && hydrated) {
|
log("Navigating to", url, "with params", params)
|
||||||
return
|
cachedGoto(url, params)
|
||||||
}
|
|
||||||
log("Navigating to", url)
|
|
||||||
cachedGoto(url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to a certain URL
|
// Redirect to a certain URL
|
||||||
const redirectUrl = url => {
|
const redirectUrl = url => {
|
||||||
if (get(isChangingPage) && hydrated) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log("Redirecting to", url)
|
log("Redirecting to", url)
|
||||||
cachedRedirect(url)
|
cachedRedirect(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates state with new URL params
|
// Updates state with new URL params
|
||||||
const mapUrlToState = params => {
|
const mapUrlToState = params => {
|
||||||
// Determine any required state updates
|
// Check if we have new URL params
|
||||||
let stateUpdates = []
|
const paramsHash = JSON.stringify(params)
|
||||||
const state = get(store)
|
const newParams = paramsHash !== previousParamsHash
|
||||||
for (let key of keys) {
|
previousParamsHash = paramsHash
|
||||||
const urlValue = params?.[key.url]
|
const urlValue = params?.[urlParam]
|
||||||
const stateValue = state?.[key.state]
|
const stateValue = get(store)?.[stateKey]
|
||||||
if (urlValue && urlValue !== stateValue) {
|
if (!newParams || !urlValue) {
|
||||||
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) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the required state updates
|
// Check if new value is valid
|
||||||
log("Performing", stateUpdates.length, "state updates")
|
if (validate && fallbackUrl) {
|
||||||
store.update(state => {
|
if (!validate(urlValue)) {
|
||||||
for (let update of stateUpdates) {
|
log("Invalid URL param!")
|
||||||
update(state)
|
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
|
// Updates the URL with new state values
|
||||||
const mapStateToUrl = state => {
|
const mapStateToUrl = state => {
|
||||||
// Determine new URL while checking for changes
|
|
||||||
let url = baseUrl
|
|
||||||
let needsUpdate = false
|
let needsUpdate = false
|
||||||
for (let key of keys) {
|
const urlValue = cachedParams?.[urlParam]
|
||||||
const urlValue = cachedParams?.[key.url]
|
const stateValue = state?.[stateKey]
|
||||||
const stateValue = state?.[key.state]
|
if (stateValue !== urlValue) {
|
||||||
url += `/${stateValue}`
|
needsUpdate = true
|
||||||
if (stateValue !== urlValue) {
|
log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`)
|
||||||
needsUpdate = true
|
if (validate && fallbackUrl) {
|
||||||
log(
|
if (!validate(stateValue)) {
|
||||||
`url.${key.url} (${urlValue}) <= state.${key.state} (${stateValue})`
|
log("Invalid state param!")
|
||||||
)
|
redirectUrl(fallbackUrl)
|
||||||
if (key.validate && key.fallbackUrl) {
|
return
|
||||||
if (!key.validate(stateValue)) {
|
|
||||||
log("Invalid state param!")
|
|
||||||
redirectUrl(key.fallbackUrl)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +107,11 @@ export const syncURLToState = options => {
|
||||||
|
|
||||||
// Navigate to the new URL
|
// Navigate to the new URL
|
||||||
if (!get(isChangingPage)) {
|
if (!get(isChangingPage)) {
|
||||||
gotoUrl(url)
|
const newUrlParams = {
|
||||||
|
...cachedParams,
|
||||||
|
[urlParam]: stateValue,
|
||||||
|
}
|
||||||
|
gotoUrl(cachedPage.path, newUrlParams)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,18 +119,21 @@ export const syncURLToState = options => {
|
||||||
mapUrlToState(cachedParams)
|
mapUrlToState(cachedParams)
|
||||||
|
|
||||||
// Subscribe to URL changes and cache them
|
// Subscribe to URL changes and cache them
|
||||||
const unsubscribeParams = params.subscribe($urlParams => {
|
const unsubscribeParams = routify.params.subscribe($urlParams => {
|
||||||
cachedParams = $urlParams
|
cachedParams = $urlParams
|
||||||
mapUrlToState($urlParams)
|
mapUrlToState($urlParams)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Subscribe to routify store changes and cache them
|
// Subscribe to routify store changes and cache them
|
||||||
const unsubscribeGoto = goto.subscribe($goto => {
|
const unsubscribeGoto = routify.goto.subscribe($goto => {
|
||||||
cachedGoto = $goto
|
cachedGoto = $goto
|
||||||
})
|
})
|
||||||
const unsubscribeRedirect = redirect.subscribe($redirect => {
|
const unsubscribeRedirect = routify.redirect.subscribe($redirect => {
|
||||||
cachedRedirect = $redirect
|
cachedRedirect = $redirect
|
||||||
})
|
})
|
||||||
|
const unsubscribePage = routify.page.subscribe($page => {
|
||||||
|
cachedPage = $page
|
||||||
|
})
|
||||||
|
|
||||||
// Subscribe to store changes and keep URL up to date
|
// Subscribe to store changes and keep URL up to date
|
||||||
const unsubscribeStore = store.subscribe(mapStateToUrl)
|
const unsubscribeStore = store.subscribe(mapStateToUrl)
|
||||||
|
@ -149,6 +143,7 @@ export const syncURLToState = options => {
|
||||||
unsubscribeParams()
|
unsubscribeParams()
|
||||||
unsubscribeGoto()
|
unsubscribeGoto()
|
||||||
unsubscribeRedirect()
|
unsubscribeRedirect()
|
||||||
|
unsubscribePage()
|
||||||
unsubscribeStore()
|
unsubscribeStore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
|
$goto("../design")
|
||||||
|
</script>
|
|
@ -11,9 +11,6 @@
|
||||||
$: roleColor = getRoleColor(roleId)
|
$: roleColor = getRoleColor(roleId)
|
||||||
$: roleName = $roles.find(x => x._id === roleId)?.name || "Unknown"
|
$: 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 => {
|
const getRoleColor = roleId => {
|
||||||
return RoleColours[roleId] || "#ffa500"
|
return RoleColours[roleId] || "#ffa500"
|
||||||
}
|
}
|
||||||
|
@ -42,7 +39,7 @@
|
||||||
<Button
|
<Button
|
||||||
cta
|
cta
|
||||||
icon="Add"
|
icon="Add"
|
||||||
on:click={() => $goto(`./components/${$selectedScreen._id}/new`)}
|
on:click={() => $goto(`../${$selectedScreen._id}/components/new`)}
|
||||||
>
|
>
|
||||||
Component
|
Component
|
||||||
</Button>
|
</Button>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
|
$redirect("../screens")
|
||||||
|
</script>
|
|
@ -1,7 +1,23 @@
|
||||||
<script>
|
<script>
|
||||||
import { IconSideNav, IconSideNavItem } from "@budibase/bbui"
|
import { IconSideNav, IconSideNavItem } from "@budibase/bbui"
|
||||||
import { goto, isActive } from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import AppPanel from "./_components/AppPanel.svelte"
|
import AppPanel from "./_components/AppPanel.svelte"
|
||||||
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
|
import { store, selectedScreen } from "builderStore"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
const { isActive, goto } = routify
|
||||||
|
|
||||||
|
// Keep URL and state in sync for selected screen ID
|
||||||
|
const stopSyncing = syncURLToState({
|
||||||
|
urlParam: "screenId",
|
||||||
|
stateKey: "selectedScreenId",
|
||||||
|
validate: id => $store.screens.some(screen => screen._id === id),
|
||||||
|
fallbackUrl: "../../",
|
||||||
|
store,
|
||||||
|
routify,
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(stopSyncing)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="design">
|
<div class="design">
|
||||||
|
@ -41,8 +57,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<slot />
|
{#if $selectedScreen}
|
||||||
<AppPanel />
|
<slot />
|
||||||
|
<AppPanel />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
let newOffsets = {}
|
let newOffsets = {}
|
||||||
|
|
||||||
// Calculate left offset
|
// Calculate left offset
|
||||||
const offsetX = bounds.left + bounds.width + scrollLeft - 40
|
const offsetX = bounds.left + bounds.width + scrollLeft - 58
|
||||||
if (offsetX > sidebarWidth) {
|
if (offsetX > sidebarWidth) {
|
||||||
newOffsets.left = offsetX - sidebarWidth
|
newOffsets.left = offsetX - sidebarWidth
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,8 +91,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.nav-items-container {
|
.nav-items-container {
|
||||||
margin: 0 calc(-1 * var(--spacing-l));
|
padding: var(--spacing-xl) 0;
|
||||||
padding: var(--spacing-xl) var(--spacing-l);
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 0;
|
height: 0;
|
|
@ -1,27 +1,18 @@
|
||||||
<script>
|
<script>
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import { store, selectedScreen } from "builderStore"
|
import { store, selectedScreen } from "builderStore"
|
||||||
import { goto, params, redirect } from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
import { findComponent } from "builderStore/componentUtils"
|
import { findComponent } from "builderStore/componentUtils"
|
||||||
|
|
||||||
// Keep URL and state in sync for selected component ID
|
// Keep URL and state in sync for selected component ID
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
keys: [
|
urlParam: "componentId",
|
||||||
{
|
stateKey: "selectedComponentId",
|
||||||
url: "componentId",
|
validate: id => !!findComponent($selectedScreen.props, id),
|
||||||
state: "selectedComponentId",
|
fallbackUrl: "../",
|
||||||
validate: componentId => {
|
|
||||||
return !!findComponent($selectedScreen.props, componentId)
|
|
||||||
},
|
|
||||||
fallbackUrl: "../",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
store,
|
store,
|
||||||
params,
|
routify,
|
||||||
goto,
|
|
||||||
redirect,
|
|
||||||
baseUrl: "..",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(stopSyncing)
|
onDestroy(stopSyncing)
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
|
$redirect("./screens")
|
||||||
|
</script>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script>
|
||||||
|
import ScreenNavigationPanel from "./_components/ScreenNavigationPanel.svelte"
|
||||||
|
import ScreenSettingsPanel from "./_components/ScreenSettingsPanel.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ScreenNavigationPanel />
|
||||||
|
<ScreenSettingsPanel />
|
|
@ -1,27 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import { onDestroy } from "svelte"
|
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
|
||||||
import { goto, params, redirect } from "@roxi/routify"
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
baseUrl: "..",
|
|
||||||
})
|
|
||||||
|
|
||||||
onDestroy(stopSyncing)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -1,21 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store, selectedScreen } from "builderStore"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { redirect } from "@roxi/routify"
|
|
||||||
|
|
||||||
let loaded = false
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if ($selectedScreen) {
|
|
||||||
$redirect(`./${$selectedScreen._id}`)
|
|
||||||
} else if ($store.screens?.length) {
|
|
||||||
$redirect(`./${$store.screens[0]._id}`)
|
|
||||||
} else {
|
|
||||||
loaded = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if loaded}
|
|
||||||
You need to create a screen!
|
|
||||||
{/if}
|
|
|
@ -1,5 +1,21 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { store, selectedScreen } from "builderStore"
|
||||||
|
import { onMount } from "svelte"
|
||||||
import { redirect } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
$redirect(`./screens`)
|
let loaded = false
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if ($selectedScreen) {
|
||||||
|
$redirect(`./${$selectedScreen._id}`)
|
||||||
|
} else if ($store.screens?.length) {
|
||||||
|
$redirect(`./${$store.screens[0]._id}`)
|
||||||
|
} else {
|
||||||
|
loaded = true
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if loaded}
|
||||||
|
You need to create a screen!
|
||||||
|
{/if}
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import { onDestroy } from "svelte"
|
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
|
||||||
import { goto, params, redirect } from "@roxi/routify"
|
|
||||||
import ScreenNavigationPanel from "./_components/ScreenNavigationPanel.svelte"
|
|
||||||
import ScreenSettingsPanel from "./_components/ScreenSettingsPanel.svelte"
|
|
||||||
|
|
||||||
// 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>
|
|
||||||
|
|
||||||
<ScreenNavigationPanel />
|
|
||||||
<ScreenSettingsPanel />
|
|
|
@ -1,21 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store, selectedScreen } from "builderStore"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { redirect } from "@roxi/routify"
|
|
||||||
|
|
||||||
let loaded = false
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if ($selectedScreen) {
|
|
||||||
$redirect(`./${$selectedScreen._id}`)
|
|
||||||
} else if ($store.screens?.length) {
|
|
||||||
$redirect(`./${$store.screens[0]._id}`)
|
|
||||||
} else {
|
|
||||||
loaded = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if loaded}
|
|
||||||
You need to create a screen!
|
|
||||||
{/if}
|
|
|
@ -2,5 +2,3 @@
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
$goto("./design")
|
$goto("./design")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=false -->
|
|
||||||
|
|
|
@ -1223,10 +1223,10 @@
|
||||||
estree-walker "^2.0.1"
|
estree-walker "^2.0.1"
|
||||||
picomatch "^2.2.2"
|
picomatch "^2.2.2"
|
||||||
|
|
||||||
"@roxi/routify@2.18.0":
|
"@roxi/routify@2.18.5":
|
||||||
version "2.18.0"
|
version "2.18.5"
|
||||||
resolved "https://registry.yarnpkg.com/@roxi/routify/-/routify-2.18.0.tgz#8f88bedd936312d0dbe44cbc11ab179b1f938ec2"
|
resolved "https://registry.yarnpkg.com/@roxi/routify/-/routify-2.18.5.tgz#abd2b7cadeed008ab20d40a68323719d9c254552"
|
||||||
integrity sha512-MVB50HN+VQWLzfjLplcBjsSBvwOiExKOmht2DuWR3WQ60JxQi9pSejkB06tFVkFKNXz2X5iYtKDqKBTdae/gRg==
|
integrity sha512-xNG84JOSUtCyOV0WeQwPcj+HbA4nxtXCqvt9JXQZm13pdJZqY18WV2C7ayReKGtRAa3ogQyDo8k/C8f1MixA1w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@roxi/ssr" "^0.2.1"
|
"@roxi/ssr" "^0.2.1"
|
||||||
"@types/node" ">=4.2.0 < 13"
|
"@types/node" ">=4.2.0 < 13"
|
||||||
|
|
Loading…
Reference in New Issue