feat(mobile): mobile app fallback skeleton (#8686)

close AF-1533, AF-1532
This commit is contained in:
CatsJuice 2024-11-05 00:35:21 +00:00
parent 5b5dc26abf
commit fe04ab35cc
No known key found for this signature in database
GPG Key ID: 1C1E76924FAFDDE4
9 changed files with 111 additions and 19 deletions

View File

@ -1,6 +1,6 @@
import { AppFallback } from '@affine/core/components/affine/app-container';
import { AffineContext } from '@affine/core/components/context'; import { AffineContext } from '@affine/core/components/context';
import { Telemetry } from '@affine/core/components/telemetry'; import { Telemetry } from '@affine/core/components/telemetry';
import { AppFallback } from '@affine/core/mobile/components';
import { configureMobileModules } from '@affine/core/mobile/modules'; import { configureMobileModules } from '@affine/core/mobile/modules';
import { router } from '@affine/core/mobile/router'; import { router } from '@affine/core/mobile/router';
import { configureCommonModules } from '@affine/core/modules'; import { configureCommonModules } from '@affine/core/modules';

View File

@ -1,6 +1,6 @@
import { AppFallback } from '@affine/core/components/affine/app-container';
import { AffineContext } from '@affine/core/components/context'; import { AffineContext } from '@affine/core/components/context';
import { Telemetry } from '@affine/core/components/telemetry'; import { Telemetry } from '@affine/core/components/telemetry';
import { AppFallback } from '@affine/core/mobile/components';
import { configureMobileModules } from '@affine/core/mobile/modules'; import { configureMobileModules } from '@affine/core/mobile/modules';
import { router } from '@affine/core/mobile/router'; import { router } from '@affine/core/mobile/router';
import { configureCommonModules } from '@affine/core/modules'; import { configureCommonModules } from '@affine/core/modules';

View File

@ -1,6 +1,6 @@
import { AppFallback } from '@affine/core/components/affine/app-container';
import { AffineContext } from '@affine/core/components/context'; import { AffineContext } from '@affine/core/components/context';
import { Telemetry } from '@affine/core/components/telemetry'; import { Telemetry } from '@affine/core/components/telemetry';
import { AppFallback } from '@affine/core/mobile/components';
import { configureMobileModules } from '@affine/core/mobile/modules'; import { configureMobileModules } from '@affine/core/mobile/modules';
import { router } from '@affine/core/mobile/router'; import { router } from '@affine/core/mobile/router';
import { configureCommonModules } from '@affine/core/modules'; import { configureCommonModules } from '@affine/core/modules';

View File

@ -5,5 +5,6 @@ export * from './rename';
export * from './search-input'; export * from './search-input';
export * from './search-result'; export * from './search-result';
export * from './selector'; export * from './selector';
export * from './skeletons';
export * from './user-plan-tag'; export * from './user-plan-tag';
export * from './workspace-selector'; export * from './workspace-selector';

View File

@ -0,0 +1,85 @@
import { SafeArea, Skeleton } from '@affine/component';
import { WorkspaceSelector } from '../workspace-selector';
const SectionTitleFallback = () => {
return (
<div style={{ padding: '0 16px' }}>
<Skeleton
animation="wave"
style={{ height: 16, borderRadius: 4, width: 93 }}
/>
</div>
);
};
const sectionRows = [127, 238, 191, 102];
const Section = () => {
return (
<div style={{ marginBottom: 32 }}>
<SectionTitleFallback />
<div
style={{
padding: '0 16px',
display: 'flex',
flexDirection: 'column',
gap: 24,
marginTop: 24,
}}
>
{sectionRows.map((width, i) => {
return (
<div
style={{ display: 'flex', gap: 16, alignItems: 'center' }}
key={i}
>
<Skeleton
animation="wave"
style={{ width: 23, height: 23, borderRadius: 4 }}
/>
<Skeleton
animation="wave"
style={{ width, height: 16, borderRadius: 4 }}
/>
</div>
);
})}
</div>
</div>
);
};
export const AppFallback = () => {
return (
<SafeArea top bottom style={{ height: '100dvh', overflow: 'hidden' }}>
{/* setting */}
<div style={{ padding: 10, display: 'flex', justifyContent: 'end' }}>
<Skeleton
animation="wave"
style={{ width: 23, height: 23, borderRadius: 4 }}
/>
</div>
{/* workspace card */}
<div style={{ padding: '4px 16px' }}>
<WorkspaceSelector />
</div>
{/* search */}
<div style={{ padding: '10px 16px 15px' }}>
<Skeleton animation="wave" style={{ height: 44, borderRadius: 10 }} />
</div>
{/* recent */}
<SectionTitleFallback />
<div style={{ padding: '16px 16px 32px 16px', display: 'flex', gap: 10 }}>
{[1, 2, 3].map(i => (
<Skeleton
key={i}
animation="wave"
style={{ width: 172, height: 210, borderRadius: 12 }}
/>
))}
</div>
<Section />
<Section />
</SafeArea>
);
};

View File

@ -0,0 +1 @@
export * from './app-fallback';

View File

@ -1,8 +1,9 @@
import { Avatar } from '@affine/component';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info'; import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { ArrowDownSmallIcon } from '@blocksuite/icons/rc'; import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
import { useService, WorkspaceService } from '@toeverything/infra'; import { useServiceOptional, WorkspaceService } from '@toeverything/infra';
import clsx from 'clsx'; import clsx from 'clsx';
import { forwardRef, type HTMLAttributes } from 'react'; import { forwardRef, type HTMLAttributes } from 'react';
@ -15,8 +16,8 @@ export const CurrentWorkspaceCard = forwardRef<
HTMLDivElement, HTMLDivElement,
CurrentWorkspaceCardProps CurrentWorkspaceCardProps
>(function CurrentWorkspaceCard({ onClick, className, ...attrs }, ref) { >(function CurrentWorkspaceCard({ onClick, className, ...attrs }, ref) {
const currentWorkspace = useService(WorkspaceService).workspace; const currentWorkspace = useServiceOptional(WorkspaceService)?.workspace;
const info = useWorkspaceInfo(currentWorkspace.meta); const info = useWorkspaceInfo(currentWorkspace?.meta);
const name = info?.name ?? UNTITLED_WORKSPACE_NAME; const name = info?.name ?? UNTITLED_WORKSPACE_NAME;
return ( return (
@ -26,15 +27,19 @@ export const CurrentWorkspaceCard = forwardRef<
className={clsx(card, className)} className={clsx(card, className)}
{...attrs} {...attrs}
> >
<WorkspaceAvatar {currentWorkspace ? (
key={currentWorkspace.id} <WorkspaceAvatar
meta={currentWorkspace.meta} key={currentWorkspace?.id}
rounded={3} meta={currentWorkspace?.meta}
data-testid="workspace-avatar" rounded={3}
size={40} data-testid="workspace-avatar"
name={name} size={40}
colorfulFallback name={name}
/> colorfulFallback
/>
) : (
<Avatar size={40} rounded={3} colorfulFallback />
)}
<div className={label}> <div className={label}>
{name} {name}
<ArrowDownSmallIcon className={dropdownIcon} /> <ArrowDownSmallIcon className={dropdownIcon} />

View File

@ -1,6 +1,6 @@
import { MobileMenu } from '@affine/component'; import { MobileMenu } from '@affine/component';
import { track } from '@affine/track'; import { track } from '@affine/track';
import { useService, WorkspacesService } from '@toeverything/infra'; import { useServiceOptional, WorkspacesService } from '@toeverything/infra';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { CurrentWorkspaceCard } from './current-card'; import { CurrentWorkspaceCard } from './current-card';
@ -8,7 +8,7 @@ import { SelectorMenu } from './menu';
export const WorkspaceSelector = () => { export const WorkspaceSelector = () => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const workspaceManager = useService(WorkspacesService); const workspaceManager = useServiceOptional(WorkspacesService);
const openMenu = useCallback(() => { const openMenu = useCallback(() => {
track.$.navigationPanel.workspaceList.open(); track.$.navigationPanel.workspaceList.open();
@ -20,7 +20,7 @@ export const WorkspaceSelector = () => {
// revalidate workspace list when open workspace list // revalidate workspace list when open workspace list
useEffect(() => { useEffect(() => {
if (open) workspaceManager.list.revalidate(); if (open) workspaceManager?.list.revalidate();
}, [workspaceManager, open]); }, [workspaceManager, open]);
return ( return (

View File

@ -1,5 +1,4 @@
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
import { AppFallback } from '@affine/core/components/affine/app-container';
import { WorkspaceLayoutProviders } from '@affine/core/components/layouts/workspace-layout'; import { WorkspaceLayoutProviders } from '@affine/core/components/layouts/workspace-layout';
import { SWRConfigProvider } from '@affine/core/components/providers/swr-config-provider'; import { SWRConfigProvider } from '@affine/core/components/providers/swr-config-provider';
import type { Workspace, WorkspaceMetadata } from '@toeverything/infra'; import type { Workspace, WorkspaceMetadata } from '@toeverything/infra';
@ -17,6 +16,7 @@ import {
useState, useState,
} from 'react'; } from 'react';
import { AppFallback } from '../../components';
import { MobileCurrentWorkspaceModals } from '../../provider/model-provider'; import { MobileCurrentWorkspaceModals } from '../../provider/model-provider';
// TODO(@forehalo): reuse the global context with [core/electron] // TODO(@forehalo): reuse the global context with [core/electron]