Compare commits

...

7 commits

Author SHA1 Message Date
thislight
63fe4acc98
Settings: fix back button pop too much frames 2024-11-16 22:38:27 +08:00
thislight
32dffaaa3d
StackedRouter: intergrated with web history API 2024-11-16 22:38:00 +08:00
thislight
5be56bb80e
A: fix missolved path 2024-11-16 22:37:22 +08:00
thislight
e3d9d0c4ba
StackedRouter: add default background 2024-11-16 22:37:05 +08:00
thislight
2da7bf134e
App: fix path for motion settings 2024-11-16 22:27:35 +08:00
thislight
ab37a280e7
ProfileMenuButton: fix open settings in new page 2024-11-16 22:27:04 +08:00
thislight
9dfcfa3868
StackedRouter: add default open animation 2024-11-16 21:50:18 +08:00
6 changed files with 66 additions and 36 deletions

View file

@ -46,10 +46,10 @@ const Routing: Component = () => {
return ( return (
<StackedRouter> <StackedRouter>
<Route path="/" component={TimelineHome} /> <Route path="/" component={TimelineHome} />
<Route path="/settings" component={Settings} />
<Route path="/settings/language" component={LanguageSettings} /> <Route path="/settings/language" component={LanguageSettings} />
<Route path="/settings/region" component={RegionSettings} /> <Route path="/settings/region" component={RegionSettings} />
<Route path="/motions" component={MotionSettings} /> <Route path="/settings/motions" component={MotionSettings} />
<Route path="/settings" component={Settings} />
<Route path="/:acct/toot/:id" component={TootBottomSheet} /> <Route path="/:acct/toot/:id" component={TootBottomSheet} />
<Route path="/:acct/profile/:id" component={Profile} /> <Route path="/:acct/profile/:id" component={Profile} />

View file

@ -1,5 +1,6 @@
import { type JSX } from "solid-js"; import { splitProps, type JSX } from "solid-js";
import { useNavigator } from "./StackedRouter"; import { useNavigator } from "./StackedRouter";
import { useResolvedPath } from "@solidjs/router";
function handleClick( function handleClick(
push: (name: string, state: unknown) => void, push: (name: string, state: unknown) => void,
@ -7,13 +8,14 @@ function handleClick(
) { ) {
const target = event.currentTarget; const target = event.currentTarget;
event.preventDefault(); event.preventDefault();
event.stopPropagation();
push(target.href, { state: target.getAttribute("state") || undefined }); push(target.href, { state: target.getAttribute("state") || undefined });
} }
const A = (oprops: JSX.HTMLElementTags["a"]) => { const A = (oprops: Omit<JSX.HTMLElementTags["a"], "onClick" | "onclick">) => {
const [props, rest] = splitProps(oprops, ["href"]);
const resolvedPath = useResolvedPath(() => props.href || "#");
const { push } = useNavigator(); const { push } = useNavigator();
return <a onClick={[handleClick, push]} {...oprops}></a>; return <a onClick={[handleClick, push]} href={resolvedPath()} {...rest}></a>;
}; };
export default A; export default A;

View file

@ -21,6 +21,7 @@ dialog.StackedPage {
contain-intrinsic-size: auto 560px auto 100dvh; contain-intrinsic-size: auto 560px auto 100dvh;
content-visibility: auto; content-visibility: auto;
background: var(--tutu-color-surface);
box-shadow: var(--tutu-shadow-e16); box-shadow: var(--tutu-shadow-e16);
@media (min-width: 560px) { @media (min-width: 560px) {

View file

@ -6,17 +6,18 @@ import {
createRenderEffect, createRenderEffect,
createUniqueId, createUniqueId,
Index, Index,
onMount,
Show, Show,
untrack, untrack,
useContext, useContext,
type Accessor, type Accessor,
} from "solid-js"; } from "solid-js";
import { createStore, unwrap } from "solid-js/store"; import { createStore, unwrap } from "solid-js/store";
import { insert, render } from "solid-js/web";
import "./StackedRouter.css"; import "./StackedRouter.css";
import { animateSlideInFromRight } from "./anim"; import { animateSlideInFromRight, animateSlideOutToRight } from "./anim";
import { ANIM_CURVE_DECELERATION, ANIM_CURVE_STD } from "../material/theme"; import { ANIM_CURVE_DECELERATION, ANIM_CURVE_STD } from "../material/theme";
import {
makeEventListener,
} from "@solid-primitives/event-listener";
export type StackedRouterProps = Omit<RouterProps, "url">; export type StackedRouterProps = Omit<RouterProps, "url">;
@ -24,7 +25,6 @@ export type StackFrame = {
path: string; path: string;
rootId: string; rootId: string;
state: unknown; state: unknown;
beforeShow?: (element: HTMLElement) => void;
}; };
export type NewFrameOptions<T> = (T extends undefined export type NewFrameOptions<T> = (T extends undefined
@ -102,6 +102,32 @@ function onDialogClick(
} }
} }
function animateClose(element: HTMLElement) {
if (window.innerWidth <= 560) {
return animateSlideOutToRight(element, { easing: ANIM_CURVE_DECELERATION });
} else {
return element.animate(
{
opacity: [0.5, 0],
},
{ easing: ANIM_CURVE_STD, duration: 220 },
);
}
}
function animateOpen(element: HTMLElement) {
if (window.innerWidth <= 560) {
animateSlideInFromRight(element, { easing: ANIM_CURVE_DECELERATION });
} else {
element.animate(
{
opacity: [0.5, 1],
},
{ easing: ANIM_CURVE_STD, duration: 220 },
);
}
}
/** /**
* The router that stacks the pages. * The router that stacks the pages.
*/ */
@ -115,10 +141,21 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
state: opts?.state, state: opts?.state,
rootId: createUniqueId(), rootId: createUniqueId(),
}; };
mutStack(opts?.replace ? stack.length - 1 : stack.length, frame); mutStack(opts?.replace ? stack.length - 1 : stack.length, frame);
if (opts?.replace) {
window.history.replaceState(unwrap(stack), "", path);
} else {
window.history.pushState(unwrap(stack), "", path);
}
return frame; return frame;
}); });
const onlyPopFrame = (depth: number) => {
mutStack((o) => o.toSpliced(o.length - depth, depth));
window.history.go(-depth);
};
const popFrame = (depth: number = 1) => const popFrame = (depth: number = 1) =>
untrack(() => { untrack(() => {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
@ -130,20 +167,11 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
const lastFrame = stack[stack.length - 1]; const lastFrame = stack[stack.length - 1];
const element = document.getElementById(lastFrame.rootId)!; const element = document.getElementById(lastFrame.rootId)!;
requestAnimationFrame(() => { requestAnimationFrame(() => {
const animation = element.animate( const animation = animateClose(element);
{ animation.addEventListener("finish", () => onlyPopFrame(depth));
opacity: [0.5, 0],
},
{ easing: ANIM_CURVE_STD, duration: 220 },
);
animation.addEventListener("finish", () =>
mutStack((o) => o.toSpliced(o.length - depth, depth)),
);
}); });
} else { } else {
mutStack((o) => { onlyPopFrame(depth);
return o.toSpliced(o.length - depth, depth);
});
} }
}); });
@ -154,24 +182,23 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
createRenderEffect(() => { createRenderEffect(() => {
if (stack.length === 0) { if (stack.length === 0) {
pushFrame("/", undefined); pushFrame(window.location.pathname);
} }
}); });
createRenderEffect(() => {
makeEventListener(window, "popstate", (event) => {
if (event.state && stack.length !== event.state.length) {
mutStack(event.state);
}
});
});
const onBeforeDialogMount = (element: HTMLDialogElement) => { const onBeforeDialogMount = (element: HTMLDialogElement) => {
createEffect(() => { createEffect(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
element.showModal(); element.showModal();
if (window.innerWidth <= 560) { animateOpen(element);
animateSlideInFromRight(element, { easing: ANIM_CURVE_DECELERATION });
} else {
element.animate(
{
opacity: [0.5, 1],
},
{ easing: ANIM_CURVE_STD, duration: 220 },
);
}
}); });
}); });
}; };

View file

@ -201,7 +201,7 @@ const Settings: ParentComponent = (props) => {
variant="dense" variant="dense"
sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }} sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
> >
<IconButton color="inherit" onClick={[pop, 11]} disableRipple> <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
<Title>{t("Settings")}</Title> <Title>{t("Settings")}</Title>

View file

@ -51,7 +51,7 @@ const ProfileMenuButton: ParentComponent<{
props.onClick?.(); props.onClick?.();
}; };
const inf = () => props.profile?.account.inf const inf = () => props.profile?.account.inf;
const onClose = () => { const onClose = () => {
props.onClick?.(); props.onClick?.();
@ -130,7 +130,7 @@ const ProfileMenuButton: ParentComponent<{
{props.children} {props.children}
<Divider /> <Divider />
</Show> </Show>
<MenuItem component={A} href="/settings" onClick={onClose}> <MenuItem component={A} href="/settings">
<ListItemIcon> <ListItemIcon>
<SettingsIcon /> <SettingsIcon />
</ListItemIcon> </ListItemIcon>