Compare commits
No commits in common. "6fbd198021b637e7c207f0c5bc12c6b82fcad1ac" and "6879eb52928c07a945f4977a22cfd150780910b2" have entirely different histories.
6fbd198021
...
6879eb5292
7 changed files with 113 additions and 115 deletions
|
@ -49,13 +49,11 @@ const UnexpectedError: Component<{ error?: any }> = (props) => {
|
||||||
<h1>Oh, it is our fault.</h1>
|
<h1>Oh, it is our fault.</h1>
|
||||||
<p>There is an unexpected error in our app, and it's not your fault.</p>
|
<p>There is an unexpected error in our app, and it's not your fault.</p>
|
||||||
<p>
|
<p>
|
||||||
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.
|
repeatly, please report to us.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={() => (window.location.replace("/"))}>
|
<Button onClick={() => window.location.reload()}>Reload</Button>
|
||||||
Restart App
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
display: contents;
|
display: contents;
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
max-width: 100dvw;
|
max-width: 100dvw;
|
||||||
|
|
||||||
contain: layout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.StackedPage {
|
dialog.StackedPage {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { StaticRouter, type RouterProps } from "@solidjs/router";
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
createContext,
|
createContext,
|
||||||
|
createEffect,
|
||||||
createRenderEffect,
|
createRenderEffect,
|
||||||
createUniqueId,
|
createUniqueId,
|
||||||
Index,
|
Index,
|
||||||
|
@ -33,33 +34,8 @@ export type NewFrameOptions<T> = (T extends undefined
|
||||||
state?: T;
|
state?: T;
|
||||||
}
|
}
|
||||||
: { state: T }) & {
|
: { state: T }) & {
|
||||||
/**
|
|
||||||
* The new frame should replace the current frame.
|
|
||||||
*/
|
|
||||||
replace?: boolean;
|
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"];
|
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"];
|
animateClose?: StackFrame["animateClose"];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,10 +53,6 @@ export type Navigator<PushGuide = Record<string, any>> = {
|
||||||
|
|
||||||
const NavigatorContext = /* @__PURE__ */ createContext<Navigator>();
|
const NavigatorContext = /* @__PURE__ */ createContext<Navigator>();
|
||||||
|
|
||||||
export function useMaybeNavigator() {
|
|
||||||
return useContext(NavigatorContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the navigator of the {@link StackedRouter}.
|
* Get the navigator of the {@link StackedRouter}.
|
||||||
*
|
*
|
||||||
|
@ -91,7 +63,7 @@ export function useMaybeNavigator() {
|
||||||
* navigator to the type you need.
|
* navigator to the type you need.
|
||||||
*/
|
*/
|
||||||
export function useNavigator() {
|
export function useNavigator() {
|
||||||
const navigator = useMaybeNavigator();
|
const navigator = useContext(NavigatorContext);
|
||||||
|
|
||||||
if (!navigator) {
|
if (!navigator) {
|
||||||
throw new TypeError("not in available scope of StackedRouter");
|
throw new TypeError("not in available scope of StackedRouter");
|
||||||
|
@ -108,12 +80,8 @@ export type CurrentFrame = {
|
||||||
const CurrentFrameContext =
|
const CurrentFrameContext =
|
||||||
/* @__PURE__ */ createContext<Accessor<Readonly<CurrentFrame>>>();
|
/* @__PURE__ */ createContext<Accessor<Readonly<CurrentFrame>>>();
|
||||||
|
|
||||||
export function useMaybeCurrentFrame() {
|
|
||||||
return useContext(CurrentFrameContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCurrentFrame() {
|
export function useCurrentFrame() {
|
||||||
const frame = useMaybeCurrentFrame();
|
const frame = useContext(CurrentFrameContext);
|
||||||
|
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
throw new TypeError("not in available scope of StackedRouter");
|
throw new TypeError("not in available scope of StackedRouter");
|
||||||
|
@ -122,28 +90,6 @@ export function useCurrentFrame() {
|
||||||
return frame;
|
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(
|
function onDialogClick(
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
event: MouseEvent & { currentTarget: HTMLDialogElement },
|
event: MouseEvent & { currentTarget: HTMLDialogElement },
|
||||||
|
@ -243,10 +189,10 @@ const StackedRouter: Component<StackedRouterProps> = (oprops) => {
|
||||||
)! as HTMLDialogElement;
|
)! as HTMLDialogElement;
|
||||||
const createAnimation = lastFrame.animateClose ?? animateClose;
|
const createAnimation = lastFrame.animateClose ?? animateClose;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
element.classList.add("animating");
|
element.classList.add("animating")
|
||||||
const animation = createAnimation(element);
|
const animation = createAnimation(element);
|
||||||
animation.addEventListener("finish", () => {
|
animation.addEventListener("finish", () => {
|
||||||
element.classList.remove("animating");
|
element.classList.remove("animating")
|
||||||
onlyPopFrame(depth);
|
onlyPopFrame(depth);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,9 +3,11 @@ import {
|
||||||
Show,
|
Show,
|
||||||
onMount,
|
onMount,
|
||||||
type ParentComponent,
|
type ParentComponent,
|
||||||
createRenderEffect,
|
children,
|
||||||
|
Suspense,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { useDocumentTitle } from "../utils";
|
import { useDocumentTitle } from "../utils";
|
||||||
|
import { type mastodon } from "masto";
|
||||||
import Scaffold from "../material/Scaffold";
|
import Scaffold from "../material/Scaffold";
|
||||||
import {
|
import {
|
||||||
AppBar,
|
AppBar,
|
||||||
|
@ -21,11 +23,17 @@ import ProfileMenuButton from "./ProfileMenuButton";
|
||||||
import Tabs from "../material/Tabs";
|
import Tabs from "../material/Tabs";
|
||||||
import Tab from "../material/Tab";
|
import Tab from "../material/Tab";
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener";
|
import { makeEventListener } from "@solid-primitives/event-listener";
|
||||||
|
import BottomSheet, {
|
||||||
|
HERO as BOTTOM_SHEET_HERO,
|
||||||
|
} from "../material/BottomSheet";
|
||||||
import { $settings } from "../settings/stores";
|
import { $settings } from "../settings/stores";
|
||||||
import { useStore } from "@nanostores/solid";
|
import { useStore } from "@nanostores/solid";
|
||||||
|
import { HeroSourceProvider, type HeroSource } from "../platform/anim";
|
||||||
|
import { setCache as setTootBottomSheetCache } from "./TootBottomSheet";
|
||||||
import TrendTimelinePanel from "./TrendTimelinePanel";
|
import TrendTimelinePanel from "./TrendTimelinePanel";
|
||||||
import TimelinePanel from "./TimelinePanel";
|
import TimelinePanel from "./TimelinePanel";
|
||||||
import { useSessions } from "../masto/clients";
|
import { useSessions } from "../masto/clients";
|
||||||
|
import { useNavigator } from "../platform/StackedRouter";
|
||||||
|
|
||||||
const Home: ParentComponent = (props) => {
|
const Home: ParentComponent = (props) => {
|
||||||
let panelList: HTMLDivElement;
|
let panelList: HTMLDivElement;
|
||||||
|
@ -35,17 +43,29 @@ const Home: ParentComponent = (props) => {
|
||||||
const settings$ = useStore($settings);
|
const settings$ = useStore($settings);
|
||||||
|
|
||||||
const profiles = useSessions();
|
const profiles = useSessions();
|
||||||
|
const profile = () => {
|
||||||
|
const all = profiles();
|
||||||
|
if (all.length > 0) {
|
||||||
|
return all[0].account.inf;
|
||||||
|
}
|
||||||
|
};
|
||||||
const client = () => {
|
const client = () => {
|
||||||
const all = profiles();
|
const all = profiles();
|
||||||
return all?.[0]?.client;
|
return all?.[0]?.client;
|
||||||
};
|
};
|
||||||
|
const {pop, push} = useNavigator();
|
||||||
|
|
||||||
|
const [heroSrc, setHeroSrc] = createSignal<HeroSource>({});
|
||||||
|
const [panelOffset, setPanelOffset] = createSignal(0);
|
||||||
const prefetching = () => !settings$().prefetchTootsDisabled;
|
const prefetching = () => !settings$().prefetchTootsDisabled;
|
||||||
|
const [currentFocusOn, setCurrentFocusOn] = createSignal<HTMLElement[]>([]);
|
||||||
const [focusRange, setFocusRange] = createSignal([0, 0] as readonly [
|
const [focusRange, setFocusRange] = createSignal([0, 0] as readonly [
|
||||||
number,
|
number,
|
||||||
number,
|
number,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const child = children(() => props.children);
|
||||||
|
|
||||||
let scrollEventLockReleased = true;
|
let scrollEventLockReleased = true;
|
||||||
|
|
||||||
const recalculateTabIndicator = () => {
|
const recalculateTabIndicator = () => {
|
||||||
|
@ -82,17 +102,17 @@ const Home: ParentComponent = (props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestRecalculateTabIndicator = () => {
|
|
||||||
if (scrollEventLockReleased) {
|
|
||||||
requestAnimationFrame(recalculateTabIndicator);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
createRenderEffect(() => {
|
|
||||||
makeEventListener(window, "resize", requestRecalculateTabIndicator);
|
|
||||||
});
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
makeEventListener(panelList, "scroll", () => {
|
||||||
|
if (scrollEventLockReleased) {
|
||||||
|
requestAnimationFrame(recalculateTabIndicator);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
makeEventListener(window, "resize", () => {
|
||||||
|
if (scrollEventLockReleased) {
|
||||||
|
requestAnimationFrame(recalculateTabIndicator);
|
||||||
|
}
|
||||||
|
});
|
||||||
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`
|
css`
|
||||||
.tab-panel {
|
.tab-panel {
|
||||||
|
@ -165,7 +209,7 @@ const Home: ParentComponent = (props) => {
|
||||||
class="responsive"
|
class="responsive"
|
||||||
sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
|
sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
|
||||||
>
|
>
|
||||||
<Tabs>
|
<Tabs onFocusChanged={setCurrentFocusOn} offset={panelOffset()}>
|
||||||
<Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}>
|
<Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}>
|
||||||
Home
|
Home
|
||||||
</Tab>
|
</Tab>
|
||||||
|
@ -195,40 +239,48 @@ const Home: ParentComponent = (props) => {
|
||||||
</AppBar>
|
</AppBar>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TimeSourceProvider value={now}>
|
<HeroSourceProvider value={[heroSrc, setHeroSrc]}>
|
||||||
<Show when={!!client()}>
|
<TimeSourceProvider value={now}>
|
||||||
<div
|
<Show when={!!client()}>
|
||||||
class="panel-list"
|
<div class="panel-list" ref={panelList!}>
|
||||||
ref={panelList!}
|
<div class="tab-panel">
|
||||||
onScroll={requestRecalculateTabIndicator}
|
<div>
|
||||||
>
|
<TimelinePanel
|
||||||
<div class="tab-panel">
|
client={client()}
|
||||||
<div>
|
name="home"
|
||||||
<TimelinePanel
|
prefetch={prefetching()}
|
||||||
client={client()}
|
openFullScreenToot={openFullScreenToot}
|
||||||
name="home"
|
/>
|
||||||
prefetch={prefetching()}
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="tab-panel">
|
||||||
<div class="tab-panel">
|
<div>
|
||||||
<div>
|
<TrendTimelinePanel
|
||||||
<TrendTimelinePanel client={client()} />
|
client={client()}
|
||||||
|
openFullScreenToot={openFullScreenToot}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="tab-panel">
|
||||||
<div class="tab-panel">
|
<div>
|
||||||
<div>
|
<TimelinePanel
|
||||||
<TimelinePanel
|
client={client()}
|
||||||
client={client()}
|
name="public"
|
||||||
name="public"
|
prefetch={prefetching()}
|
||||||
prefetch={prefetching()}
|
openFullScreenToot={openFullScreenToot}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
</Show>
|
||||||
</div>
|
</TimeSourceProvider>
|
||||||
</Show>
|
<Suspense>
|
||||||
</TimeSourceProvider>
|
<BottomSheet open={!!child()} onClose={() => pop(1)}>
|
||||||
|
{child()}
|
||||||
|
</BottomSheet>
|
||||||
|
</Suspense>
|
||||||
|
</HeroSourceProvider>
|
||||||
</Scaffold>
|
</Scaffold>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { Refresh as RefreshIcon } from "@suid/icons-material";
|
||||||
import { CircularProgress } from "@suid/material";
|
import { CircularProgress } from "@suid/material";
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener";
|
import { makeEventListener } from "@solid-primitives/event-listener";
|
||||||
import { createVisibilityObserver } from "@solid-primitives/intersection-observer";
|
import { createVisibilityObserver } from "@solid-primitives/intersection-observer";
|
||||||
import { useMaybeIsFrameSuspended } from "../platform/StackedRouter";
|
|
||||||
|
|
||||||
const PullDownToRefresh: Component<{
|
const PullDownToRefresh: Component<{
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
@ -34,7 +33,6 @@ const PullDownToRefresh: Component<{
|
||||||
});
|
});
|
||||||
|
|
||||||
const rootVisible = obvx(() => rootElement);
|
const rootVisible = obvx(() => rootElement);
|
||||||
const isFrameSuspended = useMaybeIsFrameSuspended()
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!rootVisible()) setPullDown(0);
|
if (!rootVisible()) setPullDown(0);
|
||||||
|
@ -111,9 +109,6 @@ const PullDownToRefresh: Component<{
|
||||||
if (!rootVisible()) {
|
if (!rootVisible()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isFrameSuspended()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const element = props.linkedElement;
|
const element = props.linkedElement;
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
makeEventListener(element, "wheel", handleLinkedWheel);
|
makeEventListener(element, "wheel", handleLinkedWheel);
|
||||||
|
@ -164,9 +159,6 @@ const PullDownToRefresh: Component<{
|
||||||
if (!rootVisible()) {
|
if (!rootVisible()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isFrameSuspended()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const element = props.linkedElement;
|
const element = props.linkedElement;
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
makeEventListener(element, "touchmove", handleTouch);
|
makeEventListener(element, "touchmove", handleTouch);
|
||||||
|
|
|
@ -20,6 +20,12 @@ const TimelinePanel: Component<{
|
||||||
client: mastodon.rest.Client;
|
client: mastodon.rest.Client;
|
||||||
name: "home" | "public";
|
name: "home" | "public";
|
||||||
prefetch?: boolean;
|
prefetch?: boolean;
|
||||||
|
|
||||||
|
openFullScreenToot: (
|
||||||
|
toot: mastodon.v1.Status,
|
||||||
|
srcElement?: HTMLElement,
|
||||||
|
reply?: boolean,
|
||||||
|
) => void;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const [scrollLinked, setScrollLinked] = createSignal<HTMLElement>();
|
const [scrollLinked, setScrollLinked] = createSignal<HTMLElement>();
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,12 @@ import TootList from "./TootList.jsx";
|
||||||
|
|
||||||
const TrendTimelinePanel: Component<{
|
const TrendTimelinePanel: Component<{
|
||||||
client: mastodon.rest.Client;
|
client: mastodon.rest.Client;
|
||||||
|
|
||||||
|
openFullScreenToot: (
|
||||||
|
toot: mastodon.v1.Status,
|
||||||
|
srcElement?: HTMLElement,
|
||||||
|
reply?: boolean,
|
||||||
|
) => void;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const [scrollLinked, setScrollLinked] = createSignal<HTMLElement>();
|
const [scrollLinked, setScrollLinked] = createSignal<HTMLElement>();
|
||||||
const [tl, snapshot, { refetch: refetchTimeline }] = createTimelineSnapshot(
|
const [tl, snapshot, { refetch: refetchTimeline }] = createTimelineSnapshot(
|
||||||
|
|
Loading…
Reference in a new issue