tutu/src/timelines/ReplyEditor.tsx
2024-09-27 14:23:57 +08:00

237 lines
6.3 KiB
TypeScript

import {
batch,
createSignal,
createUniqueId,
onMount,
type Component,
type Setter,
} from "solid-js";
import Scaffold from "../material/Scaffold";
import {
Avatar,
Button,
IconButton,
List,
ListItemButton,
ListItemIcon,
ListItemSecondaryAction,
ListItemText,
Radio,
Switch,
Divider,
} from "@suid/material";
import {
ArrowDropDown,
Public as PublicIcon,
Send,
People as PeopleIcon,
ThreeP as ThreePIcon,
ListAlt as ListAltIcon,
} from "@suid/icons-material";
import type { Account } from "../accounts/stores";
import tootComposers from "./TootComposer.module.css";
import { makeEventListener } from "@solid-primitives/event-listener";
import BottomSheet from "../material/BottomSheet";
type TootVisibility = "public" | "unlisted" | "private" | "direct";
const TootVisibilityPickerDialog: Component<{
open?: boolean;
onClose: () => void;
visibility: TootVisibility;
onVisibilityChange: (value: TootVisibility) => void;
}> = (props) => {
type Kind = "public" | "private" | "direct";
const kind = () =>
props.visibility === "public" || props.visibility === "unlisted"
? "public"
: props.visibility;
const setKind = (nv: Kind) => {
if (nv == "public") {
props.onVisibilityChange(discoverable() ? "public" : "unlisted");
} else {
props.onVisibilityChange(nv);
}
};
const discoverable = () => {
return props.visibility === "public";
};
const setDiscoverable = (setter: (v: boolean) => boolean) => {
const nval = setter(discoverable());
props.onVisibilityChange(nval ? "public" : "unlisted"); // trigger change
};
return (
<BottomSheet open={props.open} onClose={props.onClose} bottomUp>
<Scaffold
bottom={
<div
style={{
"border-top": "1px solid #ddd",
background: "var(--tutu-color-surface)",
padding: "8px 16px",
width: "100%",
"text-align": "end",
}}
>
<Button onClick={props.onClose}>Confirm</Button>
</div>
}
>
<List dense>
<ListItemButton onClick={[setKind, "public"]}>
<ListItemIcon>
<PublicIcon />
</ListItemIcon>
<ListItemText
primary="Public"
secondary="Everyone can see this toot"
></ListItemText>
<ListItemSecondaryAction>
<Radio checked={kind() == "public"}></Radio>
</ListItemSecondaryAction>
</ListItemButton>
<ListItemButton
sx={{ paddingLeft: "40px" }}
disabled={kind() !== "public"}
onClick={() => setDiscoverable((x) => !x)}
>
<ListItemIcon>
<ListAltIcon />
</ListItemIcon>
<ListItemText
primary="Discoverable"
secondary="The others can discover it on the exploration."
></ListItemText>
<ListItemSecondaryAction>
<Switch
checked={discoverable()}
disabled={kind() !== "public"}
></Switch>
</ListItemSecondaryAction>
</ListItemButton>
<Divider />
<ListItemButton onClick={[setKind, "private"]}>
<ListItemIcon>
<PeopleIcon />
</ListItemIcon>
<ListItemText
primary="Only Followers"
secondary="Visibile for followers only"
></ListItemText>
<ListItemSecondaryAction>
<Radio checked={kind() == "private"}></Radio>
</ListItemSecondaryAction>
</ListItemButton>
<Divider />
<ListItemButton onClick={[setKind, "direct"]}>
<ListItemIcon>
<ThreePIcon />
</ListItemIcon>
<ListItemText
primary="Only Mentions"
secondary="Visible for mentioned users only"
></ListItemText>
<ListItemSecondaryAction>
<Radio checked={kind() == "direct"}></Radio>
</ListItemSecondaryAction>
</ListItemButton>
</List>
</Scaffold>
</BottomSheet>
);
};
const ReplyEditor: Component<{
profile: Account;
replyToDisplayName: string;
isTyping?: boolean
onTypingChange: (value: boolean) => void
}> = (props) => {
let inputRef: HTMLTextAreaElement;
const buttonId = createUniqueId();
const menuId = createUniqueId();
const typing = () => props.isTyping
const setTyping = (v: boolean) => props.onTypingChange(v)
const [visibility, setVisibility] = createSignal<TootVisibility>("public");
const [permPicker, setPermPicker] = createSignal(false);
onMount(() => {
makeEventListener(inputRef, "focus", () => setTyping(true));
});
const containerStyle = () =>
typing() || permPicker()
? {
position: "sticky" as const,
top: "var(--scaffold-topbar-height, 0)",
bottom: "var(--safe-area-inset-bottom, 0)",
"z-index": 2,
}
: undefined;
const visibilityText = () => {
switch (visibility()) {
case "public":
return "Discoverable";
case "unlisted":
return "Public";
case "private":
return "Only Followers";
case "direct":
return "Only Mentions";
}
};
return (
<div
class={tootComposers.composer}
style={containerStyle()}
onClick={(e) => inputRef.focus()}
>
<div class={tootComposers.replyInput}>
<Avatar src={props.profile.inf?.avatar} />
<textarea
ref={inputRef!}
placeholder={`Reply to ${props.replyToDisplayName}...`}
style={{ width: "100%", border: "none" }}
></textarea>
<IconButton>
<Send />
</IconButton>
</div>
<div
style={{
display: "flex",
"justify-content": "flex-end",
"margin-top": "8px",
}}
>
<Button onClick={[setPermPicker, true]} id={buttonId}>
{visibilityText()}
<ArrowDropDown sx={{ marginTop: "-0.25em" }} />
</Button>
</div>
<TootVisibilityPickerDialog
open={permPicker()}
onClose={() => setPermPicker(false)}
visibility={visibility()}
onVisibilityChange={setVisibility}
/>
</div>
);
};
export default ReplyEditor;