From 0e8405ffca9c33e679f3d3ba707eb01d511bcbe6 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Thu, 28 Mar 2024 15:20:50 -0700 Subject: [PATCH] Rework App Navigation, combined with my profile --- .../app/components/sidebar-account.tsx | 4 +- .../packages/app/components/sidebar-base.tsx | 97 +++++++------- .../packages/app/components/sidebar-group.tsx | 4 +- .../packages/app/components/sidebar-main.tsx | 98 +++++++++++--- .../app/components/titlebar-title.tsx | 12 ++ frontend/packages/app/models/documents.ts | 52 ++++++-- frontend/packages/app/pages/explore.tsx | 103 +++++++++++++++ frontend/packages/app/pages/groups.tsx | 34 +++-- frontend/packages/app/pages/main.tsx | 6 + .../app/pages/publication-list-page.tsx | 120 +++++++++++++++--- frontend/packages/app/utils/routes.tsx | 9 +- 11 files changed, 430 insertions(+), 109 deletions(-) create mode 100644 frontend/packages/app/pages/explore.tsx diff --git a/frontend/packages/app/components/sidebar-account.tsx b/frontend/packages/app/components/sidebar-account.tsx index 32d79286a..715258870 100644 --- a/frontend/packages/app/components/sidebar-account.tsx +++ b/frontend/packages/app/components/sidebar-account.tsx @@ -37,7 +37,9 @@ export function AccountSidebar({ keepPreviousData: false, }, ) - const frontDocEmbeds = usePublicationEmbeds(frontDoc.data, !!frontDoc.data) + const frontDocEmbeds = usePublicationEmbeds(frontDoc.data, !!frontDoc.data, { + skipCards: true, + }) const isFeedActive = route.key === 'account-feed' && route.accountId === accountId const activeBlock = accountRoute?.blockId diff --git a/frontend/packages/app/components/sidebar-base.tsx b/frontend/packages/app/components/sidebar-base.tsx index ad041d935..920d13dc6 100644 --- a/frontend/packages/app/components/sidebar-base.tsx +++ b/frontend/packages/app/components/sidebar-base.tsx @@ -86,13 +86,16 @@ export function getRouteAccountId( ): string | null { let activeAccountId: string | null = null if (route.key === 'account') { + if (route.accountId === myAccount?.id) return null activeAccountId = route.accountId } else if (route.key === 'account-feed') { + if (route.accountId === myAccount?.id) return null activeAccountId = route.accountId } else if (route.key === 'account-content') { + if (route.accountId === myAccount?.id) return null activeAccountId = route.accountId } else if (route.key === 'draft' && route.isProfileDocument) { - return myAccount?.id || null + return null } return activeAccountId } @@ -339,12 +342,12 @@ export function SidebarItem({ export function MyAccountItem({ account, onRoute, + active, }: { account?: Account onRoute: (route: NavRoute) => void + active: boolean }) { - const route = useNavRoute() - const active = route.key == 'account' && route.accountId == account?.id return ( void + active: boolean }) { const route = useNavRoute() const account = useAccount(accountId) @@ -415,7 +420,7 @@ export function PinnedAccount({ onPress() // navigate({key: 'account', accountId}) }} - active={route.key == 'account' && route.accountId == accountId} + active={active} color={isPinned ? undefined : '$color11'} icon={ query.data) diff --git a/frontend/packages/app/components/sidebar-group.tsx b/frontend/packages/app/components/sidebar-group.tsx index b11940bf5..e935060a8 100644 --- a/frontend/packages/app/components/sidebar-group.tsx +++ b/frontend/packages/app/components/sidebar-group.tsx @@ -69,7 +69,9 @@ export function GroupSidebar({ id: frontDocId?.qid, version: frontDocId?.version || undefined, }) - const frontDocEmbeds = usePublicationEmbeds(frontDoc.data, !!frontDoc.data) + const frontDocEmbeds = usePublicationEmbeds(frontDoc.data, !!frontDoc.data, { + skipCards: true, + }) const activeBlock = groupRoute?.blockId const frontDocOutline = getDocOutline( frontDoc?.data?.document?.children || [], diff --git a/frontend/packages/app/components/sidebar-main.tsx b/frontend/packages/app/components/sidebar-main.tsx index cd59d69bb..3103601b6 100644 --- a/frontend/packages/app/components/sidebar-main.tsx +++ b/frontend/packages/app/components/sidebar-main.tsx @@ -5,14 +5,18 @@ import { stringArrayMatch, } from '@mintter/shared' import {Home, Separator, toast, Tooltip, View, YGroup} from '@mintter/ui' -import {Contact, FileText, Library} from '@tamagui/lucide-icons' +import {Contact, FileText, Library, Sparkles} from '@tamagui/lucide-icons' import {useMyAccount} from '../models/accounts' +import {usePublication, usePublicationEmbeds} from '../models/documents' import {usePins} from '../models/pins' import {useHmIdToAppRouteResolver, useNavRoute} from '../utils/navigation' import {useNavigate} from '../utils/useNavigate' import {CreateGroupButton} from './new-group' import { + activeDocOutline, GenericSidebarContainer, + getDocOutline, + getRouteAccountId, getRouteGroupId, MyAccountItem, NewDocumentButton, @@ -31,6 +35,7 @@ export function MainAppSidebar({ }) { const route = useNavRoute() const navigate = useNavigate() + const replace = useNavigate('replace') const account = useMyAccount() const pins = usePins() @@ -73,13 +78,58 @@ export function MainAppSidebar({ ) ? null : activeDocId - const accountRoute = route.key === 'account' ? route : null - const activeAccountId = accountRoute?.accountId + const myAccount = useMyAccount() + const activeAccountId = getRouteAccountId(route, myAccount.data) const unpinnedActiveAccountId = pins.data?.accounts.find( (pinned) => pinned === activeAccountId, ) + ? null + : myAccount.data?.id === activeAccountId ? null : activeAccountId + const accountRoute = route.key === 'account' ? route : null + const activeBlock = accountRoute?.blockId + const myProfileDoc = usePublication( + { + id: myAccount.data?.profile?.rootDocument, + }, + { + keepPreviousData: false, + }, + ) + const myProfileDocEmbeds = usePublicationEmbeds( + myProfileDoc.data, + !!myProfileDoc.data, + {skipCards: true}, + ) + const frontDocOutline = getDocOutline( + myProfileDoc?.data?.document?.children || [], + myProfileDocEmbeds, + ) + const {outlineContent, isBlockActive} = activeDocOutline( + frontDocOutline, + activeBlock, + myProfileDocEmbeds, + (blockId) => { + const myAccountId = myAccount.data?.id + if (!myAccountId) return + const accountRoute = + route.key == 'account' && myAccountId === route.accountId ? route : null + if (!accountRoute) { + navigate({ + key: 'account', + accountId: myAccountId, + blockId, + }) + } else { + replace({ + ...accountRoute, + blockId, + }) + } + }, + navigate, + ) return ( {account.data && ( - + )} + {outlineContent} { - navigate({key: 'documents', tab: 'trusted'}) + navigate({key: 'documents', tab: 'mine'}) }} title="Documents" bold @@ -248,19 +307,18 @@ export function MainAppSidebar({ ] }) .flat()} - - {/* - { - navigate({key: 'documents', tab: 'drafts' }) - }} - icon={Draft} - title="Drafts" - bold - /> - */} + + { + navigate({key: 'explore', tab: 'docs'}) + }} + title="Explore Content" + bold + icon={Sparkles} + rightHover={[]} + /> + - {unpinnedActiveAccountId && accountRoute ? ( + {unpinnedActiveAccountId ? ( { onSelectAccountId?.(unpinnedActiveAccountId) }} accountId={unpinnedActiveAccountId} isPinned={false} + active={true} /> ) : null} {pins.data?.accounts.map((accountId) => { @@ -291,6 +350,7 @@ export function MainAppSidebar({ navigate({key: 'account', accountId}) } }} + active={accountId === activeAccountId} isPinned={true} accountId={accountId} key={accountId} diff --git a/frontend/packages/app/components/titlebar-title.tsx b/frontend/packages/app/components/titlebar-title.tsx index 0448f0b7c..7d549a5da 100644 --- a/frontend/packages/app/components/titlebar-title.tsx +++ b/frontend/packages/app/components/titlebar-title.tsx @@ -15,6 +15,7 @@ import { TitleText, XStack, } from '@mintter/ui' +import {Sparkles} from '@tamagui/lucide-icons' import {useEffect} from 'react' import {useAccount} from '../models/accounts' import {useGroup} from '../models/groups' @@ -102,6 +103,17 @@ export function TitleContent({size = '$4'}: {size?: FontSizeTokens}) { ) } + if (route.key === 'explore') { + return ( + <> + + + Explore: {route.tab === 'docs' ? 'Documents' : 'Groups'} + + + ) + } + if (route.key === 'group' || route.key === 'group-feed') { return ( <> diff --git a/frontend/packages/app/models/documents.ts b/frontend/packages/app/models/documents.ts index a68a80949..fcbc49685 100644 --- a/frontend/packages/app/models/documents.ts +++ b/frontend/packages/app/models/documents.ts @@ -233,10 +233,14 @@ type ListedEmbed = { refId: UnpackedHypermediaId } -function extractRefs(children: HMBlockNode[]): ListedEmbed[] { +function extractRefs( + children: HMBlockNode[], + skipCards?: boolean, +): ListedEmbed[] { let refs: ListedEmbed[] = [] function extractRefsFromBlock(block: HMBlockNode) { if (block.block?.type === 'embed' && block.block.ref) { + if (block.block.attributes?.view === 'card' && skipCards) return const refId = unpackHmId(block.block.ref) if (refId) refs.push({ @@ -277,6 +281,7 @@ export type EmbedsContent = Record< export function usePublicationEmbeds( pub: HMPublication | undefined, enabled?: boolean, + opts?: {skipCards: boolean}, ): EmbedsContent { const {queryPublications, queryGroups, queryAccounts} = useMemo(() => { if (!enabled) @@ -290,15 +295,17 @@ export function usePublicationEmbeds( blockId: string refId: UnpackedHypermediaId }[] = [] - extractRefs(pub?.document?.children || []).forEach(({refId, blockId}) => { - if (refId.type === 'a') { - queryAccounts.push({blockId, refId}) - } else if (refId.type === 'g') { - queryGroups.push({blockId, refId}) - } else if (refId.type === 'd') { - queryPublications.push({blockId, refId}) - } - }) + extractRefs(pub?.document?.children || [], opts?.skipCards).forEach( + ({refId, blockId}) => { + if (refId.type === 'a') { + queryAccounts.push({blockId, refId}) + } else if (refId.type === 'g') { + queryGroups.push({blockId, refId}) + } else if (refId.type === 'd') { + queryPublications.push({blockId, refId}) + } + }, + ) return { queryPublications, queryGroups, @@ -1357,10 +1364,33 @@ function observeBlocks( }) } -export function useAccountPublications(accountId: string) { +export function useAccountPublicationFullList( + accountId: string | undefined, + opts?: UseQueryOptions, +) { + const pubList = useAccountPublications(accountId) + const accounts = useAllAccounts() + const data = useMemo(() => { + function lookupAccount(accountId: string | undefined) { + if (!accountId) return undefined + return accounts.data?.accounts.find((acc) => acc.id === accountId) + } + return pubList.data?.publications.map((pub) => { + return { + publication: pub, + author: lookupAccount(pub?.document?.author), + editors: pub?.document?.editors?.map(lookupAccount) || [], + } + }) + }, [pubList.data, accounts.data]) + return {...pubList, data} +} + +export function useAccountPublications(accountId?: string | undefined) { const grpcClient = useGRPCClient() return useQuery({ queryKey: [queryKeys.GET_ACCOUNT_PUBLICATIONS, accountId], + enabled: !!accountId, queryFn: async () => { const result = await grpcClient.publications.listAccountPublications({ accountId, diff --git a/frontend/packages/app/pages/explore.tsx b/frontend/packages/app/pages/explore.tsx new file mode 100644 index 000000000..3c6ed8682 --- /dev/null +++ b/frontend/packages/app/pages/explore.tsx @@ -0,0 +1,103 @@ +import Footer from '@mintter/app/components/footer' +import {unpackHmId} from '@mintter/shared' +import { + Container, + List, + PageContainer, + RadioButtons, + Spinner, + Text, + XStack, + YStack, +} from '@mintter/ui' +import {Book, FileText} from '@tamagui/lucide-icons' +import {useCopyGatewayReference} from '../components/copy-gateway-reference' +import {MainWrapperNoScroll} from '../components/main-wrapper' +import {useAllGroups} from '../models/groups' +import {useNavRoute} from '../utils/navigation' +import {useNavigate} from '../utils/useNavigate' +import {GroupListItem} from './groups' +import {PublicationsList} from './publication-list-page' + +const documentTabsOptions = [ + {key: 'docs', label: 'Documents', icon: FileText}, + {key: 'groups', label: 'Groups', icon: Book}, +] as const + +function ExploreTabs() { + const route = useNavRoute() + if (route.key !== 'explore') throw new Error(`invalid route: ${route.key}`) + const replace = useNavigate('replace') + return ( + + + { + replace({...route, tab}) + }} + /> + + + ) +} + +export default function ExplorePage() { + const route = useNavRoute() + if (route.key !== 'explore') throw new Error(`invalid route: ${route.key}`) + if (route.tab === 'docs') return + if (route.tab === 'groups') return +} + +function ExploreDocsPage() { + return ( + <> + + } trustedOnly={false} /> + +