diff --git a/src/App.tsx b/src/App.tsx
index abdff31..028a234 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -10,7 +10,7 @@ import {
lazy,
onCleanup,
} from "solid-js";
-import { useRootTheme } from "./material/theme.js";
+import { useRootTheme } from "./material/mui.js";
import {
Provider as ClientProvider,
createMastoClientFor,
@@ -149,7 +149,7 @@ const App: Component = () => {
return ;
}}
>
-
+
= {
- list(params?: T): mastodon.Paginator;
+type Timeline = {
+ list(params: {
+ /** Return results older than this ID. */
+ readonly maxId?: string;
+ /** Return results newer than this ID. */
+ readonly sinceId?: string;
+ /** Get a list of items with ID greater than this value excluding this ID */
+ readonly minId?: string;
+ /** Maximum number of results to return per page. Defaults to 40. NOTE: Pagination is done with the Link header from the response. */
+ readonly limit?: number;
+ }): mastodon.Paginator;
};
-type TimelineParamsOf = T extends Timeline ? P : never;
-
-export function createTimelineSnapshot<
- T extends Timeline,
->(timeline: Accessor, limit: Accessor) {
+export function createTimelineSnapshot(
+ timeline: Accessor,
+ limit: Accessor,
+) {
const [shot, { refetch }] = createResource(
() => [timeline(), limit()] as const,
async ([tl, limit]) => {
@@ -72,13 +80,13 @@ export function createTimelineSnapshot<
export type TimelineFetchDirection = mastodon.Direction;
-export type TimelineChunk = {
- tl: Timeline;
+export type TimelineChunk = {
+ tl: Timeline;
rebuilt: boolean;
chunk: readonly mastodon.v1.Status[];
done?: boolean;
direction: TimelineFetchDirection;
- params: T;
+ limit: number;
};
type TreeNode = {
@@ -100,21 +108,21 @@ function collectPath(node: TreeNode) {
return path;
}
-function createTimelineChunk>(
- timeline: Accessor,
- params: Accessor>,
+function createTimelineChunk(
+ timeline: Accessor,
+ limit: Accessor,
) {
let vpMaxId: string | undefined, vpMinId: string | undefined;
const fetchExtendingPage = async (
- tl: T,
+ tl: Timeline,
direction: TimelineFetchDirection,
- params: TimelineParamsOf,
+ limit: number,
) => {
switch (direction) {
case "next": {
const page = await tl
- .list({ ...params, sinceId: vpMaxId })
+ .list({ limit, sinceId: vpMaxId })
.setDirection(direction)
.next();
if ((page.value?.length ?? 0) > 0) {
@@ -125,7 +133,7 @@ function createTimelineChunk 0) {
@@ -137,11 +145,11 @@ function createTimelineChunk [timeline(), params()] as const,
+ () => [timeline(), limit()] as const,
async (
- [tl, params],
+ [tl, limit],
info: ResourceFetcherInfo<
- Readonly>>,
+ Readonly,
TimelineFetchDirection
>,
) => {
@@ -152,26 +160,27 @@ function createTimelineChunk,
->(timeline: Accessor, params: Accessor>) {
+export function createTimeline(
+ timeline: Accessor,
+ limit: Accessor,
+) {
const lookup = new ReactiveMap>();
const [threads, setThreads] = createStore([] as mastodon.v1.Status["id"][]);
- const [chunk, { refetch }] = createTimelineChunk(timeline, params);
+ const [chunk, { refetch }] = createTimelineChunk(timeline, limit);
createEffect(() => {
const chk = catchError(chunk, (e) => console.error(e));
diff --git a/src/material/Menu.tsx b/src/material/Menu.tsx
deleted file mode 100644
index 365d090..0000000
--- a/src/material/Menu.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { useWindowSize } from "@solid-primitives/resize-observer";
-import { MenuList } from "@suid/material";
-import {
- createEffect,
- createSignal,
- type JSX,
- type ParentComponent,
-} from "solid-js";
-import { ANIM_CURVE_STD } from "./theme";
-
-type Props = {
- open?: boolean;
- onClose?: JSX.EventHandlerUnion;
- anchor: () => DOMRect;
-};
-
-function adjustMenuPosition(
- rect: DOMRect,
- [left, top]: [number, number],
- { width, height }: { width: number; height: number },
-) {
- const ntop = rect.bottom > height ? top - (rect.bottom - height) : top;
- const nleft = rect.right > width ? left - (rect.right - width) : left;
- return [nleft, ntop] as [number, number];
-}
-
-const Menu: ParentComponent = (props) => {
- let root: HTMLDialogElement;
- const [pos, setPos] = createSignal<[number, number]>([0, 0]);
- const windowSize = useWindowSize();
-
- createEffect(() => {
- if (props.open) {
- const a = props.anchor();
- if (!root.open) {
- root.showModal();
- const rend = root.getBoundingClientRect();
-
- setPos(adjustMenuPosition(rend, [a.left, a.top], windowSize));
-
- const overflow = root.style.overflow;
- root.style.overflow = "hidden";
- const duration = (rend.height / 1600) * 1000;
- const easing = ANIM_CURVE_STD;
- const animation = root.animate(
- {
- height: [`${rend.height / 2}px`, `${rend.height}px`],
- width: [`${rend.width / 4 * 3}px`, `${rend.width}px`],
- },
- {
- duration,
- easing,
- },
- );
- animation.addEventListener(
- "finish",
- () => (root.style.overflow = overflow),
- );
- } else {
- setPos(
- adjustMenuPosition(
- root.getBoundingClientRect(),
- [a.left, a.top],
- windowSize,
- ),
- );
- }
- } else {
- animateClose();
- }
- });
-
- const animateClose = () => {
- const rend = root.getBoundingClientRect();
- const overflow = root.style.overflow;
- root.style.overflow = "hidden";
- const animation = root.animate(
- {
- height: [`${rend.height}px`, `${rend.height / 2}px`],
- width: [`${rend.width}px`, `${rend.width / 4 * 3}px`],
- },
- {
- duration: (rend.height / 2 / 1600) * 1000,
- easing: ANIM_CURVE_STD,
- },
- );
- animation.addEventListener("finish", () => {
- root.style.overflow = overflow;
- root.close();
- });
- };
-
- return (
-
- );
-};
-
-export default Menu;
diff --git a/src/material/theme.ts b/src/material/mui.ts
similarity index 57%
rename from src/material/theme.ts
rename to src/material/mui.ts
index a7c2c65..6527cc4 100644
--- a/src/material/theme.ts
+++ b/src/material/mui.ts
@@ -2,9 +2,6 @@ import { Theme, createTheme } from "@suid/material/styles";
import { deepPurple, amber } from "@suid/material/colors";
import { Accessor } from "solid-js";
-/**
- * The MUI theme.
- */
export function useRootTheme(): Accessor {
return () =>
createTheme({
@@ -18,8 +15,3 @@ export function useRootTheme(): Accessor {
},
});
}
-
-export const ANIM_CURVE_STD = "cubic-bezier(0.4, 0, 0.2, 1)";
-export const ANIM_CURVE_DECELERATION = "cubic-bezier(0, 0, 0.2, 1)";
-export const ANIM_CURVE_ACELERATION = "cubic-bezier(0.4, 0, 1, 1)";
-export const ANIM_CURVE_SHARP = "cubic-bezier(0.4, 0, 0.6, 1)";
diff --git a/src/platform/share.tsx b/src/platform/share.tsx
index 7c98cb9..5f80dc0 100644
--- a/src/platform/share.tsx
+++ b/src/platform/share.tsx
@@ -16,7 +16,7 @@ import {
import { Close as CloseIcon, ContentCopy } from "@suid/icons-material";
import { Title } from "../material/typography";
import { render } from "solid-js/web";
-import { useRootTheme } from "../material/theme";
+import { useRootTheme } from "../material/mui";
const ShareBottomSheet: Component<{
data?: ShareData;
diff --git a/src/profiles/Profile.tsx b/src/profiles/Profile.tsx
index 64cf0b5..cdea9d5 100644
--- a/src/profiles/Profile.tsx
+++ b/src/profiles/Profile.tsx
@@ -19,8 +19,8 @@ import { useWindowSize } from "@solid-primitives/resize-observer";
import { css } from "solid-styled";
import { createTimeline } from "../masto/timelines";
import TootList from "../timelines/TootList";
+import { createIntersectionObserver } from "@solid-primitives/intersection-observer";
import { createTimeSource, TimeSourceProvider } from "../platform/timesrc";
-import TootFilterButton from "./TootFilterButton";
const Profile: Component = () => {
const navigate = useNavigate();
@@ -48,6 +48,7 @@ const Profile: Component = () => {
threshold: 0.1,
},
);
+
onCleanup(() => obx.disconnect());
const [profile] = createResource(
@@ -57,25 +58,16 @@ const Profile: Component = () => {
},
);
- const [recentTootFilter, setRecentTootFilter] = createSignal({
- boost: true,
- reply: true,
- original: true,
- });
-
const [recentToots] = createTimeline(
() => session().client.v1.accounts.$select(params.id).statuses,
- () => {
- const { boost, reply } = recentTootFilter();
- return { limit: 20, excludeReblogs: !boost, excludeReplies: !reply };
- },
+ () => 20,
);
const bannerImg = () => profile()?.header;
const avatarImg = () => profile()?.avatar;
const displayName = () =>
resolveCustomEmoji(profile()?.displayName || "", profile()?.emojis ?? []);
- const fullUsername = () => `@${profile()?.acct ?? ""}`; // TODO: full user name
+ const fullUsername = () => `@${profile()?.acct ?? "..."}`; // TODO: full user name
const description = () => profile()?.note;
css`
@@ -134,9 +126,7 @@ const Profile: Component = () => {
variant="dense"
sx={{
display: "flex",
- color: scrolledPastBanner()
- ? undefined
- : bannerSampledColors()?.text,
+ color: scrolledPastBanner() ? undefined : bannerSampledColors()?.text,
paddingTop: "var(--safe-area-inset-top)",
}}
>
@@ -248,19 +238,6 @@ const Profile: Component = () => {
-
-
-
-
> = {
- options: Filters;
- applied: Record;
- disabledKeys?: (keyof Filters)[];
-
- onApply(value: Record): void;
-};
-
-function TootFilterButton>(props: Props) {
- const buttonId = createUniqueId();
- const [open, setOpen] = createSignal(false);
-
- const getTextForMultipleEntities = (texts: string[]) => {
- switch (texts.length) {
- case 0:
- return "Nothing";
- case 1:
- return texts[0];
- case 2:
- return `${texts[0]} and ${texts[1]}`;
- case 3:
- return `${texts[0]}, ${texts[1]} and ${texts[2]}`;
- default:
- return `${texts[0]} and ${texts.length - 1} other${texts.length > 2 ? "s" : ""}`;
- }
- };
-
- const optionKeys = () => Object.keys(props.options);
-
- const appliedKeys = createMemo(() => {
- const applied = props.applied;
- return optionKeys().filter((k) => applied[k]);
- });
-
- const text = () => {
- const keys = optionKeys();
- const napplied = appliedKeys().length;
- switch (napplied) {
- case keys.length:
- return "All";
- default:
- return getTextForMultipleEntities(
- appliedKeys().map((k) => props.options[k]),
- );
- }
- };
-
- const toggleKey = (key: keyof F) => {
- props.onApply(
- Object.assign({}, props.applied, {
- [key]: !props.applied[key],
- }),
- );
- };
-
- return (
- <>
-
-
- >
- );
-}
-
-export default TootFilterButton;
diff --git a/src/timelines/TimelinePanel.tsx b/src/timelines/TimelinePanel.tsx
index 5c3e150..b1a014d 100644
--- a/src/timelines/TimelinePanel.tsx
+++ b/src/timelines/TimelinePanel.tsx
@@ -32,7 +32,7 @@ const TimelinePanel: Component<{
const [timeline, snapshot, { refetch: refetchTimeline }] = createTimeline(
() => props.client.v1.timelines[props.name],
- () => ({limit: 20}),
+ () => 20,
);
const [expandedThreadId, setExpandedThreadId] = createSignal();
const [typing, setTyping] = createSignal(false);
diff --git a/vite.config.ts b/vite.config.ts
index 466d139..7881869 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -64,7 +64,7 @@ export default defineConfig(({ mode }) => ({
strategies: "injectManifest",
registerType: "autoUpdate",
devOptions: {
- enabled: !["production", "staging"].includes(mode),
+ enabled: mode === "staging" || mode === "dev",
},
srcDir: "src/serviceworker",
filename: "main.ts",