Skip to content

Commit

Permalink
feat: thread columns
Browse files Browse the repository at this point in the history
  • Loading branch information
mary-ext committed Jan 4, 2024
1 parent 3b34bf9 commit c4f234e
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 215 deletions.
2 changes: 2 additions & 0 deletions app/desktop/components/panes/PaneRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
PANE_TYPE_NOTIFICATIONS,
PANE_TYPE_PROFILE,
PANE_TYPE_SEARCH,
PANE_TYPE_THREAD,
} from '../../globals/panes.ts';

export interface PaneRouterProps {
Expand All @@ -22,6 +23,7 @@ const components: Record<PaneType, Component> = {
[PANE_TYPE_NOTIFICATIONS]: lazy(() => import('./views/NotificationsPane.tsx')),
[PANE_TYPE_PROFILE]: lazy(() => import('./views/ProfilePane.tsx')),
[PANE_TYPE_SEARCH]: lazy(() => import('./views/SearchPane.tsx')),
[PANE_TYPE_THREAD]: lazy(() => import('./views/ThreadPane.tsx')),
};

const PaneRouter = (props: PaneRouterProps) => {
Expand Down
250 changes: 37 additions & 213 deletions app/desktop/components/panes/dialogs/ThreadPaneDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,23 @@
import { For, Match, Show, Suspense, Switch, createMemo, lazy, onMount } from 'solid-js';
import { createMemo } from 'solid-js';

import { XRPCError } from '@externdefs/bluesky-client/xrpc-utils';
import { createQuery } from '@pkg/solid-query';

import type { DID } from '~/api/atp-schema.ts';
import { getRecordId, getRepoId } from '~/api/utils/misc.ts';

import {
BlockedThreadError,
getInitialPostThread,
getPostThread,
getPostThreadKey,
} from '~/api/queries/get-post-thread.ts';
import { SignalizedPost } from '~/api/stores/posts.ts';
import { getInitialPostThread, getPostThread, getPostThreadKey } from '~/api/queries/get-post-thread.ts';

import { SpecificPaneSize } from '../../../globals/panes.ts';
import { preferences } from '../../../globals/settings.ts';
import { PANE_TYPE_THREAD, SpecificPaneSize, type ThreadPaneConfig } from '../../../globals/panes.ts';
import { addPane, preferences } from '../../../globals/settings.ts';

import { Button } from '~/com/primitives/button.ts';
import { IconButton } from '~/com/primitives/icon-button.ts';

import CircularProgress from '~/com/components/CircularProgress.tsx';
import Keyed from '~/com/components/Keyed.ts';
import { LINK_POST, LINK_PROFILE, Link } from '~/com/components/Link.tsx';
import { VirtualContainer } from '~/com/components/VirtualContainer.tsx';
import TableColumnRightAddIcon from '~/com/icons/baseline-table-column-right-add.tsx';

import GenericErrorView from '~/com/components/views/GenericErrorView.tsx';
import PermalinkPost from '~/com/components/views/PermalinkPost.tsx';

import EmbedRecordBlocked from '~/com/components/embeds/EmbedRecordBlocked.tsx';
import EmbedRecordNotFound from '~/com/components/embeds/EmbedRecordNotFound.tsx';
import Post from '~/com/components/items/Post.tsx';

import { usePaneContext } from '../PaneContext.tsx';
import { usePaneContext, usePaneModalState } from '../PaneContext.tsx';
import PaneDialog from '../PaneDialog.tsx';
import PaneDialogHeader from '../PaneDialogHeader.tsx';

const FlattenedThread = lazy(() => import('~/com/components/views/threads/FlattenedThread.tsx'));
const NestedThread = lazy(() => import('~/com/components/views/threads/NestedThread.tsx'));
import ThreadView from '../partials/ThreadView.tsx';

export interface ThreadPaneDialogProps {
/** Expected to be static */
Expand All @@ -48,7 +29,9 @@ export interface ThreadPaneDialogProps {
const ThreadPaneDialog = (props: ThreadPaneDialogProps) => {
const { actor, rkey } = props;

const { pane } = usePaneContext();
const { pane, deck, index } = usePaneContext();
const modal = usePaneModalState();

const ui = preferences.ui;

const size = createMemo(() => {
Expand All @@ -74,194 +57,35 @@ const ThreadPaneDialog = (props: ThreadPaneDialogProps) => {

return (
<PaneDialog>
<PaneDialogHeader title="Thread" />

<div class="min-h-0 grow overflow-y-auto">
<Switch>
<Match when={thread.isLoading}>
<div class="grid h-13 place-items-center">
<CircularProgress />
</div>
</Match>

<Match when={thread.error} keyed>
{(err) => {
if (err instanceof XRPCError && err.error === 'NotFound') {
return (
<div class="p-3">
<EmbedRecordNotFound />
</div>
);
}

if (err instanceof BlockedThreadError) {
const viewer = err.view.author.viewer;

if (viewer?.blocking) {
return (
<div class="p-4">
<div class="mb-4 text-sm">
<p class="font-bold">This post is from a user you blocked</p>
<p class="text-muted-fg">You need to unblock the user to view the post.</p>
</div>

<Link
to={{ type: LINK_PROFILE, actor: actor }}
class={/* @once */ Button({ variant: 'primary' })}
>
View profile
</Link>
</div>
);
}

return (
<div class="p-3">
<EmbedRecordNotFound />
</div>
);
}
<PaneDialogHeader title="Thread">
{(thread.isSuccess || thread.isPlaceholderData) && (
<button
title="Add as column"
onClick={() => {
addPane<ThreadPaneConfig>(
deck,
{
type: PANE_TYPE_THREAD,
uid: pane.uid,
thread: {
actor: actor,
rkey: rkey,
},
},
index() + 1,
);

return <GenericErrorView error={err} onRetry={() => thread.refetch()} />;
modal.close();
}}
</Match>

<Match when={thread.data}>
{(data) => {
return (
<>
<Show
when={(() => {
if (thread.isPlaceholderData) {
const ancestors = data().ancestors;
const first = ancestors.length > 0 && ancestors[0];

if (first && first instanceof SignalizedPost) {
return first.record.value.reply;
}
}
})()}
>
<div class="relative flex h-13 px-4">
<div class="flex w-10 flex-col items-center">
<div class="mt-3 grow border-l-2 border-dashed border-divider" />
</div>
<div class="grid grow place-items-center">
<CircularProgress />
</div>
<div class="w-10"></div>
</div>
</Show>

<For each={data().ancestors}>
{(item) => {
if (item instanceof SignalizedPost) {
// Upwards scroll jank is a lot worse than downwards, so
// we can't set an estimate height here.
return (
<VirtualContainer>
<Post post={item} next prev interactive />
</VirtualContainer>
);
}
class={/* @once */ IconButton({ edge: 'right' })}
>
<TableColumnRightAddIcon />
</button>
)}
</PaneDialogHeader>

const type = item.$type;

if (type === 'overflow') {
const uri = item.uri;
const actor = getRepoId(uri) as DID;
const rkey = getRecordId(uri);

return (
<Link
to={{ type: LINK_POST, actor: actor, rkey: rkey }}
class="flex h-10 w-full items-center gap-3 px-4 hover:bg-secondary/10"
>
<div class="flex h-full w-10 justify-center">
<div class="mt-3 border-l-2 border-dashed border-divider" />
</div>
<span class="text-sm text-accent">Show parent post</span>
</Link>
);
}

if (type === 'app.bsky.feed.defs#notFoundPost') {
return (
<div class="p-3">
<EmbedRecordNotFound />
</div>
);
}

if (type === 'app.bsky.feed.defs#blockedPost') {
return (
<div class="p-3">
<EmbedRecordBlocked
record={
/* @once */ {
uri: item.uri,
blocked: item.blocked,
author: item.author,
}
}
/>
</div>
);
}

return null;
}}
</For>

<div
ref={(node: HTMLElement) => {
onMount(() => {
node.scrollIntoView({ behavior: 'instant' });
});
}}
class="h-[calc(100%-0.75rem)] scroll-m-3"
>
<VirtualContainer>
<Keyed key={data().post}>
<PermalinkPost post={data().post} />
</Keyed>

<hr class="border-divider" />
</VirtualContainer>

<Suspense
fallback={
<div class="grid h-13 place-items-center">
<CircularProgress />
</div>
}
>
{!ui.threadedReplies ? (
<FlattenedThread data={data()} />
) : (
<NestedThread data={data()} />
)}

<Switch>
<Match when={thread.isPlaceholderData}>
<div class="grid h-13 place-items-center">
<CircularProgress />
</div>
</Match>

<Match when>
<div class="grid h-13 place-items-center">
<p class="text-sm text-muted-fg">End of thread</p>
</div>
</Match>
</Switch>
</Suspense>
</div>
</>
);
}}
</Match>
</Switch>
<div class="min-h-0 grow overflow-y-auto">
<ThreadView actor={actor} thread={thread} />
</div>
</PaneDialog>
);
Expand Down
Loading

0 comments on commit c4f234e

Please sign in to comment.