diff --git a/bun.lockb b/bun.lockb
index 337f675..be11679 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/index.html b/index.html
index b97ae88..2712433 100644
--- a/index.html
+++ b/index.html
@@ -7,7 +7,6 @@
Tutu
-
diff --git a/package.json b/package.json
index 32a9d10..54e32ca 100644
--- a/package.json
+++ b/package.json
@@ -16,22 +16,22 @@
"devDependencies": {
"@suid/vite-plugin": "^0.2.0",
"@types/hammerjs": "^2.0.45",
- "postcss": "^8.4.41",
- "prettier": "^3.3.3",
- "typescript": "^5.5.4",
- "vite": "^5.4.0",
+ "postcss": "^8.4.39",
+ "prettier": "^3.3.2",
+ "typescript": "^5.5.2",
+ "vite": "^5.3.2",
"vite-plugin-package-version": "^1.1.0",
- "vite-plugin-pwa": "^0.20.1",
+ "vite-plugin-pwa": "^0.20.0",
"vite-plugin-solid": "^2.10.2",
"vite-plugin-solid-styled": "^0.11.1",
- "wrangler": "^3.70.0"
+ "wrangler": "^3.64.0"
},
"dependencies": {
"@nanostores/persistent": "^0.9.1",
"@nanostores/solid": "^0.4.2",
"@solid-primitives/event-listener": "^2.3.3",
"@solid-primitives/intersection-observer": "^2.1.6",
- "@solid-primitives/resize-observer": "^2.0.26",
+ "@solid-primitives/resize-observer": "^2.0.25",
"@solidjs/router": "^0.11.5",
"@suid/icons-material": "^0.7.0",
"@suid/material": "^0.16.0",
@@ -40,10 +40,9 @@
"hammerjs": "^2.0.8",
"masto": "^6.8.0",
"nanostores": "^0.9.5",
- "solid-js": "^1.8.20",
+ "solid-js": "^1.8.18",
"solid-styled": "^0.11.1",
- "stacktrace-js": "^2.0.2",
- "web-animations-js": "^2.3.2"
+ "stacktrace-js": "^2.0.2"
},
"packageManager": "bun@1.1.21"
}
diff --git a/src/App.tsx b/src/App.tsx
index c3573a5..1522795 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -22,7 +22,6 @@ const AccountMastodonOAuth2Callback = lazy(
);
const TimelineHome = lazy(() => import("./timelines/Home.js"));
const Settings = lazy(() => import("./settings/Settings.js"));
-const TootBottomSheet = lazy(() => import("./timelines/TootBottomSheet.js"));
const Routing: Component = () => {
return (
@@ -30,7 +29,6 @@ const Routing: Component = () => {
-
@@ -55,7 +53,7 @@ const App: Component = () => {
);
});
- const UnexpectedError = lazy(() => import("./UnexpectedError.js"));
+const UnexpectedError = lazy(() => import("./UnexpectedError.js"))
return (
) {
+ return createResource(
+ client,
+ (client) => {
+ return client.v1.accounts.verifyCredentials();
+ },
+ {
+ name: "MastodonAccountProfile",
+ },
+ );
+}
+
export function useSignedInProfiles() {
const sessions = useSessions();
const [accessor, tools] = createResource(sessions, async (all) => {
@@ -12,11 +24,11 @@ export function useSignedInProfiles() {
});
return [
() => {
- const value = accessor();
- if (!value) {
+ if (accessor.loading) {
+ accessor();
return sessions().map((x) => ({ ...x, inf: x.account.inf }));
}
- return value;
+ return accessor();
},
tools,
] as const;
diff --git a/src/masto/timelines.ts b/src/masto/timelines.ts
index 917ecf7..55d1276 100644
--- a/src/masto/timelines.ts
+++ b/src/masto/timelines.ts
@@ -14,49 +14,55 @@ type Timeline = {
};
export function useTimeline(timeline: Accessor) {
+ let minId: string | undefined;
+ let maxId: string | undefined;
let otl: Timeline | undefined;
- let npager: mastodon.Paginator | undefined;
- let opager: mastodon.Paginator | undefined;
+ const idSet = new Set();
const [snapshot, { refetch }] = createResource<
- {
- records: mastodon.v1.Status[];
- direction: "new" | "old";
- tlChanged: boolean;
- },
+ { records: mastodon.v1.Status[]; direction: "old" | "new" },
[Timeline],
TimelineFetchTips | undefined
>(
() => [timeline()] as const,
async ([tl], info) => {
- let tlChanged = false;
if (otl !== tl) {
- console.debug("timeline reset");
- npager = opager = undefined;
+ minId = undefined;
+ maxId = undefined;
+ idSet.clear();
otl = tl;
- tlChanged = true;
}
const direction =
typeof info.refetching !== "boolean"
- ? (info.refetching?.direction ?? "old")
+ ? info.refetching?.direction
: "old";
+ const pager = await tl.list(
+ direction === "old"
+ ? {
+ maxId: minId,
+ }
+ : {
+ minId: maxId,
+ },
+ );
+ const diff = pager.filter((x) => !idSet.has(x.id));
+ for (const v of diff.map((x) => x.id)) {
+ idSet.add(v);
+ }
if (direction === "old") {
- if (!opager) {
- opager = tl.list({}).setDirection("next");
+ minId = pager[pager.length - 1]?.id;
+ if (!maxId && pager.length > 0) {
+ maxId = pager[0].id;
}
- const next = await opager.next();
return {
- direction,
- records: next.value ?? [],
- end: next.done,
- tlChanged,
+ direction: "old" as const,
+ records: diff,
};
} else {
- if (!npager) {
- npager = tl.list({}).setDirection("prev");
+ maxId = pager.length > 0 ? pager[0].id : undefined;
+ if (!minId && pager.length > 0) {
+ minId = pager[pager.length - 1]?.id;
}
- const next = await npager.next();
- const page = next.value ?? [];
- return { direction, records: page, end: next.done, tlChanged };
+ return { direction: "new" as const, records: diff };
}
},
);
@@ -66,10 +72,7 @@ export function useTimeline(timeline: Accessor) {
createEffect(() => {
const shot = snapshot();
if (!shot) return;
- const { direction, records, tlChanged } = shot;
- if (tlChanged) {
- setStore(() => []);
- }
+ const { direction, records } = shot;
if (direction == "new") {
setStore((x) => [...records, ...x]);
} else if (direction == "old") {
diff --git a/src/masto/toot.ts b/src/masto/toot.ts
index 2651577..85fd817 100644
--- a/src/masto/toot.ts
+++ b/src/masto/toot.ts
@@ -1,4 +1,3 @@
-import { cache } from "@solidjs/router";
import type { mastodon } from "masto";
import { createRenderEffect, createResource, type Accessor } from "solid-js";
diff --git a/src/material/BottomSheet.module.css b/src/material/BottomSheet.module.css
index 237d0bd..e48ce13 100644
--- a/src/material/BottomSheet.module.css
+++ b/src/material/BottomSheet.module.css
@@ -11,14 +11,10 @@
border-radius: 2px;
overscroll-behavior: contain;
- &::backdrop {
- background-color: black;
- opacity: 0.5;
- }
-
box-shadow: var(--tutu-shadow-e16);
:global(.MuiToolbar-root) > :global(.MuiButtonBase-root):first-child {
+ color: white;
margin-left: -0.5em;
margin-right: 24px;
}
@@ -34,9 +30,4 @@
max-height: 100%;
}
}
-
- &.animated {
- position: absolute;
- transform: none;
- }
}
diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx
index b5b93cc..eeefcc5 100644
--- a/src/material/BottomSheet.tsx
+++ b/src/material/BottomSheet.tsx
@@ -1,115 +1,22 @@
-import {
- createEffect,
- createRenderEffect,
- onCleanup,
- onMount,
- startTransition,
- useTransition,
- type ParentComponent,
-} from "solid-js";
+import { createEffect, type ParentComponent } from "solid-js";
import styles from "./BottomSheet.module.css";
-import { useHeroSignal } from "../platform/anim";
export type BottomSheetProps = {
open?: boolean;
};
-export const HERO = Symbol("BottomSheet Hero Symbol");
-
-function composeAnimationFrame(
- {
- top,
- left,
- height,
- width,
- }: Record<"top" | "left" | "height" | "width", number>,
- x: Record,
-) {
- return {
- top: `${top}px`,
- left: `${left}px`,
- height: `${height}px`,
- width: `${width}px`,
- ...x,
- };
-}
-
-const MOVE_SPEED = 1400; // 1400px/s, bottom sheet is big and a bit heavier than small papers
-
const BottomSheet: ParentComponent = (props) => {
let element: HTMLDialogElement;
- let animation: Animation | undefined;
- const hero = useHeroSignal(HERO);
-
- const [pending] = useTransition()
createEffect(() => {
if (props.open) {
- if (!element.open && !pending()) {
- animatedOpen();
+ if (!element.open) {
+ element.showModal();
}
} else {
if (element.open) {
- animatedClose();
- }
- }
- });
-
- const animatedClose = () => {
- const endRect = hero();
- if (endRect) {
- const startRect = element.getBoundingClientRect();
- const animation = animateHero(startRect, endRect, element, true);
- const onClose = () => {
element.close();
- };
- animation.addEventListener("finish", onClose);
- animation.addEventListener("cancel", onClose);
- } else {
- element.close();
- }
- };
-
- const animatedOpen = () => {
- element.showModal();
- const startRect = hero();
- if (!startRect) return;
- const endRect = element.getBoundingClientRect();
- animateHero(startRect, endRect, element);
- };
-
- const animateHero = (
- startRect: DOMRect,
- endRect: DOMRect,
- element: HTMLElement,
- reserve?: boolean,
- ) => {
- const easing = "cubic-bezier(0.4, 0, 0.2, 1)";
- element.classList.add(styles.animated);
- const distance = Math.sqrt(
- Math.pow(Math.abs(startRect.top - endRect.top), 2) +
- Math.pow(Math.abs(startRect.left - startRect.top), 2),
- );
- const duration = (distance / MOVE_SPEED) * 1000;
- animation = element.animate(
- [
- composeAnimationFrame(startRect, { opacity: reserve ? 1 : 0.5 }),
- composeAnimationFrame(endRect, { opacity: reserve ? 0.5 : 1 }),
- ],
- { easing, duration },
- );
- const onAnimationEnd = () => {
- element.classList.remove(styles.animated);
- animation = undefined;
- };
- animation.addEventListener("finish", onAnimationEnd);
- animation.addEventListener("cancel", onAnimationEnd);
- return animation;
- };
-
- onCleanup(() => {
- if (animation) {
- animation.cancel();
+ }
}
});
diff --git a/src/platform/anim.ts b/src/platform/anim.ts
index 0a937e2..75446ef 100644
--- a/src/platform/anim.ts
+++ b/src/platform/anim.ts
@@ -1,49 +1,13 @@
-import {
- createContext,
- createRenderEffect,
- createSignal,
- untrack,
- useContext,
- type Accessor,
- type Signal,
-} from "solid-js";
+import { createContext, useContext, type Accessor } from "solid-js";
export type HeroSource = {
- [key: string | symbol | number]: DOMRect | undefined;
+ [key: string | symbol | number]: HTMLElement | undefined;
};
-const HeroSourceContext = createContext>(/* __@PURE__ */undefined);
+const HeroSourceContext = createContext>(() => ({}));
export const HeroSourceProvider = HeroSourceContext.Provider;
-function useHeroSource() {
+export function useHeroSource() {
return useContext(HeroSourceContext);
}
-
-/**
- * Use hero value for the {@link key}.
- */
-export function useHeroSignal(
- key: string | symbol | number,
-): Accessor {
- const source = useHeroSource();
- if (source) {
- const [get, set] = createSignal();
-
- createRenderEffect(() => {
- const value = source[0]();
- if (value[key]) {
- set(value[key]);
- source[1]((x) => {
- const cpy = Object.assign({}, x);
- delete cpy[key];
- return cpy;
- });
- }
- });
-
- return get;
- } else {
- return () => undefined;
- }
-}
diff --git a/src/platform/host.ts b/src/platform/host.ts
deleted file mode 100644
index f1a2e27..0000000
--- a/src/platform/host.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export function isiOS() {
- return [
- 'iPad Simulator',
- 'iPhone Simulator',
- 'iPod Simulator',
- 'iPad',
- 'iPhone',
- 'iPod'
- ].includes(navigator.platform)
- // iPad on iOS 13 detection
- || (navigator.userAgent.includes("Mac") && "ontouchend" in document)
-}
\ No newline at end of file
diff --git a/src/platform/polyfills.ts b/src/platform/polyfills.ts
deleted file mode 100644
index 6897af7..0000000
--- a/src/platform/polyfills.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-//! This module has side effect.
-//! It recommended to include the module by