ItemSelectionProvider: promote toot expansion
state * used in Profile, TrendTimelinePanel, TimelinePanel
This commit is contained in:
parent
dafc2c47a8
commit
9499182a8d
5 changed files with 120 additions and 68 deletions
|
@ -58,6 +58,10 @@ import Menu, { createManagedMenuState } from "~material/Menu";
|
|||
import { share } from "~platform/share";
|
||||
import "./Profile.css";
|
||||
import { useNavigator } from "~platform/StackedRouter";
|
||||
import {
|
||||
createSingluarItemSelection,
|
||||
default as ItemSelectionProvider,
|
||||
} from "../timelines/toots/ItemSelectionProvider";
|
||||
|
||||
const Profile: Component = () => {
|
||||
const { pop } = useNavigator();
|
||||
|
@ -70,6 +74,7 @@ const Profile: Component = () => {
|
|||
}>();
|
||||
const windowSize = useWindowSize();
|
||||
const time = createTimeSource();
|
||||
const [, selectionState] = createSingluarItemSelection();
|
||||
|
||||
const menuButId = createUniqueId();
|
||||
const recentTootListId = createUniqueId();
|
||||
|
@ -499,22 +504,26 @@ const Profile: Component = () => {
|
|||
></TootFilterButton>
|
||||
</div>
|
||||
|
||||
<TimeSourceProvider value={time}>
|
||||
<Show when={recentTootFilter().pinned && pinnedToots.list.length > 0}>
|
||||
<ItemSelectionProvider value={selectionState}>
|
||||
<TimeSourceProvider value={time}>
|
||||
<Show
|
||||
when={recentTootFilter().pinned && pinnedToots.list.length > 0}
|
||||
>
|
||||
<TootList
|
||||
threads={pinnedToots.list}
|
||||
onUnknownThread={pinnedToots.getPath}
|
||||
onChangeToot={pinnedToots.set}
|
||||
/>
|
||||
<Divider />
|
||||
</Show>
|
||||
<TootList
|
||||
threads={pinnedToots.list}
|
||||
onUnknownThread={pinnedToots.getPath}
|
||||
onChangeToot={pinnedToots.set}
|
||||
id={recentTootListId}
|
||||
threads={recentToots.list}
|
||||
onUnknownThread={recentToots.getPath}
|
||||
onChangeToot={recentToots.set}
|
||||
/>
|
||||
<Divider />
|
||||
</Show>
|
||||
<TootList
|
||||
id={recentTootListId}
|
||||
threads={recentToots.list}
|
||||
onUnknownThread={recentToots.getPath}
|
||||
onChangeToot={recentToots.set}
|
||||
/>
|
||||
</TimeSourceProvider>
|
||||
</TimeSourceProvider>
|
||||
</ItemSelectionProvider>
|
||||
|
||||
<Show when={!recentTootChunk()?.done}>
|
||||
<div
|
||||
|
|
|
@ -26,11 +26,18 @@ import { useStore } from "@nanostores/solid";
|
|||
import TrendTimelinePanel from "./TrendTimelinePanel";
|
||||
import TimelinePanel from "./TimelinePanel";
|
||||
import { useSessions } from "../masto/clients";
|
||||
import {
|
||||
createSingluarItemSelection,
|
||||
default as ItemSelectionProvider,
|
||||
} from "./toots/ItemSelectionProvider";
|
||||
|
||||
const Home: ParentComponent = (props) => {
|
||||
let panelList: HTMLDivElement;
|
||||
useDocumentTitle("Timelines");
|
||||
const now = createTimeSource();
|
||||
const [, selectionState] = createSingluarItemSelection(
|
||||
undefined as string | undefined,
|
||||
);
|
||||
|
||||
const settings$ = useStore($settings);
|
||||
|
||||
|
@ -115,7 +122,6 @@ const Home: ParentComponent = (props) => {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
css`
|
||||
.tab-panel {
|
||||
overflow: visible auto;
|
||||
|
@ -195,40 +201,42 @@ const Home: ParentComponent = (props) => {
|
|||
</AppBar>
|
||||
}
|
||||
>
|
||||
<TimeSourceProvider value={now}>
|
||||
<Show when={!!client()}>
|
||||
<div
|
||||
class="panel-list"
|
||||
ref={panelList!}
|
||||
onScroll={requestRecalculateTabIndicator}
|
||||
>
|
||||
<div class="tab-panel">
|
||||
<div>
|
||||
<TimelinePanel
|
||||
client={client()}
|
||||
name="home"
|
||||
prefetch={prefetching()}
|
||||
/>
|
||||
<ItemSelectionProvider value={selectionState}>
|
||||
<TimeSourceProvider value={now}>
|
||||
<Show when={!!client()}>
|
||||
<div
|
||||
class="panel-list"
|
||||
ref={panelList!}
|
||||
onScroll={requestRecalculateTabIndicator}
|
||||
>
|
||||
<div class="tab-panel">
|
||||
<div>
|
||||
<TimelinePanel
|
||||
client={client()}
|
||||
name="home"
|
||||
prefetch={prefetching()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-panel">
|
||||
<div>
|
||||
<TrendTimelinePanel client={client()} />
|
||||
<div class="tab-panel">
|
||||
<div>
|
||||
<TrendTimelinePanel client={client()} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-panel">
|
||||
<div>
|
||||
<TimelinePanel
|
||||
client={client()}
|
||||
name="public"
|
||||
prefetch={prefetching()}
|
||||
/>
|
||||
<div class="tab-panel">
|
||||
<div>
|
||||
<TimelinePanel
|
||||
client={client()}
|
||||
name="public"
|
||||
prefetch={prefetching()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</Show>
|
||||
</TimeSourceProvider>
|
||||
</Show>
|
||||
</TimeSourceProvider>
|
||||
</ItemSelectionProvider>
|
||||
</Scaffold>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
type Component,
|
||||
type JSX,
|
||||
Show,
|
||||
createRenderEffect,
|
||||
createSignal,
|
||||
type Setter,
|
||||
createContext,
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
Index,
|
||||
createMemo,
|
||||
For,
|
||||
createUniqueId,
|
||||
} from "solid-js";
|
||||
import { type mastodon } from "masto";
|
||||
import { vibrate } from "~platform/hardware";
|
||||
|
@ -21,6 +22,7 @@ import cardStyle from "~material/cards.module.css";
|
|||
import type { ThreadNode } from "../masto/timelines";
|
||||
import { useNavigator } from "~platform/StackedRouter";
|
||||
import { ANIM_CURVE_STD } from "~material/theme";
|
||||
import { useItemSelection } from "./toots/ItemSelectionProvider";
|
||||
|
||||
function durationOf(rect0: DOMRect, rect1: DOMRect) {
|
||||
const distancelt = Math.sqrt(
|
||||
|
@ -45,6 +47,9 @@ function positionTootInThread(index: number, threadLength: number) {
|
|||
return "middle";
|
||||
}
|
||||
|
||||
/**
|
||||
* Full-feature toot list.
|
||||
*/
|
||||
const TootList: Component<{
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
id?: string;
|
||||
|
@ -53,7 +58,7 @@ const TootList: Component<{
|
|||
onChangeToot: (id: string, value: mastodon.v1.Status) => void;
|
||||
}> = (props) => {
|
||||
const session = useDefaultSession();
|
||||
const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
|
||||
const [isExpanded, setExpanded] = useItemSelection();
|
||||
const { push } = useNavigator();
|
||||
|
||||
const onBookmark = async (status: mastodon.v1.Status) => {
|
||||
|
@ -190,7 +195,7 @@ const TootList: Component<{
|
|||
event.currentTarget,
|
||||
);
|
||||
|
||||
if (actionableElement && checkIsExpended(status)) {
|
||||
if (actionableElement && isExpanded(event.currentTarget.id)) {
|
||||
if (actionableElement.dataset.action === "acct") {
|
||||
event.stopPropagation();
|
||||
|
||||
|
@ -214,18 +219,13 @@ const TootList: Component<{
|
|||
}
|
||||
|
||||
// else if (!actionableElement || !checkIsExpended(status) || <rel is not one of known action>)
|
||||
if (status.id !== expandedThreadId()) {
|
||||
setExpandedThreadId((x) => (x ? undefined : status.id));
|
||||
if (!isExpanded(event.currentTarget.id)) {
|
||||
setExpanded(event.currentTarget.id);
|
||||
} else {
|
||||
openFullScreenToot(status, event.currentTarget as HTMLElement);
|
||||
}
|
||||
};
|
||||
|
||||
const checkIsExpendedId = createSelector(expandedThreadId);
|
||||
|
||||
const checkIsExpended = (status: mastodon.v1.Status) =>
|
||||
checkIsExpendedId(status.id);
|
||||
|
||||
const reply = (
|
||||
status: mastodon.v1.Status,
|
||||
event: { currentTarget: HTMLElement },
|
||||
|
@ -234,10 +234,7 @@ const TootList: Component<{
|
|||
openFullScreenToot(status, element, true);
|
||||
};
|
||||
|
||||
const vote = async (
|
||||
status: mastodon.v1.Status,
|
||||
votes: readonly number[]
|
||||
) => {
|
||||
const vote = async (status: mastodon.v1.Status, votes: readonly number[]) => {
|
||||
const client = session()?.client;
|
||||
if (!client) return;
|
||||
|
||||
|
@ -271,13 +268,15 @@ const TootList: Component<{
|
|||
return <p>Oops: {String(err)}</p>;
|
||||
}}
|
||||
>
|
||||
<TootEnvProvider value={{
|
||||
boost: toggleBoost,
|
||||
bookmark: onBookmark,
|
||||
favourite: toggleFavourite,
|
||||
reply: reply,
|
||||
vote: vote
|
||||
}}>
|
||||
<TootEnvProvider
|
||||
value={{
|
||||
boost: toggleBoost,
|
||||
bookmark: onBookmark,
|
||||
favourite: toggleFavourite,
|
||||
reply: reply,
|
||||
vote: vote,
|
||||
}}
|
||||
>
|
||||
<div ref={props.ref} id={props.id} class="toot-list">
|
||||
<For each={props.threads}>
|
||||
{(threadId, threadIdx) => {
|
||||
|
@ -291,11 +290,13 @@ const TootList: Component<{
|
|||
<Index each={thread()}>
|
||||
{(threadNode, index) => {
|
||||
const status = () => threadNode().value;
|
||||
const id = createUniqueId();
|
||||
|
||||
return (
|
||||
<RegularToot
|
||||
data-status-id={status().id}
|
||||
data-thread-sort={index}
|
||||
id={id}
|
||||
status={status()}
|
||||
thread={
|
||||
threadLength() > 1
|
||||
|
@ -303,8 +304,8 @@ const TootList: Component<{
|
|||
: undefined
|
||||
}
|
||||
class={cardStyle.card}
|
||||
evaluated={checkIsExpended(status())}
|
||||
actionable={checkIsExpended(status())}
|
||||
evaluated={isExpanded(id)}
|
||||
actionable={isExpanded(id)}
|
||||
onClick={[onItemClick, status()]}
|
||||
/>
|
||||
);
|
||||
|
|
35
src/timelines/toots/ItemSelectionProvider.ts
Normal file
35
src/timelines/toots/ItemSelectionProvider.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import {
|
||||
createContext,
|
||||
createSelector,
|
||||
createSignal,
|
||||
useContext,
|
||||
type Accessor,
|
||||
} from "solid-js";
|
||||
|
||||
export type ItemSelectionState<T> = [(value: T) => boolean, (value: T) => void];
|
||||
|
||||
const ItemSelectionContext = /* @__PURE__ */ createContext<
|
||||
ItemSelectionState<any>
|
||||
>([() => false, () => undefined]);
|
||||
|
||||
export function createSingluarItemSelection<T extends {}>(
|
||||
intial?: T,
|
||||
): readonly [Accessor<T | undefined>, ItemSelectionState<T | undefined>] {
|
||||
const [value, setValue] = createSignal<T | undefined>(intial);
|
||||
|
||||
const select = createSelector(value, (a, b) =>
|
||||
typeof b !== "undefined" ? a === b : false,
|
||||
);
|
||||
|
||||
const toggle = (v: T | undefined) => {
|
||||
setValue((o) => (o ? undefined : v));
|
||||
};
|
||||
|
||||
return [value, [select, toggle]];
|
||||
}
|
||||
|
||||
export function useItemSelection() {
|
||||
return useContext(ItemSelectionContext);
|
||||
}
|
||||
|
||||
export default ItemSelectionContext.Provider;
|
Loading…
Add table
Reference in a new issue