-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(application): source default URL params from storage #3408
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,10 +27,11 @@ | |
:src="uri(sources, '/me/:route', { | ||
route: props.name, | ||
})" | ||
@change="resolve" | ||
v-slot="{ data: me }" | ||
> | ||
<slot | ||
v-if="me && submit" | ||
v-if="me && submit && redirected" | ||
:id="UniqueId" | ||
name="default" | ||
:t="t" | ||
|
@@ -72,6 +73,7 @@ import { sources } from '@/app/me/sources' | |
import type { Ref } from 'vue' | ||
import type { RouteRecordRaw, RouteLocationNormalizedLoaded } from 'vue-router' | ||
|
||
|
||
export type RouteView = { | ||
name: string | ||
from: Ref | ||
|
@@ -89,6 +91,11 @@ const win = window | |
const env = useEnv() | ||
const can = useCan() | ||
const uri = useUri() | ||
|
||
|
||
let resolve: (resolved: object) => void | ||
const meResponse = new Promise((r) => resolve = r) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Boilerplate for setting up the JS accessible promise to wait for |
||
|
||
const htmlAttrs = useAttrs() | ||
const { t } = useI18n() | ||
const route = useRoute() | ||
|
@@ -180,56 +187,77 @@ watch(routeParams, (val) => { | |
}) | ||
|
||
// when any URL params change, normalize/validate/default and reset our actual application params | ||
const redirected = ref<boolean>(false) | ||
watch(() => { | ||
return Object.keys(props.params).map((item) => { return route.params[item] || route.query[item] }) | ||
}, () => { | ||
}, async () => { | ||
const stored = await meResponse | ||
|
||
// merge params in order of importance/priority: | ||
// 1. Anything stored by the user in storage | ||
// 2. URL query params | ||
// 3. URL path params | ||
const params = { | ||
...get(stored, 'params', {}), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the super important bit that simplifies everything, we use the stored params first, then query, then URL params. i.e. we just continue to do as we did before. The problem we more fiddling around with Vue so we could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks much cleaner now 👍 |
||
...route.query, | ||
...route.params, | ||
} | ||
|
||
// normalize/validate/default all params using the RouteView :params | ||
// 1. Ignore any `?param[]=0` type params, we just take the first one | ||
// 2. Using normalizeUrlParam and the type information from RouteView :params convert things to the correct type i.e. null > false | ||
// 3. Use RouteView :params to set any params that are empty, i.e. use RouteView :params as defaults. | ||
Object.entries({ | ||
...props.params, | ||
}).reduce((prev, [prop, def]) => { | ||
const param = urlParam(typeof params[prop] === 'undefined' ? '' : params[prop]) | ||
prev[prop] = normalizeUrlParam(param, def) | ||
return prev | ||
}, routeParams as Record<string, string | number | boolean>) | ||
}, { immediate: true }) | ||
|
||
watch(() => props.name, () => { | ||
// we only want query params here | ||
const params = Object.entries(routeParams || {}).reduce((prev, [key, value]) => { | ||
if (typeof route.params[key] === 'undefined') { | ||
prev[key] = value | ||
// only one first load, if any params are missing from the URL/query | ||
// redirect/add the query params to the URL. | ||
// this ensures any JS applied defaults are reflected in the URL | ||
if(!redirected.value) { | ||
// we only want query params here | ||
const params = Object.entries(routeParams || {}).reduce((prev, [key, value]) => { | ||
if (typeof route.params[key] === 'undefined') { | ||
prev[key] = value | ||
} | ||
return prev | ||
}, {} as Record<string, string | boolean | undefined>) | ||
if (Object.keys(params).length > 0) { | ||
router.replace({ | ||
query: cleanQuery(params, route.query), | ||
}) | ||
} | ||
return prev | ||
}, {} as Record<string, string | boolean | undefined>) | ||
if (Object.keys(params).length > 0) { | ||
router.replace({ | ||
query: cleanQuery(params, route.query), | ||
}) | ||
redirected.value = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the two merged There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that helps a lot and thanks for all the new comments 👍 |
||
} | ||
}, { immediate: true }) | ||
|
||
type RouteParams = Record<string, string | boolean | number | undefined> | ||
let newParams: RouteParams = {} | ||
|
||
const routerPush = (params: RouteParams) => { | ||
router.push({ | ||
name: props.name, | ||
query: cleanQuery(params, route.query), | ||
}) | ||
newParams = {} | ||
} | ||
|
||
const routeUpdate = (params: RouteParams): void => { | ||
newParams = { | ||
...newParams, | ||
...params, | ||
} | ||
routerPush(newParams) | ||
} | ||
|
||
const routeReplace = (...args: RouteReplaceParams) => { | ||
router.push(...args) | ||
} | ||
|
||
const routerBack = (...args: RouteReplaceParams) => { | ||
try { | ||
if (win.history.state.back !== null) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export default (prefix: string = 'kumahq-app', storage: Storage = window.localStorage) => { | ||
const get = async (key: string): Promise<object> => { | ||
try { | ||
return JSON.parse(storage.getItem(`${prefix}:${key}`) ?? '{}') | ||
} catch (e) { | ||
console.error(e) | ||
} | ||
return {} | ||
} | ||
const set = async (key: string, value: object): Promise<object> => { | ||
try { | ||
storage.setItem(`${prefix}:${key}`, JSON.stringify(value)) | ||
return value | ||
} catch (e) { | ||
console.error(e) | ||
} | ||
return {} | ||
} | ||
|
||
return { get, set } | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want to get rid of |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,38 +2,32 @@ import merge from 'deepmerge' | |
|
||
import { defineSources } from '../application/services/data-source' | ||
|
||
export const sources = (prefix: string = 'me', storage: Storage = window.localStorage) => { | ||
const get = async (key: string): Promise<object> => { | ||
try { | ||
return JSON.parse(storage.getItem(`${prefix}:${key}`) ?? '{}') | ||
} catch (e) { | ||
console.error(e) | ||
} | ||
return {} | ||
} | ||
const set = async (key: string, value: object): Promise<object> => { | ||
try { | ||
storage.setItem(`${prefix}:${key}`, JSON.stringify(value)) | ||
return value | ||
} catch (e) { | ||
console.error(e) | ||
} | ||
return {} | ||
} | ||
type Storage = { | ||
get: (key: string) => Promise<object> | ||
set: (key: string, value: object) => Promise<object> | ||
} | ||
export const sources = ({ get, set }: Storage) => { | ||
return defineSources({ | ||
// retrieves both route and global app prefs and merges them | ||
// anything route specific overwriting anything global/app specific | ||
'/me/:route': async (params) => { | ||
const json = await get(params.route) | ||
const res = merge({ | ||
params: { | ||
size: 50, | ||
const [app, route] = await Promise.all([ | ||
get('/'), | ||
get(params.route), | ||
]) | ||
return merge( | ||
{ | ||
params: { | ||
size: 50, | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a plan to use this as the "centralized 50". We currently set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could probably move these things to the global |
||
}, | ||
}, json) | ||
return res | ||
app, | ||
route, | ||
) | ||
}, | ||
'/me/:route/:data': async (params) => { | ||
const json = JSON.parse(params.data) | ||
const res = merge<object>(await get(params.route), json) | ||
|
||
set(params.route, res) | ||
}, | ||
}) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the extra template guard I reused to make sure we don't show anything until we've set our default query params. This is only necessary during CI for some reason, probably due to the single tick used in the
watch
as a consequence of theasync/await