mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
refactor(component): make component pure (#5427)
This commit is contained in:
@@ -42,8 +42,6 @@
|
|||||||
"@radix-ui/react-toast": "^1.1.5",
|
"@radix-ui/react-toast": "^1.1.5",
|
||||||
"@radix-ui/react-toolbar": "^1.0.4",
|
"@radix-ui/react-toolbar": "^1.0.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"@toeverything/hooks": "workspace:*",
|
|
||||||
"@toeverything/infra": "workspace:*",
|
|
||||||
"@toeverything/theme": "^0.7.24",
|
"@toeverything/theme": "^0.7.24",
|
||||||
"@vanilla-extract/dynamic": "^2.0.3",
|
"@vanilla-extract/dynamic": "^2.0.3",
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { Unreachable } from '@affine/env/constant';
|
import { Unreachable } from '@affine/env/constant';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons';
|
import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons';
|
||||||
import { useAppUpdater } from '@toeverything/hooks/use-app-updater';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { Tooltip } from '../../../ui/tooltip';
|
import { Tooltip } from '../../../ui/tooltip';
|
||||||
import * as styles from './index.css';
|
import * as styles from './index.css';
|
||||||
|
|
||||||
export interface AddPageButtonPureProps {
|
export interface AddPageButtonProps {
|
||||||
onClickUpdate: () => void;
|
onQuitAndInstall: () => void;
|
||||||
onDismissCurrentChangelog: () => void;
|
onDownloadUpdate: () => void;
|
||||||
currentChangelogUnread: boolean;
|
onDismissChangelog: () => void;
|
||||||
|
onOpenChangelog: () => void;
|
||||||
|
changelogUnread: boolean;
|
||||||
updateReady: boolean;
|
updateReady: boolean;
|
||||||
updateAvailable: {
|
updateAvailable: {
|
||||||
version: string;
|
version: string;
|
||||||
@@ -33,8 +34,8 @@ interface ButtonContentProps {
|
|||||||
autoDownload: boolean;
|
autoDownload: boolean;
|
||||||
downloadProgress: number | null;
|
downloadProgress: number | null;
|
||||||
appQuitting: boolean;
|
appQuitting: boolean;
|
||||||
currentChangelogUnread: boolean;
|
changelogUnread: boolean;
|
||||||
onDismissCurrentChangelog: () => void;
|
onDismissChangelog: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DownloadUpdate({ updateAvailable }: ButtonContentProps) {
|
function DownloadUpdate({ updateAvailable }: ButtonContentProps) {
|
||||||
@@ -114,14 +115,14 @@ function OpenDownloadPage({ updateAvailable }: ButtonContentProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function WhatsNew({ onDismissCurrentChangelog }: ButtonContentProps) {
|
function WhatsNew({ onDismissChangelog }: ButtonContentProps) {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const onClickClose: React.MouseEventHandler = useCallback(
|
const onClickClose: React.MouseEventHandler = useCallback(
|
||||||
e => {
|
e => {
|
||||||
onDismissCurrentChangelog();
|
onDismissChangelog();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
},
|
},
|
||||||
[onDismissCurrentChangelog]
|
[onDismissChangelog]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -149,42 +150,76 @@ const getButtonContentRenderer = (props: ButtonContentProps) => {
|
|||||||
}
|
}
|
||||||
} else if (props.updateAvailable && !props.updateAvailable?.allowAutoUpdate) {
|
} else if (props.updateAvailable && !props.updateAvailable?.allowAutoUpdate) {
|
||||||
return OpenDownloadPage;
|
return OpenDownloadPage;
|
||||||
} else if (props.currentChangelogUnread) {
|
} else if (props.changelogUnread) {
|
||||||
return WhatsNew;
|
return WhatsNew;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AppUpdaterButtonPure({
|
export function AppUpdaterButton({
|
||||||
updateReady,
|
updateReady,
|
||||||
onClickUpdate,
|
changelogUnread,
|
||||||
onDismissCurrentChangelog,
|
onDismissChangelog,
|
||||||
currentChangelogUnread,
|
onDownloadUpdate,
|
||||||
|
onQuitAndInstall,
|
||||||
|
onOpenChangelog,
|
||||||
updateAvailable,
|
updateAvailable,
|
||||||
autoDownload,
|
autoDownload,
|
||||||
downloadProgress,
|
downloadProgress,
|
||||||
appQuitting,
|
appQuitting,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
}: AddPageButtonPureProps) {
|
}: AddPageButtonProps) {
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
if (updateReady) {
|
||||||
|
onQuitAndInstall();
|
||||||
|
} else if (updateAvailable) {
|
||||||
|
if (updateAvailable.allowAutoUpdate) {
|
||||||
|
if (autoDownload) {
|
||||||
|
// wait for download to finish
|
||||||
|
} else {
|
||||||
|
onDownloadUpdate();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.open(
|
||||||
|
`https://github.com/toeverything/AFFiNE/releases/tag/v${updateAvailable.version}`,
|
||||||
|
'_blank'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (changelogUnread) {
|
||||||
|
window.open(runtimeConfig.changelogUrl, '_blank');
|
||||||
|
onOpenChangelog();
|
||||||
|
} else {
|
||||||
|
throw new Unreachable();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
updateReady,
|
||||||
|
updateAvailable,
|
||||||
|
changelogUnread,
|
||||||
|
onQuitAndInstall,
|
||||||
|
autoDownload,
|
||||||
|
onDownloadUpdate,
|
||||||
|
onOpenChangelog,
|
||||||
|
]);
|
||||||
|
|
||||||
const contentProps = useMemo(
|
const contentProps = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
updateReady,
|
updateReady,
|
||||||
updateAvailable,
|
updateAvailable,
|
||||||
currentChangelogUnread,
|
changelogUnread,
|
||||||
autoDownload,
|
autoDownload,
|
||||||
downloadProgress,
|
downloadProgress,
|
||||||
appQuitting,
|
appQuitting,
|
||||||
onDismissCurrentChangelog,
|
onDismissChangelog,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
updateReady,
|
updateReady,
|
||||||
updateAvailable,
|
updateAvailable,
|
||||||
currentChangelogUnread,
|
changelogUnread,
|
||||||
autoDownload,
|
autoDownload,
|
||||||
downloadProgress,
|
downloadProgress,
|
||||||
appQuitting,
|
appQuitting,
|
||||||
onDismissCurrentChangelog,
|
onDismissChangelog,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -222,6 +257,10 @@ export function AppUpdaterButtonPure({
|
|||||||
updateReady,
|
updateReady,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!updateAvailable && !changelogUnread) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return wrapWithTooltip(
|
return wrapWithTooltip(
|
||||||
<button
|
<button
|
||||||
style={style}
|
style={style}
|
||||||
@@ -229,7 +268,7 @@ export function AppUpdaterButtonPure({
|
|||||||
data-has-update={!!updateAvailable}
|
data-has-update={!!updateAvailable}
|
||||||
data-updating={appQuitting}
|
data-updating={appQuitting}
|
||||||
data-disabled={disabled}
|
data-disabled={disabled}
|
||||||
onClick={onClickUpdate}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{ContentComponent ? <ContentComponent {...contentProps} /> : null}
|
{ContentComponent ? <ContentComponent {...contentProps} /> : null}
|
||||||
<div className={styles.particles} aria-hidden="true"></div>
|
<div className={styles.particles} aria-hidden="true"></div>
|
||||||
@@ -238,77 +277,3 @@ export function AppUpdaterButtonPure({
|
|||||||
updateAvailable?.version
|
updateAvailable?.version
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Although it is called an input, it is actually a button.
|
|
||||||
export function AppUpdaterButton({
|
|
||||||
className,
|
|
||||||
style,
|
|
||||||
}: {
|
|
||||||
className?: string;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
}) {
|
|
||||||
const {
|
|
||||||
quitAndInstall,
|
|
||||||
appQuitting,
|
|
||||||
autoDownload,
|
|
||||||
downloadUpdate,
|
|
||||||
readChangelog,
|
|
||||||
changelogUnread,
|
|
||||||
updateReady,
|
|
||||||
updateAvailable,
|
|
||||||
downloadProgress,
|
|
||||||
currentVersion,
|
|
||||||
} = useAppUpdater();
|
|
||||||
|
|
||||||
const handleClickUpdate = useCallback(() => {
|
|
||||||
if (updateReady) {
|
|
||||||
quitAndInstall();
|
|
||||||
} else if (updateAvailable) {
|
|
||||||
if (updateAvailable.allowAutoUpdate) {
|
|
||||||
if (autoDownload) {
|
|
||||||
// wait for download to finish
|
|
||||||
} else {
|
|
||||||
downloadUpdate();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
window.open(
|
|
||||||
`https://github.com/toeverything/AFFiNE/releases/tag/v${currentVersion}`,
|
|
||||||
'_blank'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (changelogUnread) {
|
|
||||||
window.open(runtimeConfig.changelogUrl, '_blank');
|
|
||||||
readChangelog();
|
|
||||||
} else {
|
|
||||||
throw new Unreachable();
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
updateReady,
|
|
||||||
updateAvailable,
|
|
||||||
changelogUnread,
|
|
||||||
quitAndInstall,
|
|
||||||
autoDownload,
|
|
||||||
downloadUpdate,
|
|
||||||
currentVersion,
|
|
||||||
readChangelog,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!updateAvailable && !changelogUnread) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppUpdaterButtonPure
|
|
||||||
appQuitting={appQuitting}
|
|
||||||
autoDownload={autoDownload}
|
|
||||||
updateReady={!!updateReady}
|
|
||||||
onClickUpdate={handleClickUpdate}
|
|
||||||
onDismissCurrentChangelog={readChangelog}
|
|
||||||
currentChangelogUnread={changelogUnread}
|
|
||||||
updateAvailable={updateAvailable}
|
|
||||||
downloadProgress={downloadProgress}
|
|
||||||
className={className}
|
|
||||||
style={style}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
|
|||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import type { WorkspaceMetadata } from '@affine/workspace';
|
import type { WorkspaceMetadata } from '@affine/workspace';
|
||||||
import { CollaborationIcon, SettingsIcon } from '@blocksuite/icons';
|
import { CollaborationIcon, SettingsIcon } from '@blocksuite/icons';
|
||||||
import { useWorkspaceBlobObjectUrl } from '@toeverything/hooks/use-workspace-blob';
|
|
||||||
import { useWorkspaceInfo } from '@toeverything/hooks/use-workspace-info';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { Avatar } from '../../../ui/avatar';
|
import { Avatar } from '../../../ui/avatar';
|
||||||
@@ -72,6 +70,8 @@ export interface WorkspaceCardProps {
|
|||||||
onClick: (metadata: WorkspaceMetadata) => void;
|
onClick: (metadata: WorkspaceMetadata) => void;
|
||||||
onSettingClick: (metadata: WorkspaceMetadata) => void;
|
onSettingClick: (metadata: WorkspaceMetadata) => void;
|
||||||
isOwner?: boolean;
|
isOwner?: boolean;
|
||||||
|
avatar?: string;
|
||||||
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspaceCardSkeleton = () => {
|
export const WorkspaceCardSkeleton = () => {
|
||||||
@@ -96,11 +96,10 @@ export const WorkspaceCard = ({
|
|||||||
currentWorkspaceId,
|
currentWorkspaceId,
|
||||||
meta,
|
meta,
|
||||||
isOwner = true,
|
isOwner = true,
|
||||||
|
name,
|
||||||
|
avatar,
|
||||||
}: WorkspaceCardProps) => {
|
}: WorkspaceCardProps) => {
|
||||||
const information = useWorkspaceInfo(meta);
|
const displayName = name ?? UNTITLED_WORKSPACE_NAME;
|
||||||
const avatarUrl = useWorkspaceBlobObjectUrl(meta, information?.avatar);
|
|
||||||
|
|
||||||
const name = information?.name ?? UNTITLED_WORKSPACE_NAME;
|
|
||||||
return (
|
return (
|
||||||
<StyledCard
|
<StyledCard
|
||||||
data-testid="workspace-card"
|
data-testid="workspace-card"
|
||||||
@@ -109,12 +108,10 @@ export const WorkspaceCard = ({
|
|||||||
}, [onClick, meta])}
|
}, [onClick, meta])}
|
||||||
active={meta.id === currentWorkspaceId}
|
active={meta.id === currentWorkspaceId}
|
||||||
>
|
>
|
||||||
<Avatar size={28} url={avatarUrl} name={name} colorfulFallback />
|
<Avatar size={28} url={avatar} name={name} colorfulFallback />
|
||||||
<StyledWorkspaceInfo>
|
<StyledWorkspaceInfo>
|
||||||
<StyledWorkspaceTitleArea style={{ display: 'flex' }}>
|
<StyledWorkspaceTitleArea style={{ display: 'flex' }}>
|
||||||
<StyledWorkspaceTitle>
|
<StyledWorkspaceTitle>{displayName}</StyledWorkspaceTitle>
|
||||||
{information?.name ?? UNTITLED_WORKSPACE_NAME}
|
|
||||||
</StyledWorkspaceTitle>
|
|
||||||
|
|
||||||
<StyledSettingLink
|
<StyledSettingLink
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* @vitest-environment happy-dom
|
||||||
|
*/
|
||||||
|
import 'fake-indexeddb/auto';
|
||||||
|
|
||||||
|
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||||
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
|
import type { Page } from '@blocksuite/store';
|
||||||
|
import { Schema, Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
||||||
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
|
import { beforeEach } from 'vitest';
|
||||||
|
|
||||||
|
import { useBlockSuitePagePreview } from '../use-block-suite-page-preview';
|
||||||
|
let blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||||
|
|
||||||
|
const schema = new Schema();
|
||||||
|
schema.register(AffineSchemas).register(__unstableSchemas);
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.useFakeTimers({ toFake: ['requestIdleCallback'] });
|
||||||
|
blockSuiteWorkspace = new BlockSuiteWorkspace({ id: 'test', schema });
|
||||||
|
const initPage = async (page: Page) => {
|
||||||
|
await page.waitForLoaded();
|
||||||
|
expect(page).not.toBeNull();
|
||||||
|
assertExists(page);
|
||||||
|
const pageBlockId = page.addBlock('affine:page', {
|
||||||
|
title: new page.Text(''),
|
||||||
|
});
|
||||||
|
const frameId = page.addBlock('affine:note', {}, pageBlockId);
|
||||||
|
page.addBlock('affine:paragraph', {}, frameId);
|
||||||
|
};
|
||||||
|
await initPage(blockSuiteWorkspace.createPage({ id: 'page0' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useBlockSuitePagePreview', () => {
|
||||||
|
test('basic', async () => {
|
||||||
|
const page = blockSuiteWorkspace.getPage('page0') as Page;
|
||||||
|
const id = page.addBlock(
|
||||||
|
'affine:paragraph',
|
||||||
|
{
|
||||||
|
text: new page.Text('Hello, world!'),
|
||||||
|
},
|
||||||
|
page.getBlockByFlavour('affine:note')[0].id
|
||||||
|
);
|
||||||
|
const hook = renderHook(() => useAtomValue(useBlockSuitePagePreview(page)));
|
||||||
|
expect(hook.result.current).toBe('Hello, world!');
|
||||||
|
page.transact(() => {
|
||||||
|
page.getBlockById(id)!.text!.insert('Test', 0);
|
||||||
|
});
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
hook.rerender();
|
||||||
|
expect(hook.result.current).toBe('TestHello, world!');
|
||||||
|
|
||||||
|
// Insert before
|
||||||
|
page.addBlock(
|
||||||
|
'affine:paragraph',
|
||||||
|
{
|
||||||
|
text: new page.Text('First block!'),
|
||||||
|
},
|
||||||
|
page.getBlockByFlavour('affine:note')[0].id,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
hook.rerender();
|
||||||
|
expect(hook.result.current).toBe('First block! TestHello, world!');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import type { Workspace } from '@blocksuite/store';
|
import type { Workspace } from '@blocksuite/store';
|
||||||
import { useBlockSuitePagePreview } from '@toeverything/hooks/use-block-suite-page-preview';
|
|
||||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
|
import { useBlockSuitePagePreview } from './use-block-suite-page-preview';
|
||||||
|
import { useBlockSuiteWorkspacePage } from './use-block-suite-workspace-page';
|
||||||
|
|
||||||
interface PagePreviewInnerProps {
|
interface PagePreviewInnerProps {
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
pageId: string;
|
pageId: string;
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { DebugLogger } from '@affine/debug';
|
||||||
|
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||||
|
import type { Page, Workspace } from '@blocksuite/store';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const logger = new DebugLogger('use-block-suite-workspace-page');
|
||||||
|
|
||||||
|
export function useBlockSuiteWorkspacePage(
|
||||||
|
blockSuiteWorkspace: Workspace,
|
||||||
|
pageId: string | null
|
||||||
|
): Page | null {
|
||||||
|
const [page, setPage] = useState(
|
||||||
|
pageId ? blockSuiteWorkspace.getPage(pageId) : null
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const group = new DisposableGroup();
|
||||||
|
group.add(
|
||||||
|
blockSuiteWorkspace.slots.pageAdded.on(id => {
|
||||||
|
if (pageId === id) {
|
||||||
|
setPage(blockSuiteWorkspace.getPage(id));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
group.add(
|
||||||
|
blockSuiteWorkspace.slots.pageRemoved.on(id => {
|
||||||
|
if (pageId === id) {
|
||||||
|
setPage(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
group.dispose();
|
||||||
|
};
|
||||||
|
}, [blockSuiteWorkspace, pageId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (page && !page.loaded) {
|
||||||
|
page.load().catch(err => {
|
||||||
|
logger.error('Failed to load page', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
@@ -24,21 +24,28 @@ export interface WorkspaceListProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
currentWorkspaceId?: string | null;
|
currentWorkspaceId?: string | null;
|
||||||
items: WorkspaceMetadata[];
|
items: WorkspaceMetadata[];
|
||||||
onClick: (workspaceMetadata: WorkspaceMetadata) => void;
|
onClick: (workspace: WorkspaceMetadata) => void;
|
||||||
onSettingClick: (workspaceMetadata: WorkspaceMetadata) => void;
|
onSettingClick: (workspace: WorkspaceMetadata) => void;
|
||||||
onDragEnd: (event: DragEndEvent) => void;
|
onDragEnd: (event: DragEndEvent) => void;
|
||||||
useIsWorkspaceOwner?: (workspaceMetadata: WorkspaceMetadata) => boolean;
|
useIsWorkspaceOwner: (workspaceMetadata: WorkspaceMetadata) => boolean;
|
||||||
|
useWorkspaceAvatar: (
|
||||||
|
workspaceMetadata: WorkspaceMetadata
|
||||||
|
) => string | undefined;
|
||||||
|
useWorkspaceName: (
|
||||||
|
workspaceMetadata: WorkspaceMetadata
|
||||||
|
) => string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SortableWorkspaceItemProps extends Omit<WorkspaceListProps, 'items'> {
|
interface SortableWorkspaceItemProps extends Omit<WorkspaceListProps, 'items'> {
|
||||||
item: WorkspaceMetadata;
|
item: WorkspaceMetadata;
|
||||||
useIsWorkspaceOwner?: (workspaceMetadata: WorkspaceMetadata) => boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SortableWorkspaceItem = ({
|
const SortableWorkspaceItem = ({
|
||||||
disabled,
|
disabled,
|
||||||
item,
|
item,
|
||||||
useIsWorkspaceOwner,
|
useIsWorkspaceOwner,
|
||||||
|
useWorkspaceAvatar,
|
||||||
|
useWorkspaceName,
|
||||||
currentWorkspaceId,
|
currentWorkspaceId,
|
||||||
onClick,
|
onClick,
|
||||||
onSettingClick,
|
onSettingClick,
|
||||||
@@ -59,6 +66,8 @@ const SortableWorkspaceItem = ({
|
|||||||
[disabled, transform, transition]
|
[disabled, transform, transition]
|
||||||
);
|
);
|
||||||
const isOwner = useIsWorkspaceOwner?.(item);
|
const isOwner = useIsWorkspaceOwner?.(item);
|
||||||
|
const avatar = useWorkspaceAvatar?.(item);
|
||||||
|
const name = useWorkspaceName?.(item);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={workspaceItemStyle}
|
className={workspaceItemStyle}
|
||||||
@@ -74,6 +83,8 @@ const SortableWorkspaceItem = ({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onSettingClick={onSettingClick}
|
onSettingClick={onSettingClick}
|
||||||
isOwner={isOwner}
|
isOwner={isOwner}
|
||||||
|
name={name}
|
||||||
|
avatar={avatar}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export const UserWithWorkspaceList = ({
|
|||||||
<SignInItem />
|
<SignInItem />
|
||||||
)}
|
)}
|
||||||
<Divider size="thinner" />
|
<Divider size="thinner" />
|
||||||
<AFFiNEWorkspaceList workspaces={workspaces} onEventEnd={onEventEnd} />
|
<AFFiNEWorkspaceList onEventEnd={onEventEnd} />
|
||||||
{workspaces.length > 0 ? <Divider size="thinner" /> : null}
|
{workspaces.length > 0 ? <Divider size="thinner" /> : null}
|
||||||
<AddWorkspace
|
<AddWorkspace
|
||||||
onAddWorkspace={onAddWorkspace}
|
onAddWorkspace={onAddWorkspace}
|
||||||
|
|||||||
@@ -4,8 +4,15 @@ import { WorkspaceList } from '@affine/component/workspace-list';
|
|||||||
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
|
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import type { WorkspaceMetadata } from '@affine/workspace';
|
import type { WorkspaceMetadata } from '@affine/workspace';
|
||||||
import { currentWorkspaceAtom } from '@affine/workspace/atom';
|
import {
|
||||||
|
currentWorkspaceAtom,
|
||||||
|
workspaceListAtom,
|
||||||
|
} from '@affine/workspace/atom';
|
||||||
import type { DragEndEvent } from '@dnd-kit/core';
|
import type { DragEndEvent } from '@dnd-kit/core';
|
||||||
|
import {
|
||||||
|
useWorkspaceAvatar,
|
||||||
|
useWorkspaceName,
|
||||||
|
} from '@toeverything/hooks/use-workspace-info';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
@@ -54,6 +61,8 @@ const CloudWorkSpaceList = ({
|
|||||||
onSettingClick={onClickWorkspaceSetting}
|
onSettingClick={onClickWorkspaceSetting}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
useIsWorkspaceOwner={useIsWorkspaceOwner}
|
useIsWorkspaceOwner={useIsWorkspaceOwner}
|
||||||
|
useWorkspaceName={useWorkspaceName}
|
||||||
|
useWorkspaceAvatar={useWorkspaceAvatar}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -83,18 +92,21 @@ const LocalWorkspaces = ({
|
|||||||
onClick={onClickWorkspace}
|
onClick={onClickWorkspace}
|
||||||
onSettingClick={onClickWorkspaceSetting}
|
onSettingClick={onClickWorkspaceSetting}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
|
useIsWorkspaceOwner={useIsWorkspaceOwner}
|
||||||
|
useWorkspaceName={useWorkspaceName}
|
||||||
|
useWorkspaceAvatar={useWorkspaceAvatar}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AFFiNEWorkspaceList = ({
|
export const AFFiNEWorkspaceList = ({
|
||||||
workspaces,
|
|
||||||
onEventEnd,
|
onEventEnd,
|
||||||
}: {
|
}: {
|
||||||
workspaces: WorkspaceMetadata[];
|
|
||||||
onEventEnd?: () => void;
|
onEventEnd?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const workspaces = useAtomValue(workspaceListAtom);
|
||||||
|
|
||||||
const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom);
|
const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom);
|
||||||
|
|
||||||
const { jumpToSubPath } = useNavigateHelper();
|
const { jumpToSubPath } = useNavigateHelper();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
AppDownloadButton,
|
AppDownloadButton,
|
||||||
AppSidebar,
|
AppSidebar,
|
||||||
appSidebarOpenAtom,
|
appSidebarOpenAtom,
|
||||||
AppUpdaterButton,
|
|
||||||
CategoryDivider,
|
CategoryDivider,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuLinkItem,
|
MenuLinkItem,
|
||||||
@@ -49,6 +48,7 @@ import FavoriteList from '../pure/workspace-slider-bar/favorite/favorite-list';
|
|||||||
import { UserWithWorkspaceList } from '../pure/workspace-slider-bar/user-with-workspace-list';
|
import { UserWithWorkspaceList } from '../pure/workspace-slider-bar/user-with-workspace-list';
|
||||||
import { WorkspaceCard } from '../pure/workspace-slider-bar/workspace-card';
|
import { WorkspaceCard } from '../pure/workspace-slider-bar/workspace-card';
|
||||||
import ImportPage from './import-page';
|
import ImportPage from './import-page';
|
||||||
|
import { UpdaterButton } from './updater-button';
|
||||||
|
|
||||||
export type RootAppSidebarProps = {
|
export type RootAppSidebarProps = {
|
||||||
isPublicWorkspace: boolean;
|
isPublicWorkspace: boolean;
|
||||||
@@ -299,7 +299,7 @@ export const RootAppSidebar = ({
|
|||||||
)}
|
)}
|
||||||
</SidebarScrollableContainer>
|
</SidebarScrollableContainer>
|
||||||
<SidebarContainer>
|
<SidebarContainer>
|
||||||
{environment.isDesktop ? <AppUpdaterButton /> : <AppDownloadButton />}
|
{environment.isDesktop ? <UpdaterButton /> : <AppDownloadButton />}
|
||||||
<div style={{ height: '4px' }} />
|
<div style={{ height: '4px' }} />
|
||||||
<AddPageButton onClick={onClickNewPage} />
|
<AddPageButton onClick={onClickNewPage} />
|
||||||
</SidebarContainer>
|
</SidebarContainer>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { AppUpdaterButton } from '@affine/component/app-sidebar/app-updater-button';
|
||||||
|
import { useAppUpdater } from '@toeverything/hooks/use-app-updater';
|
||||||
|
|
||||||
|
export const UpdaterButton = () => {
|
||||||
|
const appUpdater = useAppUpdater();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppUpdaterButton
|
||||||
|
onQuitAndInstall={appUpdater.quitAndInstall}
|
||||||
|
onDownloadUpdate={appUpdater.downloadUpdate}
|
||||||
|
onDismissChangelog={appUpdater.dismissChangelog}
|
||||||
|
onOpenChangelog={appUpdater.openChangelog}
|
||||||
|
changelogUnread={appUpdater.changelogUnread}
|
||||||
|
updateReady={!!appUpdater.updateReady}
|
||||||
|
updateAvailable={appUpdater.updateAvailable}
|
||||||
|
autoDownload={appUpdater.autoDownload}
|
||||||
|
downloadProgress={appUpdater.downloadProgress}
|
||||||
|
appQuitting={appUpdater.appQuitting}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -8,11 +8,9 @@ import { assertExists } from '@blocksuite/global/utils';
|
|||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { Schema, Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
import { Schema, Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { useAtomValue } from 'jotai';
|
|
||||||
import { describe, expect, test, vi } from 'vitest';
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
import { beforeEach } from 'vitest';
|
import { beforeEach } from 'vitest';
|
||||||
|
|
||||||
import { useBlockSuitePagePreview } from '../use-block-suite-page-preview';
|
|
||||||
import { useBlockSuiteWorkspacePageTitle } from '../use-block-suite-workspace-page-title';
|
import { useBlockSuiteWorkspacePageTitle } from '../use-block-suite-workspace-page-title';
|
||||||
|
|
||||||
let blockSuiteWorkspace: BlockSuiteWorkspace;
|
let blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||||
@@ -49,37 +47,3 @@ describe('useBlockSuiteWorkspacePageTitle', () => {
|
|||||||
expect(pageTitleHook.result.current).toBe('1');
|
expect(pageTitleHook.result.current).toBe('1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('useBlockSuitePagePreview', () => {
|
|
||||||
test('basic', async () => {
|
|
||||||
const page = blockSuiteWorkspace.getPage('page0') as Page;
|
|
||||||
const id = page.addBlock(
|
|
||||||
'affine:paragraph',
|
|
||||||
{
|
|
||||||
text: new page.Text('Hello, world!'),
|
|
||||||
},
|
|
||||||
page.getBlockByFlavour('affine:note')[0].id
|
|
||||||
);
|
|
||||||
const hook = renderHook(() => useAtomValue(useBlockSuitePagePreview(page)));
|
|
||||||
expect(hook.result.current).toBe('Hello, world!');
|
|
||||||
page.transact(() => {
|
|
||||||
page.getBlockById(id)!.text!.insert('Test', 0);
|
|
||||||
});
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
hook.rerender();
|
|
||||||
expect(hook.result.current).toBe('TestHello, world!');
|
|
||||||
|
|
||||||
// Insert before
|
|
||||||
page.addBlock(
|
|
||||||
'affine:paragraph',
|
|
||||||
{
|
|
||||||
text: new page.Text('First block!'),
|
|
||||||
},
|
|
||||||
page.getBlockByFlavour('affine:note')[0].id,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
hook.rerender();
|
|
||||||
expect(hook.result.current).toBe('First block! TestHello, world!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -168,7 +168,12 @@ export const useAppUpdater = () => {
|
|||||||
[setSetting]
|
[setSetting]
|
||||||
);
|
);
|
||||||
|
|
||||||
const readChangelog = useAsyncCallback(async () => {
|
const openChangelog = useAsyncCallback(async () => {
|
||||||
|
window.open(runtimeConfig.changelogUrl, '_blank');
|
||||||
|
await setChangelogUnread(true);
|
||||||
|
}, [setChangelogUnread]);
|
||||||
|
|
||||||
|
const dismissChangelog = useAsyncCallback(async () => {
|
||||||
await setChangelogUnread(true);
|
await setChangelogUnread(true);
|
||||||
}, [setChangelogUnread]);
|
}, [setChangelogUnread]);
|
||||||
|
|
||||||
@@ -183,7 +188,8 @@ export const useAppUpdater = () => {
|
|||||||
autoCheck: setting.autoCheckUpdate,
|
autoCheck: setting.autoCheckUpdate,
|
||||||
autoDownload: setting.autoDownloadUpdate,
|
autoDownload: setting.autoDownloadUpdate,
|
||||||
changelogUnread,
|
changelogUnread,
|
||||||
readChangelog,
|
openChangelog,
|
||||||
|
dismissChangelog,
|
||||||
updateReady,
|
updateReady,
|
||||||
updateAvailable: useAtomValue(updateAvailableAtom),
|
updateAvailable: useAtomValue(updateAvailableAtom),
|
||||||
downloadProgress,
|
downloadProgress,
|
||||||
|
|||||||
@@ -1,112 +1,42 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import { assertExists, DisposableGroup } from '@blocksuite/global/utils';
|
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||||
import type { Page, Workspace } from '@blocksuite/store';
|
import type { Page, Workspace } from '@blocksuite/store';
|
||||||
import type { Atom } from 'jotai';
|
import { useEffect, useState } from 'react';
|
||||||
import { atom, useAtomValue } from 'jotai';
|
|
||||||
import PQueue from 'p-queue';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
const logger = new DebugLogger('use-block-suite-workspace-page');
|
const logger = new DebugLogger('use-block-suite-workspace-page');
|
||||||
|
|
||||||
const weakMap = new WeakMap<Workspace, Map<string, Atom<Page | null>>>();
|
|
||||||
|
|
||||||
const emptyAtom = atom<Page | null>(null);
|
|
||||||
|
|
||||||
function getAtom(w: Workspace, pageId: string | null): Atom<Page | null> {
|
|
||||||
if (!pageId) {
|
|
||||||
return emptyAtom;
|
|
||||||
}
|
|
||||||
if (!weakMap.has(w)) {
|
|
||||||
weakMap.set(w, new Map());
|
|
||||||
}
|
|
||||||
const map = weakMap.get(w);
|
|
||||||
assertExists(map);
|
|
||||||
if (!map.has(pageId)) {
|
|
||||||
const baseAtom = atom(w.getPage(pageId));
|
|
||||||
baseAtom.onMount = set => {
|
|
||||||
const group = new DisposableGroup();
|
|
||||||
group.add(
|
|
||||||
w.slots.pageAdded.on(id => {
|
|
||||||
if (pageId === id) {
|
|
||||||
set(w.getPage(id));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
group.add(
|
|
||||||
w.slots.pageRemoved.on(id => {
|
|
||||||
if (pageId === id) {
|
|
||||||
set(null);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return () => {
|
|
||||||
group.dispose();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
map.set(pageId, baseAtom);
|
|
||||||
return baseAtom;
|
|
||||||
} else {
|
|
||||||
return map.get(pageId) as Atom<Page | null>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// concurrently load 3 pages at most
|
|
||||||
const CONCURRENT_JOBS = 3;
|
|
||||||
|
|
||||||
const loadPageQueue = new PQueue({
|
|
||||||
concurrency: CONCURRENT_JOBS,
|
|
||||||
});
|
|
||||||
|
|
||||||
const loadedPages = new WeakSet<Page>();
|
|
||||||
|
|
||||||
const awaitForIdle = () =>
|
|
||||||
new Promise(resolve =>
|
|
||||||
requestIdleCallback(resolve, {
|
|
||||||
timeout: 1000, // do not wait for too long
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const awaitForTimeout = (timeout: number) =>
|
|
||||||
new Promise(resolve => setTimeout(resolve, timeout));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a page and wait for it to be loaded
|
|
||||||
* This page will be loaded in a queue so that it will not jam the network and browser CPU
|
|
||||||
*/
|
|
||||||
export function loadPage(page: Page, priority = 0) {
|
|
||||||
if (loadedPages.has(page)) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
loadedPages.add(page);
|
|
||||||
return loadPageQueue.add(
|
|
||||||
async () => {
|
|
||||||
if (!page.loaded) {
|
|
||||||
await awaitForIdle();
|
|
||||||
await page.waitForLoaded();
|
|
||||||
logger.debug('page loaded', page.id);
|
|
||||||
// we do not know how long it takes to load a page here
|
|
||||||
// so that we just use 300ms timeout as the default page processing time
|
|
||||||
await awaitForTimeout(300);
|
|
||||||
} else {
|
|
||||||
// do nothing if it is already loaded
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
priority,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useBlockSuiteWorkspacePage(
|
export function useBlockSuiteWorkspacePage(
|
||||||
blockSuiteWorkspace: Workspace,
|
blockSuiteWorkspace: Workspace,
|
||||||
pageId: string | null
|
pageId: string | null
|
||||||
): Page | null {
|
): Page | null {
|
||||||
const pageAtom = getAtom(blockSuiteWorkspace, pageId);
|
const [page, setPage] = useState(
|
||||||
assertExists(pageAtom);
|
pageId ? blockSuiteWorkspace.getPage(pageId) : null
|
||||||
const page = useAtomValue(pageAtom);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const group = new DisposableGroup();
|
||||||
|
group.add(
|
||||||
|
blockSuiteWorkspace.slots.pageAdded.on(id => {
|
||||||
|
if (pageId === id) {
|
||||||
|
setPage(blockSuiteWorkspace.getPage(id));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
group.add(
|
||||||
|
blockSuiteWorkspace.slots.pageRemoved.on(id => {
|
||||||
|
if (pageId === id) {
|
||||||
|
setPage(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
group.dispose();
|
||||||
|
};
|
||||||
|
}, [blockSuiteWorkspace, pageId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (page && !page.loaded) {
|
if (page && !page.loaded) {
|
||||||
loadPage(page).catch(err => {
|
page.load().catch(err => {
|
||||||
logger.error('Failed to load page', err);
|
logger.error('Failed to load page', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import type { Workspace, WorkspaceMetadata } from '@affine/workspace';
|
import type { WorkspaceMetadata } from '@affine/workspace';
|
||||||
import { workspaceManagerAtom } from '@affine/workspace/atom';
|
import { workspaceManagerAtom } from '@affine/workspace/atom';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
export function useWorkspaceInfo(
|
import { useWorkspaceBlobObjectUrl } from './use-workspace-blob';
|
||||||
meta: WorkspaceMetadata,
|
|
||||||
workspace?: Workspace
|
export function useWorkspaceInfo(meta: WorkspaceMetadata) {
|
||||||
) {
|
|
||||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||||
|
|
||||||
const [information, setInformation] = useState(
|
const [information, setInformation] = useState(
|
||||||
@@ -20,7 +19,20 @@ export function useWorkspaceInfo(
|
|||||||
return information.onUpdated.on(info => {
|
return information.onUpdated.on(info => {
|
||||||
setInformation(info);
|
setInformation(info);
|
||||||
}).dispose;
|
}).dispose;
|
||||||
}, [meta, workspace, workspaceManager]);
|
}, [meta, workspaceManager]);
|
||||||
|
|
||||||
return information;
|
return information;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useWorkspaceName(meta: WorkspaceMetadata) {
|
||||||
|
const information = useWorkspaceInfo(meta);
|
||||||
|
|
||||||
|
return information?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWorkspaceAvatar(meta: WorkspaceMetadata) {
|
||||||
|
const information = useWorkspaceInfo(meta);
|
||||||
|
const avatar = useWorkspaceBlobObjectUrl(meta, information?.avatar);
|
||||||
|
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
type AddPageButtonPureProps,
|
type AddPageButtonProps,
|
||||||
AppUpdaterButtonPure,
|
AppUpdaterButton,
|
||||||
} from '@affine/component/app-sidebar';
|
} from '@affine/component/app-sidebar';
|
||||||
import type { Meta, StoryFn } from '@storybook/react';
|
import type { Meta, StoryFn } from '@storybook/react';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'AFFiNE/AppUpdaterButton',
|
title: 'AFFiNE/AppUpdaterButton',
|
||||||
component: AppUpdaterButtonPure,
|
component: AppUpdaterButton,
|
||||||
parameters: {
|
parameters: {
|
||||||
chromatic: { disableSnapshot: true },
|
chromatic: { disableSnapshot: true },
|
||||||
},
|
},
|
||||||
} satisfies Meta<typeof AppUpdaterButtonPure>;
|
} satisfies Meta<typeof AppUpdaterButton>;
|
||||||
|
|
||||||
const Container = ({ children }: PropsWithChildren) => (
|
const Container = ({ children }: PropsWithChildren) => (
|
||||||
<main
|
<main
|
||||||
@@ -28,10 +28,10 @@ const Container = ({ children }: PropsWithChildren) => (
|
|||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Default: StoryFn<AddPageButtonPureProps> = props => {
|
export const Default: StoryFn<AddPageButtonProps> = props => {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<AppUpdaterButtonPure {...props} />
|
<AppUpdaterButton {...props} />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -44,17 +44,18 @@ Default.args = {
|
|||||||
allowAutoUpdate: true,
|
allowAutoUpdate: true,
|
||||||
},
|
},
|
||||||
downloadProgress: 42,
|
downloadProgress: 42,
|
||||||
currentChangelogUnread: true,
|
changelogUnread: true,
|
||||||
|
autoDownload: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Updated: StoryFn<AddPageButtonPureProps> = props => {
|
export const Updated: StoryFn<AddPageButtonProps> = props => {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<AppUpdaterButtonPure {...props} updateAvailable={null} />
|
<AppUpdaterButton {...props} updateAvailable={null} />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Updated.args = {
|
Updated.args = {
|
||||||
currentChangelogUnread: true,
|
changelogUnread: true,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ export const Default = () => {
|
|||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
onSettingClick={() => {}}
|
onSettingClick={() => {}}
|
||||||
onDragEnd={_ => {}}
|
onDragEnd={_ => {}}
|
||||||
|
useWorkspaceAvatar={() => undefined}
|
||||||
|
useWorkspaceName={() => undefined}
|
||||||
|
useIsWorkspaceOwner={() => false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -253,8 +253,6 @@ __metadata:
|
|||||||
"@storybook/test-runner": "npm:^0.16.0"
|
"@storybook/test-runner": "npm:^0.16.0"
|
||||||
"@storybook/testing-library": "npm:^0.2.2"
|
"@storybook/testing-library": "npm:^0.2.2"
|
||||||
"@testing-library/react": "npm:^14.0.0"
|
"@testing-library/react": "npm:^14.0.0"
|
||||||
"@toeverything/hooks": "workspace:*"
|
|
||||||
"@toeverything/infra": "workspace:*"
|
|
||||||
"@toeverything/theme": "npm:^0.7.24"
|
"@toeverything/theme": "npm:^0.7.24"
|
||||||
"@types/bytes": "npm:^3.1.3"
|
"@types/bytes": "npm:^3.1.3"
|
||||||
"@types/react": "npm:^18.2.28"
|
"@types/react": "npm:^18.2.28"
|
||||||
@@ -12807,7 +12805,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@toeverything/hooks@workspace:*, @toeverything/hooks@workspace:packages/frontend/hooks":
|
"@toeverything/hooks@workspace:packages/frontend/hooks":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@toeverything/hooks@workspace:packages/frontend/hooks"
|
resolution: "@toeverything/hooks@workspace:packages/frontend/hooks"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user