Compare commits
3 commits
88ff705d68
...
ad0076156b
Author | SHA1 | Date | |
---|---|---|---|
|
ad0076156b | ||
|
9499182a8d | ||
|
dafc2c47a8 |
7 changed files with 129 additions and 79 deletions
|
@ -33,7 +33,7 @@ Don't choose algorithm solely on the time complexity. GUI app needs smooth, not
|
||||||
But it comes with cost. Modern browsers are already very smart on rendering. Using `contain`, you are trading onething off for another:
|
But it comes with cost. Modern browsers are already very smart on rendering. Using `contain`, you are trading onething off for another:
|
||||||
|
|
||||||
- `layout` affects the reflow. This property usually won't make large change: mainline browsers already can incrementally reflow.
|
- `layout` affects the reflow. This property usually won't make large change: mainline browsers already can incrementally reflow.
|
||||||
- `style` affacts the style computation, is automatically enabled when using `container` property. Usually won't make large change too, unless you frequently change the styles (and/or your stylesheet is large and/or with complex selectors).
|
- `style` affects the style computation, is automatically enabled when using `container` property. Usually won't make large change too, unless you frequently change the styles (and/or your stylesheet is large and/or with complex selectors), containing the computation may help performance.
|
||||||
- `paint` affects the shading, the pixel-filling process. This is useful - the shading is resource-heavy - but the browser may need more buffers and more time to compose the final frame.
|
- `paint` affects the shading, the pixel-filling process. This is useful - the shading is resource-heavy - but the browser may need more buffers and more time to compose the final frame.
|
||||||
- This containment may increase memory usage.
|
- This containment may increase memory usage.
|
||||||
- `size` says the size is not affected by outside elements and is defined. It hints the user agent can use the pre-defined size and/or cache the computed size (with `auto` keyword).
|
- `size` says the size is not affected by outside elements and is defined. It hints the user agent can use the pre-defined size and/or cache the computed size (with `auto` keyword).
|
||||||
|
|
|
@ -58,6 +58,10 @@ import Menu, { createManagedMenuState } from "~material/Menu";
|
||||||
import { share } from "~platform/share";
|
import { share } from "~platform/share";
|
||||||
import "./Profile.css";
|
import "./Profile.css";
|
||||||
import { useNavigator } from "~platform/StackedRouter";
|
import { useNavigator } from "~platform/StackedRouter";
|
||||||
|
import {
|
||||||
|
createSingluarItemSelection,
|
||||||
|
default as ItemSelectionProvider,
|
||||||
|
} from "../timelines/toots/ItemSelectionProvider";
|
||||||
|
|
||||||
const Profile: Component = () => {
|
const Profile: Component = () => {
|
||||||
const { pop } = useNavigator();
|
const { pop } = useNavigator();
|
||||||
|
@ -70,6 +74,7 @@ const Profile: Component = () => {
|
||||||
}>();
|
}>();
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
const time = createTimeSource();
|
const time = createTimeSource();
|
||||||
|
const [, selectionState] = createSingluarItemSelection();
|
||||||
|
|
||||||
const menuButId = createUniqueId();
|
const menuButId = createUniqueId();
|
||||||
const recentTootListId = createUniqueId();
|
const recentTootListId = createUniqueId();
|
||||||
|
@ -499,8 +504,11 @@ const Profile: Component = () => {
|
||||||
></TootFilterButton>
|
></TootFilterButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ItemSelectionProvider value={selectionState}>
|
||||||
<TimeSourceProvider value={time}>
|
<TimeSourceProvider value={time}>
|
||||||
<Show when={recentTootFilter().pinned && pinnedToots.list.length > 0}>
|
<Show
|
||||||
|
when={recentTootFilter().pinned && pinnedToots.list.length > 0}
|
||||||
|
>
|
||||||
<TootList
|
<TootList
|
||||||
threads={pinnedToots.list}
|
threads={pinnedToots.list}
|
||||||
onUnknownThread={pinnedToots.getPath}
|
onUnknownThread={pinnedToots.getPath}
|
||||||
|
@ -515,6 +523,7 @@ const Profile: Component = () => {
|
||||||
onChangeToot={recentToots.set}
|
onChangeToot={recentToots.set}
|
||||||
/>
|
/>
|
||||||
</TimeSourceProvider>
|
</TimeSourceProvider>
|
||||||
|
</ItemSelectionProvider>
|
||||||
|
|
||||||
<Show when={!recentTootChunk()?.done}>
|
<Show when={!recentTootChunk()?.done}>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -26,11 +26,18 @@ import { useStore } from "@nanostores/solid";
|
||||||
import TrendTimelinePanel from "./TrendTimelinePanel";
|
import TrendTimelinePanel from "./TrendTimelinePanel";
|
||||||
import TimelinePanel from "./TimelinePanel";
|
import TimelinePanel from "./TimelinePanel";
|
||||||
import { useSessions } from "../masto/clients";
|
import { useSessions } from "../masto/clients";
|
||||||
|
import {
|
||||||
|
createSingluarItemSelection,
|
||||||
|
default as ItemSelectionProvider,
|
||||||
|
} from "./toots/ItemSelectionProvider";
|
||||||
|
|
||||||
const Home: ParentComponent = (props) => {
|
const Home: ParentComponent = (props) => {
|
||||||
let panelList: HTMLDivElement;
|
let panelList: HTMLDivElement;
|
||||||
useDocumentTitle("Timelines");
|
useDocumentTitle("Timelines");
|
||||||
const now = createTimeSource();
|
const now = createTimeSource();
|
||||||
|
const [, selectionState] = createSingluarItemSelection(
|
||||||
|
undefined as string | undefined,
|
||||||
|
);
|
||||||
|
|
||||||
const settings$ = useStore($settings);
|
const settings$ = useStore($settings);
|
||||||
|
|
||||||
|
@ -115,7 +122,6 @@ const Home: ParentComponent = (props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
css`
|
css`
|
||||||
.tab-panel {
|
.tab-panel {
|
||||||
overflow: visible auto;
|
overflow: visible auto;
|
||||||
|
@ -195,6 +201,7 @@ const Home: ParentComponent = (props) => {
|
||||||
</AppBar>
|
</AppBar>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<ItemSelectionProvider value={selectionState}>
|
||||||
<TimeSourceProvider value={now}>
|
<TimeSourceProvider value={now}>
|
||||||
<Show when={!!client()}>
|
<Show when={!!client()}>
|
||||||
<div
|
<div
|
||||||
|
@ -229,6 +236,7 @@ const Home: ParentComponent = (props) => {
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</TimeSourceProvider>
|
</TimeSourceProvider>
|
||||||
|
</ItemSelectionProvider>
|
||||||
</Scaffold>
|
</Scaffold>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {
|
||||||
type Component,
|
type Component,
|
||||||
type JSX,
|
type JSX,
|
||||||
Show,
|
Show,
|
||||||
createRenderEffect,
|
|
||||||
createSignal,
|
createSignal,
|
||||||
type Setter,
|
type Setter,
|
||||||
createContext,
|
createContext,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
Index,
|
Index,
|
||||||
createMemo,
|
createMemo,
|
||||||
For,
|
For,
|
||||||
|
createUniqueId,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { type mastodon } from "masto";
|
import { type mastodon } from "masto";
|
||||||
import { vibrate } from "~platform/hardware";
|
import { vibrate } from "~platform/hardware";
|
||||||
|
@ -21,6 +22,7 @@ import cardStyle from "~material/cards.module.css";
|
||||||
import type { ThreadNode } from "../masto/timelines";
|
import type { ThreadNode } from "../masto/timelines";
|
||||||
import { useNavigator } from "~platform/StackedRouter";
|
import { useNavigator } from "~platform/StackedRouter";
|
||||||
import { ANIM_CURVE_STD } from "~material/theme";
|
import { ANIM_CURVE_STD } from "~material/theme";
|
||||||
|
import { useItemSelection } from "./toots/ItemSelectionProvider";
|
||||||
|
|
||||||
function durationOf(rect0: DOMRect, rect1: DOMRect) {
|
function durationOf(rect0: DOMRect, rect1: DOMRect) {
|
||||||
const distancelt = Math.sqrt(
|
const distancelt = Math.sqrt(
|
||||||
|
@ -45,6 +47,9 @@ function positionTootInThread(index: number, threadLength: number) {
|
||||||
return "middle";
|
return "middle";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full-feature toot list.
|
||||||
|
*/
|
||||||
const TootList: Component<{
|
const TootList: Component<{
|
||||||
ref?: Ref<HTMLDivElement>;
|
ref?: Ref<HTMLDivElement>;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -53,7 +58,7 @@ const TootList: Component<{
|
||||||
onChangeToot: (id: string, value: mastodon.v1.Status) => void;
|
onChangeToot: (id: string, value: mastodon.v1.Status) => void;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const session = useDefaultSession();
|
const session = useDefaultSession();
|
||||||
const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
|
const [isExpanded, setExpanded] = useItemSelection();
|
||||||
const { push } = useNavigator();
|
const { push } = useNavigator();
|
||||||
|
|
||||||
const onBookmark = async (status: mastodon.v1.Status) => {
|
const onBookmark = async (status: mastodon.v1.Status) => {
|
||||||
|
@ -190,7 +195,7 @@ const TootList: Component<{
|
||||||
event.currentTarget,
|
event.currentTarget,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (actionableElement && checkIsExpended(status)) {
|
if (actionableElement && isExpanded(event.currentTarget.id)) {
|
||||||
if (actionableElement.dataset.action === "acct") {
|
if (actionableElement.dataset.action === "acct") {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
@ -214,18 +219,13 @@ const TootList: Component<{
|
||||||
}
|
}
|
||||||
|
|
||||||
// else if (!actionableElement || !checkIsExpended(status) || <rel is not one of known action>)
|
// else if (!actionableElement || !checkIsExpended(status) || <rel is not one of known action>)
|
||||||
if (status.id !== expandedThreadId()) {
|
if (!isExpanded(event.currentTarget.id)) {
|
||||||
setExpandedThreadId((x) => (x ? undefined : status.id));
|
setExpanded(event.currentTarget.id);
|
||||||
} else {
|
} else {
|
||||||
openFullScreenToot(status, event.currentTarget as HTMLElement);
|
openFullScreenToot(status, event.currentTarget as HTMLElement);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkIsExpendedId = createSelector(expandedThreadId);
|
|
||||||
|
|
||||||
const checkIsExpended = (status: mastodon.v1.Status) =>
|
|
||||||
checkIsExpendedId(status.id);
|
|
||||||
|
|
||||||
const reply = (
|
const reply = (
|
||||||
status: mastodon.v1.Status,
|
status: mastodon.v1.Status,
|
||||||
event: { currentTarget: HTMLElement },
|
event: { currentTarget: HTMLElement },
|
||||||
|
@ -234,10 +234,7 @@ const TootList: Component<{
|
||||||
openFullScreenToot(status, element, true);
|
openFullScreenToot(status, element, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const vote = async (
|
const vote = async (status: mastodon.v1.Status, votes: readonly number[]) => {
|
||||||
status: mastodon.v1.Status,
|
|
||||||
votes: readonly number[]
|
|
||||||
) => {
|
|
||||||
const client = session()?.client;
|
const client = session()?.client;
|
||||||
if (!client) return;
|
if (!client) return;
|
||||||
|
|
||||||
|
@ -271,13 +268,15 @@ const TootList: Component<{
|
||||||
return <p>Oops: {String(err)}</p>;
|
return <p>Oops: {String(err)}</p>;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TootEnvProvider value={{
|
<TootEnvProvider
|
||||||
|
value={{
|
||||||
boost: toggleBoost,
|
boost: toggleBoost,
|
||||||
bookmark: onBookmark,
|
bookmark: onBookmark,
|
||||||
favourite: toggleFavourite,
|
favourite: toggleFavourite,
|
||||||
reply: reply,
|
reply: reply,
|
||||||
vote: vote
|
vote: vote,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<div ref={props.ref} id={props.id} class="toot-list">
|
<div ref={props.ref} id={props.id} class="toot-list">
|
||||||
<For each={props.threads}>
|
<For each={props.threads}>
|
||||||
{(threadId, threadIdx) => {
|
{(threadId, threadIdx) => {
|
||||||
|
@ -291,11 +290,13 @@ const TootList: Component<{
|
||||||
<Index each={thread()}>
|
<Index each={thread()}>
|
||||||
{(threadNode, index) => {
|
{(threadNode, index) => {
|
||||||
const status = () => threadNode().value;
|
const status = () => threadNode().value;
|
||||||
|
const id = createUniqueId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RegularToot
|
<RegularToot
|
||||||
data-status-id={status().id}
|
data-status-id={status().id}
|
||||||
data-thread-sort={index}
|
data-thread-sort={index}
|
||||||
|
id={id}
|
||||||
status={status()}
|
status={status()}
|
||||||
thread={
|
thread={
|
||||||
threadLength() > 1
|
threadLength() > 1
|
||||||
|
@ -303,8 +304,8 @@ const TootList: Component<{
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
class={cardStyle.card}
|
class={cardStyle.card}
|
||||||
evaluated={checkIsExpended(status())}
|
evaluated={isExpanded(id)}
|
||||||
actionable={checkIsExpended(status())}
|
actionable={isExpanded(id)}
|
||||||
onClick={[onItemClick, status()]}
|
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;
|
|
@ -10,7 +10,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.TootAuthorGroup > .name-grp {
|
.TootAuthorGroup>.name-grp {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
color: var(--tutu-color-secondary-text-on-surface);
|
color: var(--tutu-color-secondary-text-on-surface);
|
||||||
|
@ -22,18 +22,16 @@
|
||||||
>time {
|
>time {
|
||||||
text-align: end;
|
text-align: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
> .name-primary {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.TootAuthorGroup > .name-grp > .name-primary {
|
.RegularToot.expanded .TootAuthorGroup>.name-grp>.name-primary {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TootAuthorGroup>.name-grp>.name-primary {
|
||||||
color: var(--tutu-color-on-surface);
|
color: var(--tutu-color-on-surface);
|
||||||
|
|
||||||
> .acct-mark {
|
>.acct-mark {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
color: var(--tutu-color-secondary-text-on-surface);
|
color: var(--tutu-color-secondary-text-on-surface);
|
||||||
vertical-align: sub;
|
vertical-align: sub;
|
||||||
|
@ -41,7 +39,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.TootAuthorGroup > .avatar {
|
.TootAuthorGroup>.avatar {
|
||||||
width: calc(var(--toot-avatar-size, 40px) - 1px);
|
width: calc(var(--toot-avatar-size, 40px) - 1px);
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue