-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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(web): assets now have a permanent URL #8532
Conversation
Hello, I just finished the first round of testing; one issue I am seeing is using the mouse clicking on the back button from the |
Yeah, something slipped in late last night. Took a look with fresh eyes today and fixed the problem. Extracted the url manipulation into the navigation.ts util. Force pushed to resolve some conflicts with main. Not sure if this project prefers merge commits or rebases in PRs. |
It doesn't matter because we'll squash it into a single commit on main. |
@@ -1,11 +0,0 @@ | |||
import { authenticate } from '$lib/utils/auth'; |
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.
Why do we remove this file?
There are a few
|
Should fix #8238 as well. Thanks for working on this! |
Will also close #4746 |
I've split up this PR into very small, hopefully easy to understand, commit. I've tried hard to not update all the paths, but ultimately failed. However, I was able to remove the By using optional params, I don't need to parse the URL anymore. However, I still need to do that when I need to handle previous/next and open-asset navigation. That is because sveltekit doesn't expose https://github.com/sveltejs/kit/blob/main/packages/kit/src/utils/routing.js which would make it easier to parse, find/replace route segments. i.e. I could find the current route, and call Ok - now on to the actual meat of this PR - updating the URL with the new path when opening/closing and prev/next buttons are pushed. Previously, I had the navigation (the Here, I created some custom DOM events, and have the asestviewer/grid send these events at the right time. Then, once these events work they're way back up to the layout (which is part of /src/routes) it will figure out the route to navigate to. Now - as I was typing this, I googled a bit about bubbling events in svelte - and it turns out there is way they want you to do this - thats to forward them: https://svelte.dev/examples/event-forwarding In light of this guidance, I can rework the custom events to just use forwarding of the standard events. But before I do that, my question to ya'll is - do you want the components to directly call |
I believe these are the redirects (before this pr, navigations to |
@midzelis Firstly, thanks a lot for this write-up and for investing the time and efforts! :) |
edit: I commented before reading your write up, thank you |
Hello @midzelis, I was running some functional testing of this PR. There are a few things/bugs I noticed
Let's fix those first before another round of testing 😄 Thanks again for the amazing work! |
nice! switching from
I haven't checked if you could apply it in this PR, but you can share functionality without sharing URL paths with groups: https://kit.svelte.dev/docs/advanced-routing#advanced-layouts-group. However, it applies all or none to a directory still and so if you wanted to apply it to a subset of routes, as I think may be the case here, it may not be of use
I've tried 😄 It's contentious though. I think the concerns are that exposing an API would reduce our flexibility to make changes. Now that things are a bit more stable and we've had multiple major releases maybe it could be revisited at some point - though everyone's pretty busy with other things so I doubt it'd happen soon |
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.
I'm really liking the code so far! :)
|
||
$: albumId = ($page.route?.id === '/(user)/albums/[albumId]' || undefined) && $page.params.albumId; | ||
$: isShare = $page.route?.id === '/(user)/share/[key]' || undefined; | ||
$: albumId = isAlbumsRoute($page.route?.id) ? $page.params.albumId : undefined; |
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.
You could probably simplify this
$: albumId = isAlbumsRoute($page.route?.id) ? $page.params.albumId : undefined; | |
$: albumId = isAlbumsRoute($page.route?.id) && $page.params.albumId; |
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.
Does this not work?
web/src/lib/utils/navigation.ts
Outdated
export const isPhotosRoute = (route?: string | null) => route?.startsWith('/(user)/photos/[[assetId=id]]') || false; | ||
export const isSharedLinkRoute = (route?: string | null) => route?.startsWith('/(user)/share/[key]') || false; | ||
export const isSearchRoute = (route?: string | null) => route?.startsWith('/(user)/search') || false; | ||
export const isAlbumsRoute = (route?: string | null) => route?.startsWith('/(user)/albums/[albumId=id]') || false; | ||
export const isPeopleRoute = (route?: string | null) => route?.startsWith('/(user)/people/[personId]') || false; |
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.
You could remove all those || false
and instead write !!route?.startsWith(...)
. Probably up to personal preference though
web/src/lib/utils/navigation.ts
Outdated
export const isAssetViewerRoute = (target?: NavigationTarget | null) => | ||
(target?.route.id?.endsWith('/[[assetId=id]]') && 'assetId' in (target?.params || {})) || false; | ||
|
||
export async function getAssetInfoFromParam(params: { assetId?: string }) { |
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.
export async function getAssetInfoFromParam(params: { assetId?: string }) { | |
export async function getAssetInfoFromParam({ assetId }: { assetId?: string }) { |
web/src/lib/utils/navigation.ts
Outdated
const { assetId } = route; | ||
const next = assetId ? currentUrlReplaceAssetId(assetId) : currentUrlWithoutAsset(); | ||
if (next !== currentUrl()) { | ||
void goto(next, { replaceState: false }); |
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.
Why isn't this function async and we're awaiting the goto?
on:close={() => assetViewingStore.showAssetViewer(false)} | ||
isShared={false} | ||
/> | ||
{#await import('../../../../../lib/components/asset-viewer/asset-viewer.svelte') then AssetViewer} |
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.
Can we... not do that? lol
An absolute import would be much nicer here haha. Also, why do even need to import it inline?
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.
I believe it was imported via this await block in order to speed initial page load
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.
Nice!
web/src/lib/stores/assets.store.ts
Outdated
if (!bucketInfo) { | ||
let date = fromLocalDateTime(localDateTime); | ||
if (this.options.size == TimeBucketSize.Month) { | ||
date = date.set({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 }); | ||
} else if (this.options.size == TimeBucketSize.Day) { | ||
date = date.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }); | ||
} | ||
await this.loadBucket(date.toISO()!, BucketPosition.Unknown); | ||
} | ||
return this.assetToBucket[id] || null; |
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.
if (!bucketInfo) { | |
let date = fromLocalDateTime(localDateTime); | |
if (this.options.size == TimeBucketSize.Month) { | |
date = date.set({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 }); | |
} else if (this.options.size == TimeBucketSize.Day) { | |
date = date.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }); | |
} | |
await this.loadBucket(date.toISO()!, BucketPosition.Unknown); | |
} | |
return this.assetToBucket[id] || null; | |
if (bucketInfo) { | |
return bucketInfo; | |
} | |
let date = fromLocalDateTime(localDateTime); | |
if (this.options.size == TimeBucketSize.Month) { | |
date = date.set({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 }); | |
} else if (this.options.size == TimeBucketSize.Day) { | |
date = date.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }); | |
} | |
await this.loadBucket(date.toISO()!, BucketPosition.Unknown); | |
return this.assetToBucket[id] || null; |
@bo0tzz After thinking about it, I would vote for a move away from UUIDs entirely. I would say, use nanoid to generate the primary key - and store that nanoid id in the db. Personally, I don't think we need 340 undecillion records (340,282,366,920,938,000,000,000,000,000,000,000,000) in the db ;-) Using nanoid, with just 12 characters (which is generous), we get 9B ids before repetition, or if you are uploading 1000 pictures/hour, it will take 1,000 years to exhaust. You can play with a calculator here: https://zelark.github.io/nano-id-cc/ (Honestly, 8chars which is 2M ids is probably enough) And you could make it dynamic - if you run out of ids, just bump the length, and generate new ids with the new size. To handle collisions, there would need to be a retry mechanism on insert. Try to insert with generated id, if collision, then generate a new id and try again. I think I'll prototype this in another PR to see how it works. The beauty is that we don't need to update any of the existing ids - they can still work. But new assets will have smaller ids. Or, we could run a script to update all the existing ids to the new format, since nobody really has links to assets by id anyways, but there could be other API integrations out there - so I would make it a total optional thing, like the storage template migration job. |
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.
Nice one! :)
Add stable urls for all assets. Navigating within app will update browser location. Reloading the page will properly load the asset within the context.
#2906
#7017