feat(core): preview template & snapshot import (#8193)

This commit is contained in:
EYHN
2024-09-11 07:11:33 +00:00
parent 52d9569f47
commit 8c191e6baa
9 changed files with 123 additions and 29 deletions

View File

@@ -0,0 +1,24 @@
import { Button } from '@affine/component';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { useI18n } from '@affine/i18n';
export const ImportTemplateButton = ({
workspaceId,
docId,
name,
}: {
workspaceId: string;
docId: string;
name: string;
}) => {
const t = useI18n();
const { jumpToImportTemplate } = useNavigateHelper();
return (
<Button
variant="primary"
onClick={() => jumpToImportTemplate(workspaceId, docId, name)}
>
{t['com.affine.share-page.header.import-template']()}
</Button>
);
};

View File

@@ -2,6 +2,7 @@ import { AuthService } from '@affine/core/modules/cloud';
import type { DocMode } from '@blocksuite/blocks';
import { useLiveData, useService } from '@toeverything/infra';
import { ImportTemplateButton } from './import-template';
import { PresentButton } from './present';
import { SignIn } from './sign-in';
import * as styles from './styles.css';
@@ -9,28 +10,45 @@ import { PublishPageUserAvatar } from './user-avatar';
export type ShareHeaderRightItemProps = {
workspaceId: string;
pageId: string;
docId: string;
publishMode: DocMode;
isTemplate?: boolean;
templateName?: string;
};
const ShareHeaderRightItem = ({ ...props }: ShareHeaderRightItemProps) => {
const ShareHeaderRightItem = ({
publishMode,
isTemplate,
templateName,
workspaceId,
docId,
}: ShareHeaderRightItemProps) => {
const loginStatus = useLiveData(useService(AuthService).session.status$);
const { publishMode } = props;
const authenticated = loginStatus === 'authenticated';
return (
<div className={styles.rightItemContainer}>
{authenticated ? null : <SignIn />}
{publishMode === 'edgeless' ? <PresentButton /> : null}
{authenticated ? (
{isTemplate ? (
<ImportTemplateButton
docId={docId}
workspaceId={workspaceId}
name={templateName ?? ''}
/>
) : (
<>
<div
className={styles.headerDivider}
data-authenticated={true}
data-is-edgeless={publishMode === 'edgeless'}
/>
<PublishPageUserAvatar />
{authenticated ? null : <SignIn />}
{publishMode === 'edgeless' ? <PresentButton /> : null}
{authenticated ? (
<>
<div
className={styles.headerDivider}
data-authenticated={true}
data-is-edgeless={publishMode === 'edgeless'}
/>
<PublishPageUserAvatar />
</>
) : null}
</>
) : null}
)}
</div>
);
};

View File

@@ -182,6 +182,15 @@ export function useNavigateHelper() {
[navigate]
);
const jumpToImportTemplate = useCallback(
(workspaceId: string, docId: string, name: string) => {
return navigate(
`/template/import?workspaceId=${encodeURIComponent(workspaceId)}&docId=${encodeURIComponent(docId)}&name=${encodeURIComponent(name)}`
);
},
[navigate]
);
return useMemo(
() => ({
jumpToPage,
@@ -197,6 +206,7 @@ export function useNavigateHelper() {
jumpToTags,
jumpToTag,
openInApp,
jumpToImportTemplate,
}),
[
jumpToPage,
@@ -212,6 +222,7 @@ export function useNavigateHelper() {
jumpToTags,
jumpToTag,
openInApp,
jumpToImportTemplate,
]
);
}

View File

@@ -1,4 +1,5 @@
import type { WorkspaceFlavour } from '@affine/env/workspace';
import { ZipTransformer } from '@blocksuite/blocks';
import type { WorkspaceMetadata, WorkspacesService } from '@toeverything/infra';
import { Service } from '@toeverything/infra';
@@ -16,13 +17,18 @@ export class ImportTemplateService extends Service {
metadata: workspaceMetadata,
});
await workspace.engine.waitForRootDocReady();
const newDoc = workspace.docCollection.createDoc({});
await workspace.engine.doc.storage.behavior.doc.set(
newDoc.spaceDoc.guid,
docBinary
const [importedDoc] = await ZipTransformer.importDocs(
workspace.docCollection,
new Blob([docBinary], {
type: 'application/zip',
})
);
disposeWorkspace();
return newDoc.id;
if (importedDoc) {
disposeWorkspace();
return importedDoc.id;
} else {
throw new Error('Failed to import doc');
}
}
async importToNewWorkspace(

View File

@@ -7,9 +7,12 @@ export class TemplateDownloaderStore extends Store {
super();
}
async download(workspaceId: string, docId: string) {
async download(
/* not support workspaceid for now */ _workspaceId: string,
docId: string
) {
const response = await this.fetchService.fetch(
`/api/workspaces/${workspaceId}/docs/${docId}`,
`https://affine.pro/templates/snapshots/${docId}.zip `,
{
priority: 'high',
} as any

View File

@@ -11,10 +11,14 @@ export function ShareHeader({
pageId,
publishMode,
docCollection,
isTemplate,
templateName,
}: {
pageId: string;
publishMode: DocMode;
docCollection: DocCollection;
isTemplate?: boolean;
templateName?: string;
}) {
return (
<div className={styles.header}>
@@ -23,8 +27,10 @@ export function ShareHeader({
<div className={styles.spacer} />
<ShareHeaderRightItem
workspaceId={docCollection.id}
pageId={pageId}
docId={pageId}
publishMode={publishMode}
isTemplate={isTemplate}
templateName={templateName}
/>
<AuthModal />
</div>

View File

@@ -32,7 +32,7 @@ import {
WorkspacesService,
} from '@toeverything/infra';
import clsx from 'clsx';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { PageNotFound } from '../../404';
@@ -57,14 +57,18 @@ export const SharePage = ({
const location = useLocation();
const [mode, setMode] = useState<DocMode | null>(null);
useEffect(() => {
const { mode, isTemplate, templateName } = useMemo(() => {
const searchParams = new URLSearchParams(location.search);
const queryStringMode = searchParams.get('mode') as DocMode | null;
if (queryStringMode && DocModes.includes(queryStringMode)) {
setMode(queryStringMode);
}
return {
mode:
queryStringMode && DocModes.includes(queryStringMode)
? queryStringMode
: null,
isTemplate: searchParams.has('isTemplate'),
templateName: searchParams.get('templateName') || '',
};
}, [location.search]);
useEffect(() => {
@@ -88,6 +92,8 @@ export const SharePage = ({
workspaceBinary={data.workspaceBinary}
docBinary={data.docBinary}
publishMode={mode || data.publishMode}
isTemplate={isTemplate}
templateName={templateName}
/>
);
} else {
@@ -101,12 +107,16 @@ const SharePageInner = ({
workspaceBinary,
docBinary,
publishMode = 'page' as DocMode,
isTemplate,
templateName,
}: {
workspaceId: string;
docId: string;
workspaceBinary: Uint8Array;
docBinary: Uint8Array;
publishMode?: DocMode;
isTemplate?: boolean;
templateName?: string;
}) => {
const workspacesService = useService(WorkspacesService);
@@ -218,6 +228,8 @@ const SharePageInner = ({
pageId={page.id}
publishMode={publishMode}
docCollection={page.blockSuiteDoc.collection}
isTemplate={isTemplate}
templateName={templateName}
/>
<Scrollable.Root>
<Scrollable.Viewport

View File

@@ -94,6 +94,19 @@ export const topLevelRoutes = [
path: '/template/import',
lazy: () => import('./pages/import-template'),
},
{
path: '/template/preview',
loader: ({ request }) => {
const url = new URL(request.url);
const workspaceId = url.searchParams.get('workspaceId');
const docId = url.searchParams.get('docId');
const templateName = url.searchParams.get('name');
return redirect(
`/workspace/${workspaceId}/${docId}?isTemplate=true&templateName=${encodeURIComponent(templateName ?? '')}`
);
},
},
{
path: '/auth/:authType',
lazy: () => import(/* webpackChunkName: "auth" */ './pages/auth/auth'),

View File

@@ -1457,6 +1457,7 @@
"com.affine.share-page.footer.get-started": "Get started for free",
"com.affine.share-page.header.login": "Login or Sign Up",
"com.affine.share-page.header.present": "Present",
"com.affine.share-page.header.import-template": "Use This Template",
"com.affine.shortcutsTitle.edgeless": "Edgeless",
"com.affine.shortcutsTitle.general": "General",
"com.affine.shortcutsTitle.markdownSyntax": "Markdown syntax",