+1
src/auto-imports.d.ts
+1
src/auto-imports.d.ts
···
19
19
const IconMaterialSymbolsTag: typeof import('~icons/material-symbols/tag.jsx').default
20
20
const IconMdiAccountCircle: typeof import('~icons/mdi/account-circle.jsx').default
21
21
const IconMdiAccountPlus: typeof import('~icons/mdi/account-plus.jsx').default
22
+
const IconMdiCheck: typeof import('~icons/mdi/check.jsx').default
22
23
const IconMdiMessageReplyTextOutline: typeof import('~icons/mdi/message-reply-text-outline.jsx').default
23
24
const IconMdiPencilOutline: typeof import('~icons/mdi/pencil-outline.jsx').default
24
25
}
+128
-23
src/routes/notifications.tsx
+128
-23
src/routes/notifications.tsx
···
6
6
7
7
import defaultpfp from "~/../public/favicon.png";
8
8
import { Header } from "~/components/Header";
9
-
import { ReusableTabRoute, useReusableTabScrollRestore } from "~/components/ReusableTabRoute";
9
+
import {
10
+
ReusableTabRoute,
11
+
useReusableTabScrollRestore,
12
+
} from "~/components/ReusableTabRoute";
10
13
import {
11
14
MdiCardsHeartOutline,
12
15
MdiCommentOutline,
···
17
20
import {
18
21
constellationURLAtom,
19
22
imgCDNAtom,
23
+
postInteractionsFiltersAtom,
20
24
} from "~/utils/atoms";
21
25
import {
22
26
useInfiniteQueryAuthorFeed,
···
102
106
);
103
107
}, [infiniteMentionsData]);
104
108
105
-
106
109
useReusableTabScrollRestore("Notifications");
107
110
108
111
if (isLoading) return <LoadingState text="Loading mentions..." />;
···
169
172
170
173
useReusableTabScrollRestore("Notifications");
171
174
172
-
if (isLoading) return <LoadingState text="Loading mentions..." />;
175
+
if (isLoading) return <LoadingState text="Loading follows..." />;
173
176
if (isError) return <ErrorState error={error} />;
174
177
175
-
if (!followsAturis?.length) return <EmptyState text="No mentions yet." />;
178
+
if (!followsAturis?.length) return <EmptyState text="No follows yet." />;
176
179
177
180
return (
178
181
<>
···
224
227
225
228
useReusableTabScrollRestore("Notifications");
226
229
230
+
const [filters] = useAtom(postInteractionsFiltersAtom);
231
+
const empty = (!filters.likes && !filters.quotes && !filters.replies && !filters.reposts);
232
+
227
233
return (
228
234
<>
229
-
{posts.map((m) => (
235
+
<PostInteractionsFilterChipBar />
236
+
{!empty && posts.map((m) => (
230
237
<PostInteractionsItem key={m.uri} uri={m.uri} />
231
238
))}
232
239
···
243
250
);
244
251
}
245
252
246
-
const ORDER: ("like" | "repost" | "reply" | "quote")[] = [
247
-
"like",
248
-
"repost",
249
-
"reply",
250
-
"quote",
251
-
];
253
+
function PostInteractionsFilterChipBar() {
254
+
const [filters, setFilters] = useAtom(postInteractionsFiltersAtom);
255
+
// const empty = (!filters.likes && !filters.quotes && !filters.replies && !filters.reposts);
256
+
257
+
// useEffect(() => {
258
+
// if (empty) {
259
+
// setFilters((prev) => ({
260
+
// ...prev,
261
+
// likes: true,
262
+
// }));
263
+
// }
264
+
// }, [
265
+
// empty,
266
+
// setFilters,
267
+
// ]);
268
+
269
+
const toggle = (key: keyof typeof filters) => {
270
+
setFilters((prev) => ({
271
+
...prev,
272
+
[key]: !prev[key],
273
+
}));
274
+
};
275
+
276
+
return (
277
+
<div className="flex flex-row flex-wrap gap-2 px-4 pt-4">
278
+
<Chip
279
+
state={filters.likes}
280
+
text="Likes"
281
+
onClick={() => toggle("likes")}
282
+
/>
283
+
<Chip
284
+
state={filters.reposts}
285
+
text="Reposts"
286
+
onClick={() => toggle("reposts")}
287
+
/>
288
+
<Chip
289
+
state={filters.replies}
290
+
text="Replies"
291
+
onClick={() => toggle("replies")}
292
+
/>
293
+
<Chip
294
+
state={filters.quotes}
295
+
text="Quotes"
296
+
onClick={() => toggle("quotes")}
297
+
/>
298
+
<Chip
299
+
state={filters.showAll}
300
+
text="Show All Metrics"
301
+
onClick={() => toggle("showAll")}
302
+
/>
303
+
</div>
304
+
);
305
+
}
306
+
307
+
function Chip({
308
+
state,
309
+
text,
310
+
onClick,
311
+
}: {
312
+
state: boolean;
313
+
text: string;
314
+
onClick: React.MouseEventHandler<HTMLButtonElement>;
315
+
}) {
316
+
return (
317
+
<button
318
+
onClick={onClick}
319
+
className={`relative inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium transition-all
320
+
${
321
+
state
322
+
? "bg-primary/20 text-primary bg-gray-200 dark:bg-gray-800 border border-transparent"
323
+
: "bg-surface-container-low text-on-surface-variant border border-outline"
324
+
}
325
+
hover:bg-primary/30 active:scale-[0.97]
326
+
dark:border-outline-variant
327
+
`}
328
+
>
329
+
{state && (
330
+
<IconMdiCheck
331
+
className="mr-1.5 inline-block w-4 h-4 rounded-full bg-primary"
332
+
aria-hidden
333
+
/>
334
+
)}
335
+
{text}
336
+
</button>
337
+
);
338
+
}
252
339
253
340
function PostInteractionsItem({ uri }: { uri: string }) {
341
+
const [filters] = useAtom(postInteractionsFiltersAtom);
254
342
const { data: links } = useQueryConstellation({
255
343
method: "/links/all",
256
344
target: uri,
···
271
359
272
360
const all = likes + replies + reposts + quotes;
273
361
362
+
const failLikes = filters.likes && likes < 1;
363
+
const failReposts = filters.reposts && reposts < 1;
364
+
const failReplies = filters.replies && replies < 1;
365
+
const failQuotes = filters.quotes && quotes < 1;
366
+
367
+
const showLikes = filters.showAll || filters.likes
368
+
const showReposts = filters.showAll || filters.reposts
369
+
const showReplies = filters.showAll || filters.replies
370
+
const showQuotes = filters.showAll || filters.quotes
371
+
372
+
const showNone = !showLikes && !showReposts && !showReplies && !showQuotes;
373
+
374
+
const fail = failLikes || failReposts || failReplies || failQuotes || showNone;
375
+
376
+
377
+
if (fail) return;
378
+
274
379
return (
275
380
<div className="flex flex-col">
381
+
{/* <span>fail likes {failLikes ? "true" : "false"}</span>
382
+
<span>fail repost {failReposts ? "true" : "false"}</span>
383
+
<span>fail reply {failReplies ? "true" : "false"}</span>
384
+
<span>fail qupte {failQuotes ? "true" : "false"}</span> */}
276
385
<div className="border rounded-xl mx-4 mt-4 overflow-hidden">
277
386
<UniversalPostRendererATURILoader
278
387
isQuote
···
282
391
concise={true}
283
392
/>
284
393
<div className="flex flex-col divide-x">
285
-
<InteractionsButton
286
-
key={likes}
394
+
{showLikes &&(<InteractionsButton
287
395
type={"like"}
288
396
uri={uri}
289
397
count={likes}
290
-
/>
291
-
<InteractionsButton
292
-
key={reposts}
398
+
/>)}
399
+
{showReposts && (<InteractionsButton
293
400
type={"repost"}
294
401
uri={uri}
295
402
count={reposts}
296
-
/>
297
-
<InteractionsButton
298
-
key={replies}
403
+
/>)}
404
+
{showReplies && (<InteractionsButton
299
405
type={"reply"}
300
406
uri={uri}
301
407
count={replies}
302
-
/>
303
-
<InteractionsButton
304
-
key={quotes}
408
+
/>)}
409
+
{showQuotes && (<InteractionsButton
305
410
type={"quote"}
306
411
uri={uri}
307
412
count={quotes}
308
-
/>
413
+
/>)}
309
414
{!all && (
310
415
<div className="text-center text-gray-500 dark:text-gray-400 pb-3 pt-2 border-t">
311
416
No interactions yet.
+22
src/utils/atoms.ts
+22
src/utils/atoms.ts
···
25
25
activeTab: string;
26
26
scrollPositions: Record<string, number>;
27
27
};
28
+
/**
29
+
* @deprecated should be safe to remove i think
30
+
*/
28
31
export const notificationsScrollAtom = atom<TabRouteScrollState>({
29
32
activeTab: "mentions",
30
33
scrollPositions: {},
31
34
});
35
+
36
+
export type InteractionFilter = {
37
+
likes: boolean;
38
+
reposts: boolean;
39
+
quotes: boolean;
40
+
replies: boolean;
41
+
showAll: boolean;
42
+
};
43
+
const defaultFilters: InteractionFilter = {
44
+
likes: true,
45
+
reposts: true,
46
+
quotes: true,
47
+
replies: true,
48
+
showAll: false,
49
+
};
50
+
export const postInteractionsFiltersAtom = atomWithStorage<InteractionFilter>(
51
+
"postInteractionsFilters",
52
+
defaultFilters
53
+
);
32
54
33
55
export const reusableTabRouteScrollAtom = atom<Record<string, TabRouteScrollState | undefined> | undefined>({});
34
56