-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(web): persist scroll position on navigation back to album (#11388)
Co-authored-by: Calum Dingwall <[email protected]> Co-authored-by: Alex <[email protected]>
- Loading branch information
1 parent
51de108
commit d277096
Showing
8 changed files
with
234 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { navigating } from '$app/stores'; | ||
import { AppRoute, SessionStorageKey } from '$lib/constants'; | ||
import { handlePromiseError } from '$lib/utils'; | ||
|
||
interface Options { | ||
/** | ||
* {@link AppRoute} for subpages that scroll state should be kept while visiting. | ||
* | ||
* This must be kept the same in all subpages of this route for the scroll memory clearer to work. | ||
*/ | ||
routeStartsWith: AppRoute; | ||
/** | ||
* Function to clear additional data/state before scrolling (ex infinite scroll). | ||
*/ | ||
beforeClear?: () => void; | ||
} | ||
|
||
interface PageOptions extends Options { | ||
/** | ||
* Function to save additional data/state before scrolling (ex infinite scroll). | ||
*/ | ||
beforeSave?: () => void; | ||
/** | ||
* Function to load additional data/state before scrolling (ex infinite scroll). | ||
*/ | ||
beforeScroll?: () => Promise<void>; | ||
} | ||
|
||
/** | ||
* @param node The scroll slot element, typically from {@link UserPageLayout} | ||
*/ | ||
export function scrollMemory( | ||
node: HTMLElement, | ||
{ routeStartsWith, beforeSave, beforeClear, beforeScroll }: PageOptions, | ||
) { | ||
const unsubscribeNavigating = navigating.subscribe((navigation) => { | ||
const existingScroll = sessionStorage.getItem(SessionStorageKey.SCROLL_POSITION); | ||
if (navigation?.to && !existingScroll) { | ||
// Save current scroll information when going into a subpage. | ||
if (navigation.to.url.pathname.startsWith(routeStartsWith)) { | ||
beforeSave?.(); | ||
sessionStorage.setItem(SessionStorageKey.SCROLL_POSITION, node.scrollTop.toString()); | ||
} else { | ||
beforeClear?.(); | ||
sessionStorage.removeItem(SessionStorageKey.SCROLL_POSITION); | ||
} | ||
} | ||
}); | ||
|
||
handlePromiseError( | ||
(async () => { | ||
await beforeScroll?.(); | ||
|
||
const newScroll = sessionStorage.getItem(SessionStorageKey.SCROLL_POSITION); | ||
if (newScroll) { | ||
node.scroll({ | ||
top: Number.parseFloat(newScroll), | ||
behavior: 'instant', | ||
}); | ||
} | ||
beforeClear?.(); | ||
sessionStorage.removeItem(SessionStorageKey.SCROLL_POSITION); | ||
})(), | ||
); | ||
|
||
return { | ||
destroy() { | ||
unsubscribeNavigating(); | ||
}, | ||
}; | ||
} | ||
|
||
export function scrollMemoryClearer(_node: HTMLElement, { routeStartsWith, beforeClear }: Options) { | ||
const unsubscribeNavigating = navigating.subscribe((navigation) => { | ||
// Forget scroll position from main page if going somewhere else. | ||
if (navigation?.to && !navigation?.to.url.pathname.startsWith(routeStartsWith)) { | ||
beforeClear?.(); | ||
sessionStorage.removeItem(SessionStorageKey.SCROLL_POSITION); | ||
} | ||
}); | ||
|
||
return { | ||
destroy() { | ||
unsubscribeNavigating(); | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/** | ||
* @license Apache-2.0 | ||
* https://github.com/hperrin/svelte-material-ui/blob/master/packages/common/src/internal/useActions.ts | ||
*/ | ||
|
||
export type SvelteActionReturnType<P> = { | ||
update?: (newParams?: P) => void; | ||
destroy?: () => void; | ||
} | void; | ||
|
||
export type SvelteHTMLActionType<P> = (node: HTMLElement, params?: P) => SvelteActionReturnType<P>; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export type HTMLActionEntry<P = any> = SvelteHTMLActionType<P> | [SvelteHTMLActionType<P>, P]; | ||
|
||
export type HTMLActionArray = HTMLActionEntry[]; | ||
|
||
export type SvelteSVGActionType<P> = (node: SVGElement, params?: P) => SvelteActionReturnType<P>; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export type SVGActionEntry<P = any> = SvelteSVGActionType<P> | [SvelteSVGActionType<P>, P]; | ||
|
||
export type SVGActionArray = SVGActionEntry[]; | ||
|
||
export type ActionArray = HTMLActionArray | SVGActionArray; | ||
|
||
export function useActions(node: HTMLElement | SVGElement, actions: ActionArray) { | ||
const actionReturns: SvelteActionReturnType<unknown>[] = []; | ||
|
||
if (actions) { | ||
for (const actionEntry of actions) { | ||
const action = Array.isArray(actionEntry) ? actionEntry[0] : actionEntry; | ||
if (Array.isArray(actionEntry) && actionEntry.length > 1) { | ||
actionReturns.push(action(node as HTMLElement & SVGElement, actionEntry[1])); | ||
} else { | ||
actionReturns.push(action(node as HTMLElement & SVGElement)); | ||
} | ||
} | ||
} | ||
|
||
return { | ||
update(actions: ActionArray) { | ||
if ((actions?.length || 0) != actionReturns.length) { | ||
throw new Error('You must not change the length of an actions array.'); | ||
} | ||
|
||
if (actions) { | ||
for (const [i, returnEntry] of actionReturns.entries()) { | ||
if (returnEntry && returnEntry.update) { | ||
const actionEntry = actions[i]; | ||
if (Array.isArray(actionEntry) && actionEntry.length > 1) { | ||
returnEntry.update(actionEntry[1]); | ||
} else { | ||
returnEntry.update(); | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
|
||
destroy() { | ||
for (const returnEntry of actionReturns) { | ||
returnEntry?.destroy?.(); | ||
} | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters