Profile: filter recent toots

- material: new Menu component
- material/theme: animation curves
- TootFilterButton
This commit is contained in:
thislight 2024-10-24 23:47:44 +08:00
parent 08201cccef
commit f8dc2950d2
5 changed files with 284 additions and 5 deletions

View file

@ -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,7 +48,6 @@ const Profile: Component = () => {
threshold: 0.1,
},
);
onCleanup(() => obx.disconnect());
const [profile] = createResource(
@ -58,9 +57,18 @@ const Profile: Component = () => {
},
);
const [recentTootFilter, setRecentTootFilter] = createSignal({
boost: true,
reply: true,
original: true,
});
const [recentToots] = createTimeline(
() => session().client.v1.accounts.$select(params.id).statuses,
() => ({ limit: 20 }),
() => {
const { boost, reply } = recentTootFilter();
return { limit: 20, excludeReblogs: !boost, excludeReplies: !reply };
},
);
const bannerImg = () => profile()?.header;
@ -126,7 +134,9 @@ const Profile: Component = () => {
variant="dense"
sx={{
display: "flex",
color: scrolledPastBanner() ? undefined : bannerSampledColors()?.text,
color: scrolledPastBanner()
? undefined
: bannerSampledColors()?.text,
paddingTop: "var(--safe-area-inset-top)",
}}
>
@ -238,6 +248,19 @@ const Profile: Component = () => {
</table>
</div>
<div>
<TootFilterButton
options={{
boost: "Boosteds",
reply: "Replies",
original: "Originals",
}}
applied={recentTootFilter()}
onApply={setRecentTootFilter}
disabledKeys={["original"]}
></TootFilterButton>
</div>
<TimeSourceProvider value={time}>
<TootList
threads={recentToots.list}

View file

@ -0,0 +1,112 @@
import {
Button,
MenuItem,
Checkbox,
ListItemText,
} from "@suid/material";
import {
createMemo,
createSignal,
createUniqueId,
For,
} from "solid-js";
import Menu from "../material/Menu";
import { FilterList, FilterListOff } from "@suid/icons-material";
type Props<Filters extends Record<string, string>> = {
options: Filters;
applied: Record<keyof Filters, boolean | undefined>;
disabledKeys?: (keyof Filters)[];
onApply(value: Record<keyof Filters, boolean | undefined>): void;
};
function TootFilterButton<F extends Record<string, string>>(props: Props<F>) {
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 (
<>
<Button size="large" onClick={[setOpen, true]} id={buttonId}>
{appliedKeys().length === optionKeys().length ? (
<FilterListOff />
) : (
<FilterList />
)}
<span style={{ "margin-left": "0.5em" }}>{text()}</span>
</Button>
<Menu
open={open()}
onClose={[setOpen, false]}
anchor={() =>
document.getElementById(buttonId)!.getBoundingClientRect()
}
>
<For each={Object.keys(props.options)}>
{(item, idx) => (
<>
<MenuItem
data-sort={idx()}
onClick={[toggleKey, item]}
disabled={props.disabledKeys?.includes(item)}
>
<ListItemText>{props.options[item]}</ListItemText>
<Checkbox
checked={props.applied[item]}
sx={{ marginRight: "-8px" }}
disabled={props.disabledKeys?.includes(item)}
></Checkbox>
</MenuItem>
</>
)}
</For>
</Menu>
</>
);
}
export default TootFilterButton;