mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-21 08:01:31 +03:00
refactor(mobile): determine the currently active tab through a persistent state (#9018)
close AF-1868 Only tap on specific tab can change active tab
This commit is contained in:
parent
f073df3ee5
commit
8fe188e773
@ -1,51 +1,29 @@
|
||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
||||
import { JournalService } from '@affine/core/modules/journal';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import track from '@affine/track';
|
||||
import { EditIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
DocsService,
|
||||
useLiveData,
|
||||
useServices,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { tabItem } from './styles.css';
|
||||
import type { AppTabCustomFCProps } from './data';
|
||||
import { TabItem } from './tab-item';
|
||||
|
||||
export const AppTabCreate = () => {
|
||||
const { docsService, workbenchService, workspaceService, journalService } =
|
||||
useServices({
|
||||
DocsService,
|
||||
WorkbenchService,
|
||||
WorkspaceService,
|
||||
JournalService,
|
||||
});
|
||||
const workbench = workbenchService.workbench;
|
||||
export const AppTabCreate = ({ tab }: AppTabCustomFCProps) => {
|
||||
const workspaceService = useService(WorkspaceService);
|
||||
const currentWorkspace = workspaceService.workspace;
|
||||
const location = useLiveData(workbench.location$);
|
||||
const pageHelper = usePageHelper(currentWorkspace.docCollection);
|
||||
|
||||
const maybeDocId = location.pathname.split('/')[1].split('?')[0];
|
||||
const doc = useLiveData(docsService.list.doc$(maybeDocId));
|
||||
const journalDate = useLiveData(journalService.journalDate$(maybeDocId));
|
||||
const isActive = !!doc && !journalDate;
|
||||
|
||||
const createPage = useCallback(() => {
|
||||
const createPage = useCallback(
|
||||
(isActive: boolean) => {
|
||||
if (isActive) return;
|
||||
pageHelper.createPage(undefined, true);
|
||||
track.$.navigationPanel.$.createDoc();
|
||||
}, [isActive, pageHelper]);
|
||||
},
|
||||
[pageHelper]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={tabItem}
|
||||
data-active={isActive}
|
||||
role="tab"
|
||||
aria-label="New Page"
|
||||
onClick={createPage}
|
||||
>
|
||||
<TabItem id={tab.key} onClick={createPage} label="New Page">
|
||||
<EditIcon />
|
||||
</div>
|
||||
</TabItem>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,46 @@
|
||||
import { AllDocsIcon, MobileHomeIcon } from '@blocksuite/icons/rc';
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { AppTabCreate } from './create';
|
||||
import { AppTabJournal } from './journal';
|
||||
|
||||
interface AppTabBase {
|
||||
key: string;
|
||||
onClick?: (framework: Framework, isActive: boolean) => void;
|
||||
}
|
||||
export interface AppTabLink extends AppTabBase {
|
||||
Icon: React.FC;
|
||||
to: string;
|
||||
LinkComponent?: React.FC;
|
||||
}
|
||||
|
||||
export interface AppTabCustom extends AppTabBase {
|
||||
custom: (props: AppTabCustomFCProps) => React.ReactNode;
|
||||
}
|
||||
|
||||
export type Tab = AppTabLink | AppTabCustom;
|
||||
|
||||
export interface AppTabCustomFCProps {
|
||||
tab: Tab;
|
||||
}
|
||||
|
||||
export const tabs: Tab[] = [
|
||||
{
|
||||
key: 'home',
|
||||
to: '/home',
|
||||
Icon: MobileHomeIcon,
|
||||
},
|
||||
{
|
||||
key: 'all',
|
||||
to: '/all',
|
||||
Icon: AllDocsIcon,
|
||||
},
|
||||
{
|
||||
key: 'journal',
|
||||
custom: AppTabJournal,
|
||||
},
|
||||
{
|
||||
key: 'new',
|
||||
custom: AppTabCreate,
|
||||
},
|
||||
];
|
@ -1,59 +1,14 @@
|
||||
import { SafeArea } from '@affine/component';
|
||||
import {
|
||||
WorkbenchLink,
|
||||
WorkbenchService,
|
||||
} from '@affine/core/modules/workbench';
|
||||
import { AllDocsIcon, MobileHomeIcon } from '@blocksuite/icons/rc';
|
||||
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||
import React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import type { Location } from 'react-router-dom';
|
||||
|
||||
import { VirtualKeyboardService } from '../../modules/virtual-keyboard/services/virtual-keyboard';
|
||||
import { AppTabCreate } from './create';
|
||||
import { AppTabJournal } from './journal';
|
||||
import { type AppTabLink, tabs } from './data';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
interface AppTabBaseProps {
|
||||
key: string;
|
||||
}
|
||||
interface AppTabLinkProps extends AppTabBaseProps {
|
||||
Icon: React.FC;
|
||||
to: string;
|
||||
LinkComponent?: React.FC;
|
||||
isActive?: (location: Location) => boolean;
|
||||
}
|
||||
interface AppTabCustomProps extends AppTabBaseProps {
|
||||
node: React.ReactNode;
|
||||
}
|
||||
|
||||
type Route = AppTabLinkProps | AppTabCustomProps;
|
||||
|
||||
const routes: Route[] = [
|
||||
{
|
||||
key: 'home',
|
||||
to: '/home',
|
||||
Icon: MobileHomeIcon,
|
||||
},
|
||||
{
|
||||
key: 'all',
|
||||
to: '/all',
|
||||
Icon: AllDocsIcon,
|
||||
isActive: location =>
|
||||
location.pathname === '/all' ||
|
||||
location.pathname.startsWith('/collection') ||
|
||||
location.pathname.startsWith('/tag'),
|
||||
},
|
||||
{
|
||||
key: 'journal',
|
||||
node: <AppTabJournal />,
|
||||
},
|
||||
{
|
||||
key: 'new',
|
||||
node: <AppTabCreate />,
|
||||
},
|
||||
];
|
||||
import { TabItem } from './tab-item';
|
||||
|
||||
export const AppTabs = ({ background }: { background?: string }) => {
|
||||
const virtualKeyboardService = useService(VirtualKeyboardService);
|
||||
@ -72,12 +27,14 @@ export const AppTabs = ({ background }: { background?: string }) => {
|
||||
}}
|
||||
>
|
||||
<ul className={styles.appTabsInner} id="app-tabs" role="tablist">
|
||||
{routes.map(route => {
|
||||
if ('to' in route) {
|
||||
return <AppTabLink route={route} key={route.key} />;
|
||||
{tabs.map(tab => {
|
||||
if ('to' in tab) {
|
||||
return <AppTabLink route={tab} key={tab.key} />;
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment key={route.key}>{route.node}</React.Fragment>
|
||||
<React.Fragment key={tab.key}>
|
||||
{<tab.custom tab={tab} />}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
})}
|
||||
@ -87,27 +44,19 @@ export const AppTabs = ({ background }: { background?: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const AppTabLink = ({ route }: { route: AppTabLinkProps }) => {
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const location = useLiveData(workbench.location$);
|
||||
const AppTabLink = ({ route }: { route: AppTabLink }) => {
|
||||
const Link = route.LinkComponent || WorkbenchLink;
|
||||
|
||||
const isActive = route.isActive
|
||||
? route.isActive(location)
|
||||
: location.pathname === route.to;
|
||||
return (
|
||||
<Link
|
||||
data-active={isActive}
|
||||
className={styles.tabItem}
|
||||
to={route.to}
|
||||
key={route.to}
|
||||
className={styles.tabItem}
|
||||
role="tab"
|
||||
aria-label={route.to.slice(1)}
|
||||
replaceHistory
|
||||
>
|
||||
<li style={{ lineHeight: 0 }}>
|
||||
<TabItem id={route.key} label={route.to.slice(1)}>
|
||||
<route.Icon />
|
||||
</li>
|
||||
</TabItem>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
@ -6,9 +6,10 @@ import { TodayIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { tabItem } from './styles.css';
|
||||
import type { AppTabCustomFCProps } from './data';
|
||||
import { TabItem } from './tab-item';
|
||||
|
||||
export const AppTabJournal = () => {
|
||||
export const AppTabJournal = ({ tab }: AppTabCustomFCProps) => {
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const location = useLiveData(workbench.location$);
|
||||
const journalService = useService(JournalService);
|
||||
@ -26,14 +27,8 @@ export const AppTabJournal = () => {
|
||||
const Icon = journalDate ? JournalIcon : TodayIcon;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={tabItem}
|
||||
onClick={handleOpenToday}
|
||||
data-active={!!journalDate}
|
||||
role="tab"
|
||||
aria-label="Journal"
|
||||
>
|
||||
<TabItem onClick={handleOpenToday} id={tab.key} label="Journal">
|
||||
<Icon />
|
||||
</div>
|
||||
</TabItem>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,44 @@
|
||||
import {
|
||||
GlobalCacheService,
|
||||
LiveData,
|
||||
useLiveData,
|
||||
useService,
|
||||
} from '@toeverything/infra';
|
||||
import { type PropsWithChildren, useCallback, useMemo } from 'react';
|
||||
|
||||
import { tabItem } from './styles.css';
|
||||
|
||||
export interface TabItemProps extends PropsWithChildren {
|
||||
id: string;
|
||||
label: string;
|
||||
onClick?: (isActive: boolean) => void;
|
||||
}
|
||||
|
||||
const cacheKey = 'activeAppTabId';
|
||||
export const TabItem = ({ id, label, children, onClick }: TabItemProps) => {
|
||||
const globalCache = useService(GlobalCacheService).globalCache;
|
||||
const activeTabId$ = useMemo(
|
||||
() => LiveData.from(globalCache.watch(cacheKey), 'home'),
|
||||
[globalCache]
|
||||
);
|
||||
const activeTabId = useLiveData(activeTabId$) ?? 'home';
|
||||
|
||||
const isActive = id === activeTabId;
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
globalCache.set(cacheKey, id);
|
||||
onClick?.(isActive);
|
||||
}, [globalCache, id, isActive, onClick]);
|
||||
|
||||
return (
|
||||
<li
|
||||
className={tabItem}
|
||||
role="tab"
|
||||
aria-label={label}
|
||||
data-active={isActive}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user