TrendTimelinePanel: improved error handling

This commit is contained in:
thislight 2024-10-10 20:32:54 +08:00
parent f16290e610
commit cd38eeb89a
No known key found for this signature in database
GPG key ID: A50F9451AC56A63E
3 changed files with 69 additions and 53 deletions

View file

@ -1,40 +1,59 @@
import { Button } from '@suid/material'; import { Button } from "@suid/material";
import {Component, createResource} from 'solid-js' import { Component, createResource } from "solid-js";
import { css } from 'solid-styled'; import { css } from "solid-styled";
const UnexpectedError: Component<{error?: any}> = (props) => { const UnexpectedError: Component<{ error?: any }> = (props) => {
const [errorMsg] = createResource(
() => props.error,
async (err) => {
if (err instanceof Error) {
const mod = await import("stacktrace-js");
try {
const stacktrace = await mod.fromError(err);
const strackMsg = stacktrace
.map(
(entry) =>
`${entry.functionName ?? "<unknown>"}@${entry.fileName}:(${entry.lineNumber}:${entry.columnNumber})`,
)
.join("\n");
return `${err.name}: ${err.message}\n${strackMsg}`;
} catch (reason) {
return `<failed to build the stacktrace of "${err}"...>\n${reason}`;
}
}
const [errorMsg] = createResource(() => props.error, async (err) => { return err.toString();
if (err instanceof Error) { },
const mod = await import('stacktrace-js') );
const stacktrace = await mod.fromError(err)
const strackMsg = stacktrace.map(entry => `${entry.functionName ?? "<unknown>"}@${entry.fileName}:(${entry.lineNumber}:${entry.columnNumber})`).join('\n')
return `${err.name}: ${err.message}\n${strackMsg}`
}
return err.toString()
})
css` css`
main { main {
padding: calc(var(--safe-area-inset-top) + 20px) calc(var(--safe-area-inset-right) + 20px) calc(var(--safe-area-inset-bottom) + 20px) calc(var(--safe-area-inset-left) + 20px); padding: calc(var(--safe-area-inset-top) + 20px)
} calc(var(--safe-area-inset-right) + 20px)
` calc(var(--safe-area-inset-bottom) + 20px)
calc(var(--safe-area-inset-left) + 20px);
}
`;
return <main> return (
<h1>Oh, it is our fault.</h1> <main>
<p>There is an unexpected error in our app, and it's not your fault.</p> <h1>Oh, it is our fault.</h1>
<p>You can reload to see if this guy is gone. If you meet this guy repeatly, please report to us.</p> <p>There is an unexpected error in our app, and it's not your fault.</p>
<div> <p>
<Button onClick={() => window.location.reload()}>Reload</Button> You can reload to see if this guy is gone. If you meet this guy
</div> repeatly, please report to us.
<details> </p>
<summary>{errorMsg.loading ? 'Generating ' : " "}Technical Infomation (Bring to us if you report the problem)</summary> <div>
<pre> <Button onClick={() => window.location.reload()}>Reload</Button>
{errorMsg()} </div>
</pre> <details>
</details> <summary>
</main> {errorMsg.loading ? "Generating " : " "}Technical Infomation
} </summary>
<pre>{errorMsg()}</pre>
</details>
</main>
);
};
export default UnexpectedError; export default UnexpectedError;

View file

@ -1,5 +1,5 @@
import { type mastodon } from "masto"; import { type mastodon } from "masto";
import { Accessor, createEffect, createResource } from "solid-js"; import { Accessor, catchError, createEffect, createResource } from "solid-js";
import { createStore } from "solid-js/store"; import { createStore } from "solid-js/store";
type TimelineFetchTips = { type TimelineFetchTips = {
@ -114,7 +114,7 @@ export function createTimelineSnapshot(
timeline: Accessor<Timeline>, timeline: Accessor<Timeline>,
limit: Accessor<number>, limit: Accessor<number>,
) { ) {
const [shot, {refetch}] = createResource( const [shot, { refetch }] = createResource(
() => [timeline(), limit()] as const, () => [timeline(), limit()] as const,
async ([tl, limit]) => { async ([tl, limit]) => {
const ls = await tl.list({ limit }).next(); const ls = await tl.list({ limit }).next();
@ -125,7 +125,7 @@ export function createTimelineSnapshot(
const [snapshot, setSnapshot] = createStore([] as mastodon.v1.Status[][]); const [snapshot, setSnapshot] = createStore([] as mastodon.v1.Status[][]);
createEffect(() => { createEffect(() => {
const nls = shot(); const nls = catchError(shot, (e) => console.error(e));
if (!nls) return; if (!nls) return;
const ols = Array.from(snapshot); const ols = Array.from(snapshot);
// The algorithm below assumes the snapshot is not changing // The algorithm below assumes the snapshot is not changing
@ -154,10 +154,14 @@ export function createTimelineSnapshot(
} }
}); });
return [snapshot, shot, { return [
refetch, snapshot,
mutate: setSnapshot shot,
}] as const; {
refetch,
mutate: setSnapshot,
},
] as const;
} }
export function createTimeline(timeline: Accessor<Timeline>) { export function createTimeline(timeline: Accessor<Timeline>) {

View file

@ -3,7 +3,6 @@ import {
For, For,
onCleanup, onCleanup,
createSignal, createSignal,
Show,
untrack, untrack,
Match, Match,
Switch as JsSwitch, Switch as JsSwitch,
@ -11,7 +10,7 @@ import {
createSelector, createSelector,
} from "solid-js"; } from "solid-js";
import { type mastodon } from "masto"; import { type mastodon } from "masto";
import { Button, LinearProgress } from "@suid/material"; import { Button } from "@suid/material";
import { createTimelineSnapshot } from "../masto/timelines.js"; import { createTimelineSnapshot } from "../masto/timelines.js";
import { vibrate } from "../platform/hardware.js"; import { vibrate } from "../platform/hardware.js";
import PullDownToRefresh from "./PullDownToRefresh.jsx"; import PullDownToRefresh from "./PullDownToRefresh.jsx";
@ -40,7 +39,7 @@ const TrendTimelinePanel: Component<{
const tlEndObserver = new IntersectionObserver(() => { const tlEndObserver = new IntersectionObserver(() => {
if (untrack(() => props.prefetch) && !snapshot.loading) if (untrack(() => props.prefetch) && !snapshot.loading)
refetchTimeline({ direction: "old" }); refetchTimeline();
}); });
onCleanup(() => tlEndObserver.disconnect()); onCleanup(() => tlEndObserver.disconnect());
@ -143,26 +142,19 @@ const TrendTimelinePanel: Component<{
</div> </div>
<div ref={(e) => tlEndObserver.observe(e)}></div> <div ref={(e) => tlEndObserver.observe(e)}></div>
<Show when={snapshot.loading}>
<div
class="loading-line"
style={{
width: "100%",
}}
>
<LinearProgress />
</div>
</Show>
<div <div
style={{ style={{
display: "flex", display: "flex",
padding: "20px 0 calc(20px + var(--safe-area-inset-bottom, 0px))", padding: "20px 0 calc(20px + var(--safe-area-inset-bottom, 0px))",
"align-items": "center", "align-items": "center",
"justify-content": "center", "justify-content": "center",
"flex-flow": "column",
gap: "20px"
}} }}
> >
<JsSwitch> <JsSwitch>
<Match when={snapshot.error}> <Match when={snapshot.error}>
<p>{`Oops: ${snapshot.error}`}</p>
<Button <Button
variant="contained" variant="contained"
onClick={[refetchTimeline, undefined]} onClick={[refetchTimeline, undefined]}
@ -170,6 +162,7 @@ const TrendTimelinePanel: Component<{
> >
Retry Retry
</Button> </Button>
</Match> </Match>
<Match when={true}> <Match when={true}>
<Button <Button