diff --git a/src/UnexpectedError.tsx b/src/UnexpectedError.tsx index 4177758..906d99d 100644 --- a/src/UnexpectedError.tsx +++ b/src/UnexpectedError.tsx @@ -49,13 +49,11 @@ const UnexpectedError: Component<{ error?: any }> = (props) => {

Oh, it is our fault.

There is an unexpected error in our app, and it's not your fault.

- You can restart the app to see if this guy is gone. If you meet this guy + You can reload to see if this guy is gone. If you meet this guy repeatly, please report to us.

- +
diff --git a/src/platform/StackedRouter.css b/src/platform/StackedRouter.css index eac26ce..6b912c1 100644 --- a/src/platform/StackedRouter.css +++ b/src/platform/StackedRouter.css @@ -3,8 +3,6 @@ display: contents; max-width: 100vw; max-width: 100dvw; - - contain: layout; } dialog.StackedPage { diff --git a/src/platform/StackedRouter.tsx b/src/platform/StackedRouter.tsx index 5bc6d8c..792ec97 100644 --- a/src/platform/StackedRouter.tsx +++ b/src/platform/StackedRouter.tsx @@ -2,6 +2,7 @@ import { StaticRouter, type RouterProps } from "@solidjs/router"; import { Component, createContext, + createEffect, createRenderEffect, createUniqueId, Index, @@ -33,33 +34,8 @@ export type NewFrameOptions = (T extends undefined state?: T; } : { state: T }) & { - /** - * The new frame should replace the current frame. - */ replace?: boolean; - /** - * The animatedOpen phase of the life cycle. - * - * You can use this hook to animate the opening - * of the frame. In this phase, the frame content is created - * and is mounted to the document. - * - * You must return an {@link Animation}. This function must be - * without side effects. This phase is ended after the {@link Animation} - * finished. - */ animateOpen?: StackFrame["animateOpen"]; - /** - * The animatedClose phase of the life cycle. - * - * You can use this hook to animate the closing of the frame. - * In this phase, the frame content is still mounted in the - * document and will be unmounted after this phase. - * - * You must return an {@link Animation}. This function must be - * without side effects. This phase is ended after the - * {@link Animation} finished. - */ animateClose?: StackFrame["animateClose"]; }; @@ -77,10 +53,6 @@ export type Navigator> = { const NavigatorContext = /* @__PURE__ */ createContext(); -export function useMaybeNavigator() { - return useContext(NavigatorContext); -} - /** * Get the navigator of the {@link StackedRouter}. * @@ -91,7 +63,7 @@ export function useMaybeNavigator() { * navigator to the type you need. */ export function useNavigator() { - const navigator = useMaybeNavigator(); + const navigator = useContext(NavigatorContext); if (!navigator) { throw new TypeError("not in available scope of StackedRouter"); @@ -108,12 +80,8 @@ export type CurrentFrame = { const CurrentFrameContext = /* @__PURE__ */ createContext>>(); -export function useMaybeCurrentFrame() { - return useContext(CurrentFrameContext); -} - export function useCurrentFrame() { - const frame = useMaybeCurrentFrame(); + const frame = useContext(CurrentFrameContext); if (!frame) { throw new TypeError("not in available scope of StackedRouter"); @@ -122,28 +90,6 @@ export function useCurrentFrame() { return frame; } -/** - * Return an accessor of is current frame is suspended. - * - * A suspended frame is the one not on the top. "Suspended" - * is the description of a certain situtation, not in the life cycle - * of a frame. - */ -export function useMaybeIsFrameSuspended() { - const { frames } = useMaybeNavigator() || {}; - - if (typeof frames === "undefined") { - return () => false; - } - - const thisFrame = useCurrentFrame(); - - return () => { - const idx = thisFrame().index; - return frames.length - 1 > idx; - }; -} - function onDialogClick( onClose: () => void, event: MouseEvent & { currentTarget: HTMLDialogElement }, @@ -243,10 +189,10 @@ const StackedRouter: Component = (oprops) => { )! as HTMLDialogElement; const createAnimation = lastFrame.animateClose ?? animateClose; requestAnimationFrame(() => { - element.classList.add("animating"); + element.classList.add("animating") const animation = createAnimation(element); animation.addEventListener("finish", () => { - element.classList.remove("animating"); + element.classList.remove("animating") onlyPopFrame(depth); }); }); diff --git a/src/timelines/Home.tsx b/src/timelines/Home.tsx index 13b59d7..339e157 100644 --- a/src/timelines/Home.tsx +++ b/src/timelines/Home.tsx @@ -3,9 +3,11 @@ import { Show, onMount, type ParentComponent, - createRenderEffect, + children, + Suspense, } from "solid-js"; import { useDocumentTitle } from "../utils"; +import { type mastodon } from "masto"; import Scaffold from "../material/Scaffold"; import { AppBar, @@ -21,11 +23,17 @@ import ProfileMenuButton from "./ProfileMenuButton"; import Tabs from "../material/Tabs"; import Tab from "../material/Tab"; import { makeEventListener } from "@solid-primitives/event-listener"; +import BottomSheet, { + HERO as BOTTOM_SHEET_HERO, +} from "../material/BottomSheet"; import { $settings } from "../settings/stores"; import { useStore } from "@nanostores/solid"; +import { HeroSourceProvider, type HeroSource } from "../platform/anim"; +import { setCache as setTootBottomSheetCache } from "./TootBottomSheet"; import TrendTimelinePanel from "./TrendTimelinePanel"; import TimelinePanel from "./TimelinePanel"; import { useSessions } from "../masto/clients"; +import { useNavigator } from "../platform/StackedRouter"; const Home: ParentComponent = (props) => { let panelList: HTMLDivElement; @@ -35,17 +43,29 @@ const Home: ParentComponent = (props) => { const settings$ = useStore($settings); const profiles = useSessions(); + const profile = () => { + const all = profiles(); + if (all.length > 0) { + return all[0].account.inf; + } + }; const client = () => { const all = profiles(); return all?.[0]?.client; }; + const {pop, push} = useNavigator(); + const [heroSrc, setHeroSrc] = createSignal({}); + const [panelOffset, setPanelOffset] = createSignal(0); const prefetching = () => !settings$().prefetchTootsDisabled; + const [currentFocusOn, setCurrentFocusOn] = createSignal([]); const [focusRange, setFocusRange] = createSignal([0, 0] as readonly [ number, number, ]); + const child = children(() => props.children); + let scrollEventLockReleased = true; const recalculateTabIndicator = () => { @@ -82,17 +102,17 @@ const Home: ParentComponent = (props) => { } }; - const requestRecalculateTabIndicator = () => { - if (scrollEventLockReleased) { - requestAnimationFrame(recalculateTabIndicator); - } - }; - - createRenderEffect(() => { - makeEventListener(window, "resize", requestRecalculateTabIndicator); - }); - onMount(() => { + makeEventListener(panelList, "scroll", () => { + if (scrollEventLockReleased) { + requestAnimationFrame(recalculateTabIndicator); + } + }); + makeEventListener(window, "resize", () => { + if (scrollEventLockReleased) { + requestAnimationFrame(recalculateTabIndicator); + } + }); requestAnimationFrame(recalculateTabIndicator); }); @@ -115,6 +135,30 @@ const Home: ParentComponent = (props) => { } }; + const openFullScreenToot = ( + toot: mastodon.v1.Status, + srcElement?: HTMLElement, + reply?: boolean, + ) => { + const p = profiles()[0]; + const inf = p.account.inf ?? profile(); + if (!inf) { + console.warn("no account info?"); + return; + } + setHeroSrc((x) => + Object.assign({}, x, { [BOTTOM_SHEET_HERO]: srcElement }), + ); + const acct = `${inf.username}@${p.account.site}`; + setTootBottomSheetCache(acct, toot); + push(`/${encodeURIComponent(acct)}/toot/${toot.id}`, { + state: reply + ? { + tootReply: true, + } + : undefined, + }); + }; css` .tab-panel { @@ -165,7 +209,7 @@ const Home: ParentComponent = (props) => { class="responsive" sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }} > - + Home @@ -195,40 +239,48 @@ const Home: ParentComponent = (props) => { } > - - -
-
-
- + + + +
+
+
+ +
-
-
-
- +
+
+ +
-
-
-
- +
+
+ +
+
-
-
- - + + + + pop(1)}> + {child()} + + + ); diff --git a/src/timelines/PullDownToRefresh.tsx b/src/timelines/PullDownToRefresh.tsx index 7cbacdb..10df2bf 100644 --- a/src/timelines/PullDownToRefresh.tsx +++ b/src/timelines/PullDownToRefresh.tsx @@ -10,7 +10,6 @@ import { Refresh as RefreshIcon } from "@suid/icons-material"; import { CircularProgress } from "@suid/material"; import { makeEventListener } from "@solid-primitives/event-listener"; import { createVisibilityObserver } from "@solid-primitives/intersection-observer"; -import { useMaybeIsFrameSuspended } from "../platform/StackedRouter"; const PullDownToRefresh: Component<{ loading?: boolean; @@ -34,7 +33,6 @@ const PullDownToRefresh: Component<{ }); const rootVisible = obvx(() => rootElement); - const isFrameSuspended = useMaybeIsFrameSuspended() createEffect(() => { if (!rootVisible()) setPullDown(0); @@ -111,9 +109,6 @@ const PullDownToRefresh: Component<{ if (!rootVisible()) { return; } - if (isFrameSuspended()) { - return; - } const element = props.linkedElement; if (!element) return; makeEventListener(element, "wheel", handleLinkedWheel); @@ -164,9 +159,6 @@ const PullDownToRefresh: Component<{ if (!rootVisible()) { return; } - if (isFrameSuspended()) { - return; - } const element = props.linkedElement; if (!element) return; makeEventListener(element, "touchmove", handleTouch); diff --git a/src/timelines/TimelinePanel.tsx b/src/timelines/TimelinePanel.tsx index 7a4c4e1..150ca0e 100644 --- a/src/timelines/TimelinePanel.tsx +++ b/src/timelines/TimelinePanel.tsx @@ -20,6 +20,12 @@ const TimelinePanel: Component<{ client: mastodon.rest.Client; name: "home" | "public"; prefetch?: boolean; + + openFullScreenToot: ( + toot: mastodon.v1.Status, + srcElement?: HTMLElement, + reply?: boolean, + ) => void; }> = (props) => { const [scrollLinked, setScrollLinked] = createSignal(); diff --git a/src/timelines/TrendTimelinePanel.tsx b/src/timelines/TrendTimelinePanel.tsx index 9f0ba75..1691ec8 100644 --- a/src/timelines/TrendTimelinePanel.tsx +++ b/src/timelines/TrendTimelinePanel.tsx @@ -13,6 +13,12 @@ import TootList from "./TootList.jsx"; const TrendTimelinePanel: Component<{ client: mastodon.rest.Client; + + openFullScreenToot: ( + toot: mastodon.v1.Status, + srcElement?: HTMLElement, + reply?: boolean, + ) => void; }> = (props) => { const [scrollLinked, setScrollLinked] = createSignal(); const [tl, snapshot, { refetch: refetchTimeline }] = createTimelineSnapshot(