diff --git a/src/App.tsx b/src/App.tsx index e7ca588..8f7ba44 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { Route } from "@solidjs/router"; +import { Route, Router } from "@solidjs/router"; import { ThemeProvider } from "@suid/material"; import { Component, @@ -17,12 +17,7 @@ import { } from "./masto/clients.js"; import { $accounts, updateAcctInf } from "./accounts/stores.js"; import { useStore } from "@nanostores/solid"; -import { - AppLocaleProvider, - createCurrentLanguage, - createCurrentRegion, - createDateFnLocaleResource, -} from "./platform/i18n.jsx"; +import { DateFnScope, useLanguage } from "./platform/i18n.jsx"; import { useRegisterSW } from "virtual:pwa-register/solid"; import { isJSONRPCResult, @@ -72,9 +67,7 @@ const Routing: Component = () => { const App: Component = () => { const theme = useRootTheme(); const accts = useStore($accounts); - const lang = createCurrentLanguage(); - const region = createCurrentRegion(); - const dateFnLocale = createDateFnLocaleResource(region); + const lang = useLanguage(); const [serviceWorker, setServiceWorker] = createSignal< ServiceWorker | undefined >(undefined, { name: "serviceWorker" }); @@ -157,13 +150,7 @@ const App: Component = () => { }} > - + { - + ); diff --git a/src/platform/i18n.tsx b/src/platform/i18n.tsx index b64b920..ade3868 100644 --- a/src/platform/i18n.tsx +++ b/src/platform/i18n.tsx @@ -1,12 +1,12 @@ import { - catchError, + ParentComponent, createContext, createMemo, createResource, useContext, } from "solid-js"; import { match } from "@formatjs/intl-localematcher"; -import { Accessor } from "solid-js"; +import { Accessor, createEffect, createSignal } from "solid-js"; import { $settings } from "../settings/stores"; import { enGB } from "date-fns/locale/en-GB"; import { useStore } from "@nanostores/solid"; @@ -17,6 +17,13 @@ import { type Template, } from "@solid-primitives/i18n"; +async function synchronised( + name: string, + callback: () => Promise | void, +): Promise { + await navigator.locks.request(name, callback); +} + export const SUPPORTED_LANGS = ["en", "zh-Hans"] as const; export const SUPPORTED_REGIONS = ["en_US", "en_GB", "zh_CN"] as const; @@ -31,6 +38,14 @@ export function autoMatchLangTag() { return match(Array.from(navigator.languages), SUPPORTED_LANGS, DEFAULT_LANG); } +const DateFnLocaleCx = /* __@PURE__ */ createContext>( + () => enGB, +); + +const cachedDateFnLocale: Record = { + enGB, +}; + export function autoMatchRegion() { const specifiers = navigator.languages.map((x) => x.split("-")); @@ -55,7 +70,7 @@ export function autoMatchRegion() { return "en_GB"; } -export function createCurrentRegion() { +export function useRegion() { const appSettings = useStore($settings); return createMemo( @@ -85,6 +100,53 @@ async function importDateFnLocale(tag: string): Promise { } } +/** + * Provides runtime values and fetch dependencies for date-fns locale + */ +export const DateFnScope: ParentComponent = (props) => { + const [dateFnLocale, setDateFnLocale] = createSignal(enGB, { + name: "dateFnLocale", + }); + const region = useRegion(); + + createEffect(() => { + const dateFnLocaleName = region(); + + if (cachedDateFnLocale[dateFnLocaleName]) { + setDateFnLocale(cachedDateFnLocale[dateFnLocaleName]); + } else { + synchronised("i18n-wrapper-load-date-fns-locale", async () => { + if (cachedDateFnLocale[dateFnLocaleName]) { + setDateFnLocale(cachedDateFnLocale[dateFnLocaleName]); + return; + } + const target = `date-fns/locale/${dateFnLocaleName}`; + try { + const mod = await importDateFnLocale(dateFnLocaleName); + cachedDateFnLocale[dateFnLocaleName] = mod; + setDateFnLocale(mod); + } catch (reason) { + console.error( + { + act: "load-date-fns-locale", + stat: "failed", + reason, + target, + }, + "failed to load date-fns locale", + ); + } + }); + } + }); + + return ( + + {props.children} + + ); +}; + /** * Get the {@link Locale} object for date-fns. * @@ -93,11 +155,11 @@ async function importDateFnLocale(tag: string): Promise { * @returns Accessor for Locale */ export function useDateFnLocale(): Accessor { - const { dateFn } = useAppLocale(); - return dateFn; + const cx = useContext(DateFnLocaleCx); + return cx; } -export function createCurrentLanguage() { +export function useLanguage() { const settings = useStore($settings); return () => settings().language || autoMatchLangTag(); } @@ -117,7 +179,7 @@ type MergedImportedModule = T extends [] export function createStringResource< T extends ImportFn | undefined>>[], >(...importFns: T) { - const language = createCurrentLanguage(); + const language = useLanguage(); // TODO: this function costs to much, provide a global cache const cache: Record> = {}; return createResource( @@ -147,38 +209,3 @@ export function createTranslator< return [translator(res[0], resolveTemplate), res] as const; } - -export type AppLocale = { - dateFn: () => Locale; - language: () => string; - region: () => string; -}; - -const AppLocaleContext = /* @__PURE__ */ createContext(); - -export const AppLocaleProvider = AppLocaleContext.Provider; - -export function useAppLocale() { - const l = useContext(AppLocaleContext); - if (!l) { - throw new TypeError("app locale not found"); - } - return l; -} - -export function createDateFnLocaleResource(region: () => string) { - const [localeUncaught] = createResource( - region, - async (region) => { - return await importDateFnLocale(region); - }, - { initialValue: enGB }, - ); - - return createMemo( - () => - catchError(localeUncaught, (reason) => { - console.error("fetch date-fns locale", reason); - }) ?? enGB, - ); -} diff --git a/src/timelines/CompactToot.tsx b/src/timelines/CompactToot.tsx new file mode 100644 index 0000000..52bd551 --- /dev/null +++ b/src/timelines/CompactToot.tsx @@ -0,0 +1,57 @@ +import type { mastodon } from "masto"; +import { Show, type Component } from "solid-js"; +import tootStyle from "./toot.module.css"; +import { formatRelative } from "date-fns"; +import Img from "~material/Img"; +import { Body2 } from "~material/typography"; +import { appliedCustomEmoji } from "../masto/toot"; +import { TootPreviewCard } from "./RegularToot"; + +type CompactTootProps = { + status: mastodon.v1.Status; + now: Date; + class?: string; +}; + +const CompactToot: Component = (props) => { + const toot = () => props.status; + return ( +
+ +
+ { + appliedCustomEmoji( + e, + toot().account.displayName, + toot().account.emojis, + ); + }} + > + + @{toot().account.username}@{new URL(toot().account.url).hostname} + + +
+
{ + appliedCustomEmoji(e, toot().content, toot().emojis); + }} + class={[tootStyle.compactTootContent].join(" ")} + >
+ + + +
+ ); +}; + +export default CompactToot; diff --git a/src/timelines/RegularToot.css b/src/timelines/RegularToot.css deleted file mode 100644 index d7a50c3..0000000 --- a/src/timelines/RegularToot.css +++ /dev/null @@ -1,78 +0,0 @@ -.RegularToot { - --card-pad: 16px; - --card-gut: 16px; - --toot-avatar-size: 40px; - margin-block: 0; - position: relative; - contain: layout style; - cursor: pointer; - - - transition: - margin-top 60ms var(--tutu-anim-curve-sharp), - margin-bottom 60ms var(--tutu-anim-curve-sharp), - height 60ms var(--tutu-anim-curve-sharp), - var(--tutu-transition-shadow); - border-radius: 0; - - time { - color: var(--tutu-color-secondary-text-on-surface); - } - - >.retoot-grp { - display: flex; - gap: 0.25em; - margin-bottom: 8px; - align-items: center; - - > :first-child { - margin-right: 0.25em; - } - } - - & .custom-emoji { - height: 1em; - object-fit: contain; - } - - &.expanded { - margin-block: 20px; - box-shadow: var(--tutu-shadow-e9); - } - - &.thread-top, - &.thread-mid, - &.thread-btm { - position: relative; - - &::before { - content: ""; - position: absolute; - left: 36px; - background-color: var(--tutu-color-secondary); - width: 2px; - display: block; - } - } - - &.thread-mid { - &::before { - top: 0; - bottom: 0; - } - } - - &.thread-top { - &::before { - top: 16px; - bottom: 0; - } - } - - &.thread-btm { - &::before { - top: 0; - height: 16px; - } - } -} diff --git a/src/timelines/RegularToot.tsx b/src/timelines/RegularToot.tsx index fd5c80d..68c0f72 100644 --- a/src/timelines/RegularToot.tsx +++ b/src/timelines/RegularToot.tsx @@ -7,62 +7,63 @@ import { createRenderEffect, createSignal, type Setter, - createContext, - useContext, } from "solid-js"; import tootStyle from "./toot.module.css"; -import { formatRelative } from "date-fns"; +import { formatRelative, parseISO } from "date-fns"; import Img from "~material/Img.js"; import { Body2 } from "~material/typography.js"; -import { SmartToySharp, Lock } from "@suid/icons-material"; +import { css } from "solid-styled"; +import { + BookmarkAddOutlined, + Repeat, + ReplyAll, + Star, + StarOutline, + Bookmark, + Share, + SmartToySharp, + Lock, +} from "@suid/icons-material"; import { useTimeSource } from "~platform/timesrc.js"; import { resolveCustomEmoji } from "../masto/toot.js"; import { Divider } from "@suid/material"; import cardStyle from "~material/cards.module.css"; +import Button from "~material/Button.js"; import MediaAttachmentGrid from "./toots/MediaAttachmentGrid.jsx"; import { useDateFnLocale } from "~platform/i18n"; +import { canShare, share } from "~platform/share"; import { makeAcctText, useDefaultSession } from "../masto/clients"; import TootContent from "./toots/TootContent"; import BoostIcon from "./toots/BoostIcon"; import PreviewCard from "./toots/PreviewCard"; import TootPoll from "./toots/TootPoll"; -import TootActionGroup from "./toots/TootActionGroup.js"; -import "./RegularToot.css"; -export type TootEnv = { - boost: (value: mastodon.v1.Status) => void; - favourite: (value: mastodon.v1.Status) => void; - bookmark: (value: mastodon.v1.Status) => void; - reply?: ( - value: mastodon.v1.Status, +type TootActionGroupProps = { + onRetoot?: (value: T) => void; + onFavourite?: (value: T) => void; + onBookmark?: (value: T) => void; + onReply?: ( + value: T, event: MouseEvent & { currentTarget: HTMLButtonElement }, ) => void; - vote: ( - status: mastodon.v1.Status, - votes: readonly number[], - ) => void | Promise; }; -const TootEnvContext = /* @__PURE__ */ createContext(); - -export const TootEnvProvider = TootEnvContext.Provider; - -export function useTootEnv() { - const env = useContext(TootEnvContext); - if (!env) { - throw new TypeError( - "environment not found, use TootEnvProvider to provide", - ); - } - return env; -} - type RegularTootProps = { status: mastodon.v1.Status; actionable?: boolean; evaluated?: boolean; thread?: "top" | "bottom" | "middle"; -} & JSX.HTMLElementTags["article"]; + + onVote?: (value: { + status: mastodon.v1.Status; + votes: readonly number[]; + }) => void | Promise; +} & TootActionGroupProps & + JSX.HTMLElementTags["article"]; + +function isolatedCallback(e: MouseEvent) { + e.stopPropagation(); +} export function findRootToot(element: HTMLElement) { let current: HTMLElement | null = element; @@ -77,6 +78,73 @@ export function findRootToot(element: HTMLElement) { return current; } +function TootActionGroup( + props: TootActionGroupProps & { value: T }, +) { + let actGrpElement: HTMLDivElement; + const toot = () => props.value; + return ( +
+ + + + + + + + + + +
+ ); +} + function TootAuthorGroup( props: { status: mastodon.v1.Status; @@ -152,8 +220,6 @@ function onToggleReveal(setValue: Setter, event: Event) { * this component under a `` with correct * session. * - * This component requires be under ``. - * * **Handling Clicks** * There are multiple actions supported in the component. Some handlers * are passed in, some should be handled as the click event. @@ -175,39 +241,80 @@ function onToggleReveal(setValue: Setter, event: Event) { * You can extract the intent from the attributes of the "actionable" element. * The action type is the dataset's `action`. */ -const RegularToot: Component = (oprops) => { +const RegularToot: Component = (props) => { let rootRef: HTMLElement; - const [props, rest] = splitProps(oprops, [ - "status", - "lang", - "class", - "actionable", - "evaluated", - "thread", - ]); + const [managed, managedActionGroup, pollProps, rest] = splitProps( + props, + ["status", "lang", "class", "actionable", "evaluated", "thread"], + ["onRetoot", "onFavourite", "onBookmark", "onReply"], + ["onVote"], + ); const now = useTimeSource(); - const status = () => props.status; + const status = () => managed.status; const toot = () => status().reblog ?? status(); const session = useDefaultSession(); const [reveal, setReveal] = createSignal(false); + css` + .reply-sep { + margin-left: calc(var(--toot-avatar-size) + var(--card-pad) + 8px); + margin-block: 8px; + } + + .thread-top, + .thread-mid, + .thread-btm { + position: relative; + + &::before { + content: ""; + position: absolute; + left: 36px; + background-color: var(--tutu-color-secondary); + width: 2px; + display: block; + } + } + + .thread-mid { + &::before { + top: 0; + bottom: 0; + } + } + + .thread-top { + &::before { + top: 16px; + bottom: 0; + } + } + + .thread-btm { + &::before { + top: 0; + height: 16px; + } + } + `; + return ( <>
-
+
{ @@ -253,14 +360,27 @@ const RegularToot: Component = (oprops) => { /> - + pollProps.onVote?.({ status: status(), votes })} + /> - + - +
diff --git a/src/timelines/TootBottomSheet.tsx b/src/timelines/TootBottomSheet.tsx index 09465db..d38d34f 100644 --- a/src/timelines/TootBottomSheet.tsx +++ b/src/timelines/TootBottomSheet.tsx @@ -13,10 +13,7 @@ import { Title } from "~material/typography"; import { Close as CloseIcon } from "@suid/icons-material"; import { useSessionForAcctStr } from "../masto/clients"; import { resolveCustomEmoji } from "../masto/toot"; -import RegularToot, { - findElementActionable, - TootEnvProvider, -} from "./RegularToot"; +import RegularToot, { findElementActionable } from "./RegularToot"; import type { mastodon } from "masto"; import cards from "~material/cards.module.css"; import { css } from "solid-styled"; @@ -172,33 +169,6 @@ const TootBottomSheet: Component = (props) => { return Array.from(new Set(values).keys()); }; - const vote = async (status: mastodon.v1.Status, votes: readonly number[]) => { - const client = session()?.client; - if (!client) return; - - const toot = status.reblog ?? status; - if (!toot.poll) return; - - const npoll = await client.v1.polls.$select(toot.poll.id).votes.create({ - choices: votes, - }); - - if (status.reblog) { - setRemoteToot({ - ...status, - reblog: { - ...status.reblog, - poll: npoll, - }, - }); - } else { - setRemoteToot({ - ...status, - poll: npoll, - }); - } - }; - const handleMainTootClick = ( event: MouseEvent & { currentTarget: HTMLElement }, ) => { @@ -285,29 +255,23 @@ const TootBottomSheet: Component = (props) => {
- - - + status={toot()!} + actionable={!!actSession()} + evaluated={true} + onBookmark={onBookmark} + onRetoot={onBoost} + onFavourite={onFav} + onClick={handleMainTootClick} + >
diff --git a/src/timelines/TootComposer.tsx b/src/timelines/TootComposer.tsx index 2895c67..3c6b696 100644 --- a/src/timelines/TootComposer.tsx +++ b/src/timelines/TootComposer.tsx @@ -42,7 +42,7 @@ import { import type { Account } from "../accounts/stores"; import "./TootComposer.css"; import BottomSheet from "~material/BottomSheet"; -import { useAppLocale } from "~platform/i18n"; +import { useLanguage } from "~platform/i18n"; import iso639_1 from "iso-639-1"; import ChooseTootLang from "./ChooseTootLang"; import type { mastodon } from "masto"; @@ -98,8 +98,7 @@ const TootVisibilityPickerDialog: Component<{ style={{ "border-top": "1px solid #ddd", background: "var(--tutu-color-surface)", - padding: - "8px 16px calc(8px + var(--safe-area-inset-bottom, 0px))", + padding: "8px 16px calc(8px + var(--safe-area-inset-bottom, 0px))", width: "100%", "text-align": "end", }} @@ -233,7 +232,7 @@ const TootComposer: Component<{ const [permPicker, setPermPicker] = createSignal(false); const [language, setLanguage] = createSignal("en"); const [langPickerOpen, setLangPickerOpen] = createSignal(false); - const { language: appLanguage } = useAppLocale(); + const appLanguage = useLanguage(); const [openMenu, menuState] = createManagedMenuState(); const randomPlaceholder = useRandomChoice(() => [ diff --git a/src/timelines/TootList.tsx b/src/timelines/TootList.tsx index b35dcce..e72c360 100644 --- a/src/timelines/TootList.tsx +++ b/src/timelines/TootList.tsx @@ -6,7 +6,6 @@ import { createSelector, Index, createMemo, - For, } from "solid-js"; import { type mastodon } from "masto"; import { vibrate } from "~platform/hardware"; @@ -15,7 +14,6 @@ import { setCache as setTootBottomSheetCache } from "./TootBottomSheet"; import RegularToot, { findElementActionable, findRootToot, - TootEnvProvider, } from "./RegularToot"; import cardStyle from "~material/cards.module.css"; import type { ThreadNode } from "../masto/timelines"; @@ -234,10 +232,13 @@ const TootList: Component<{ openFullScreenToot(status, element, true); }; - const vote = async ( - status: mastodon.v1.Status, - votes: readonly number[] - ) => { + const vote = async ({ + status, + votes, + }: { + status: mastodon.v1.Status; + votes: readonly number[]; + }) => { const client = session()?.client; if (!client) return; @@ -262,6 +263,7 @@ const TootList: Component<{ poll: npoll, }); } + }; return ( @@ -271,50 +273,49 @@ const TootList: Component<{ return

Oops: {String(err)}

; }} > - -
- - {(threadId, threadIdx) => { - const thread = createMemo(() => - props.onUnknownThread(threadId)?.reverse(), - ); +
+ + {(threadId, threadIdx) => { + const thread = createMemo(() => + props.onUnknownThread(threadId())?.reverse(), + ); - const threadLength = () => thread()?.length ?? 0; + const threadLength = () => thread()?.length ?? 0; - return ( - - {(threadNode, index) => { - const status = () => threadNode().value; + return ( + + {(threadNode, index) => { + const status = () => threadNode().value; - return ( - 1 - ? positionTootInThread(index, threadLength()) - : undefined - } - class={cardStyle.card} - evaluated={checkIsExpended(status())} - actionable={checkIsExpended(status())} - onClick={[onItemClick, status()]} - /> - ); - }} - - ); - }} - -
- + return ( + 1 + ? positionTootInThread(index, threadLength()) + : undefined + } + class={cardStyle.card} + evaluated={checkIsExpended(status())} + actionable={checkIsExpended(status())} + onBookmark={onBookmark} + onRetoot={toggleBoost} + onFavourite={toggleFavourite} + onReply={reply} + onVote={vote} + onClick={[onItemClick, status()]} + /> + ); + }} + + ); + }} + +
); }; diff --git a/src/timelines/toot.module.css b/src/timelines/toot.module.css index e666f66..bb10964 100644 --- a/src/timelines/toot.module.css +++ b/src/timelines/toot.module.css @@ -1,3 +1,41 @@ +.toot { + --card-pad: 16px; + --card-gut: 16px; + --toot-avatar-size: 40px; + margin-block: 0; + position: relative; + contain: content; + cursor: pointer; + + &.toot { + /* fix composition ordering: I think the css module processor should aware the overriding and behaves, but no */ + transition: + margin-top 60ms var(--tutu-anim-curve-sharp), + margin-bottom 60ms var(--tutu-anim-curve-sharp), + height 60ms var(--tutu-anim-curve-sharp), + var(--tutu-transition-shadow); + border-radius: 0; + } + + &>.toot { + box-shadow: none; + } + + time { + color: var(--tutu-color-secondary-text-on-surface); + } + + & :global(.custom-emoji) { + height: 1em; + object-fit: contain; + } + + &.expanded { + margin-block: 20px; + box-shadow: var(--tutu-shadow-e9); + } +} + .tootAuthorGrp { display: flex; align-items: flex-start; @@ -50,3 +88,90 @@ border: 1px solid var(--tutu-color-surface); background-color: var(--tutu-color-surface-d); } + +.toot.compact { + display: grid; + grid-template-columns: auto 1fr; + gap: 8px; + row-gap: 0; + padding-block: var(--card-gut, 16px); + padding-inline: var(--card-pad, 16px); + + > :first-child { + grid-row: 1/3; + } + + > :last-child { + grid-column: 2 /3; + } +} + +.compactAuthorGroup { + display: flex; + gap: 8px; + align-items: center; + margin-bottom: 8px; + flex-flow: row wrap; + justify-content: flex-end; + + >.compactAuthorUsername { + color: var(--tutu-color-secondary-text-on-surface); + flex-grow: 1; + } + + >time { + color: var(--tutu-color-secondary-text-on-surface); + } +} + +.tootRetootGrp { + display: flex; + gap: 0.25em; + margin-bottom: 8px; + align-items: center; + + > :first-child { + margin-right: 0.25em; + } +} + +.tootBottomActionGrp { + composes: cardGutSkip from "~material/cards.module.css"; + padding-block: calc((var(--card-gut) - 10px) / 2); + + animation: 225ms var(--tutu-anim-curve-std) tootBottomExpanding; + display: flex; + flex-flow: row wrap; + justify-content: space-evenly; + + >button { + color: var(--tutu-color-on-surface); + padding: 10px 8px; + + >svg { + font-size: 20px; + } + } +} + +.tootActionWithCount { + display: flex; + align-items: center; + gap: 8px; +} + +.tootAction { + display: flex; + align-items: center; + justify-content: center; +} + +@keyframes tootBottomExpanding { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} \ No newline at end of file diff --git a/src/timelines/toots/TootActionGroup.css b/src/timelines/toots/TootActionGroup.css deleted file mode 100644 index b82432c..0000000 --- a/src/timelines/toots/TootActionGroup.css +++ /dev/null @@ -1,41 +0,0 @@ -.TootActionGroup { - padding-block: calc((var(--card-gut) - 10px) / 2); - contain: layout style; - - animation: 225ms var(--tutu-anim-curve-std) TootActionGroup_fade-in; - display: flex; - flex-flow: row wrap; - justify-content: space-evenly; - - >button { - color: var(--tutu-color-on-surface); - padding: 10px 8px; - - >svg { - font-size: 20px; - } - } - - >* { - display: flex; - align-items: center; - } - - >.with-count { - gap: 8px; - } - - >.plain { - justify-content: center; - } -} - -@keyframes TootActionGroup_fade-in { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} \ No newline at end of file diff --git a/src/timelines/toots/TootActionGroup.tsx b/src/timelines/toots/TootActionGroup.tsx deleted file mode 100644 index fef75ef..0000000 --- a/src/timelines/toots/TootActionGroup.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import type { mastodon } from "masto"; -import { useTootEnv } from "../RegularToot"; -import { Button } from "@suid/material"; -import { Show } from "solid-js"; -import { - Bookmark, - BookmarkAddOutlined, - Repeat, - ReplyAll, - Share, - Star, - StarOutline, -} from "@suid/icons-material"; -import { canShare, share } from "~platform/share"; -import "./TootActionGroup.css"; - -async function shareContent(toot: mastodon.v1.Status) { - return await share({ - url: toot.url ?? undefined, - }); -} - -function isolatedCallback(e: MouseEvent) { - e.stopPropagation(); -} - -function TootActionGroup(props: { - value: T; - class?: string; -}) { - const { reply, boost, favourite, bookmark } = useTootEnv(); - let actGrpElement: HTMLDivElement; - const toot = () => props.value; - return ( -
- - - - - - - - - - -
- ); -} - -export default TootActionGroup; diff --git a/src/timelines/toots/TootPoll.tsx b/src/timelines/toots/TootPoll.tsx index a148fcb..ce972b0 100644 --- a/src/timelines/toots/TootPoll.tsx +++ b/src/timelines/toots/TootPoll.tsx @@ -28,17 +28,21 @@ import { useTimeSource } from "~platform/timesrc"; import { useDateFnLocale } from "~platform/i18n"; import TootPollDialog from "./TootPollDialog"; import { ANIM_CURVE_STD } from "~material/theme"; -import { useTootEnv } from "../RegularToot"; type TootPollProps = { - value: mastodon.v1.Poll - status: mastodon.v1.Status + options: Readonly; + multiple?: boolean; + votesCount: number; + expired?: boolean; + expiredAt?: Date; + voted?: boolean; + ownVotes?: readonly number[]; + + onVote(votes: readonly number[]): void | Promise; }; const TootPoll: Component = (props) => { let list: HTMLUListElement; - const {vote}= useTootEnv() - const now = useTimeSource(); const dateFnLocale = useDateFnLocale(); const [mustShowResult, setMustShowResult] = createSignal(); @@ -46,26 +50,24 @@ const TootPoll: Component = (props) => { const [initialVote, setInitialVote] = createSignal(0); - const poll = () => props.value - const isShowResult = () => { const n = mustShowResult(); if (typeof n !== "undefined") { return n; } - return poll().expired || poll().voted; + return props.expired || props.voted; }; const isOwnVote = createSelector( - () => poll().ownVotes, + () => props.ownVotes, (idx: number, votes) => votes?.includes(idx) || false, ); const openVote = (i: number, event: Event) => { event.stopPropagation(); - if (poll().expired || poll().voted) { + if (props.expired || props.voted) { return; } @@ -98,13 +100,13 @@ const TootPoll: Component = (props) => { return (
- {poll().votesCount} votes in total - + {props.votesCount} votes in total + Poll is ended
- + {(option, index) => { return ( <> @@ -138,7 +140,7 @@ const TootPoll: Component = (props) => { = (props) => { - + - {isBefore(now(), poll().expiresAt!) ? "Expire in" : "Expired"} + {isBefore(now(), props.expiredAt!) ? "Expire in" : "Expired"} -