From 8c191e6baafa3e166a5ec9f5b737c1eb46ea169a Mon Sep 17 00:00:00 2001 From: EYHN Date: Wed, 11 Sep 2024 07:11:33 +0000 Subject: [PATCH] feat(core): preview template & snapshot import (#8193) --- .../import-template.tsx | 24 ++++++++++ .../cloud/share-header-right-item/index.tsx | 44 +++++++++++++------ .../core/src/hooks/use-navigate-helper.ts | 11 +++++ .../import-template/services/import.ts | 18 +++++--- .../import-template/store/downloader.ts | 7 ++- .../pages/workspace/share/share-header.tsx | 8 +++- .../src/pages/workspace/share/share-page.tsx | 26 ++++++++--- packages/frontend/core/src/router.tsx | 13 ++++++ packages/frontend/i18n/src/resources/en.json | 1 + 9 files changed, 123 insertions(+), 29 deletions(-) create mode 100644 packages/frontend/core/src/components/cloud/share-header-right-item/import-template.tsx diff --git a/packages/frontend/core/src/components/cloud/share-header-right-item/import-template.tsx b/packages/frontend/core/src/components/cloud/share-header-right-item/import-template.tsx new file mode 100644 index 000000000..5bc38bc9d --- /dev/null +++ b/packages/frontend/core/src/components/cloud/share-header-right-item/import-template.tsx @@ -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 ( + + ); +}; diff --git a/packages/frontend/core/src/components/cloud/share-header-right-item/index.tsx b/packages/frontend/core/src/components/cloud/share-header-right-item/index.tsx index 4db381e37..88a86f0b4 100644 --- a/packages/frontend/core/src/components/cloud/share-header-right-item/index.tsx +++ b/packages/frontend/core/src/components/cloud/share-header-right-item/index.tsx @@ -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 (
- {authenticated ? null : } - {publishMode === 'edgeless' ? : null} - {authenticated ? ( + {isTemplate ? ( + + ) : ( <> -
- + {authenticated ? null : } + {publishMode === 'edgeless' ? : null} + {authenticated ? ( + <> +
+ + + ) : null} - ) : null} + )}
); }; diff --git a/packages/frontend/core/src/hooks/use-navigate-helper.ts b/packages/frontend/core/src/hooks/use-navigate-helper.ts index d9d3b2cea..5c7fd27ea 100644 --- a/packages/frontend/core/src/hooks/use-navigate-helper.ts +++ b/packages/frontend/core/src/hooks/use-navigate-helper.ts @@ -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, ] ); } diff --git a/packages/frontend/core/src/modules/import-template/services/import.ts b/packages/frontend/core/src/modules/import-template/services/import.ts index 35c070014..b97a4e7cf 100644 --- a/packages/frontend/core/src/modules/import-template/services/import.ts +++ b/packages/frontend/core/src/modules/import-template/services/import.ts @@ -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( diff --git a/packages/frontend/core/src/modules/import-template/store/downloader.ts b/packages/frontend/core/src/modules/import-template/store/downloader.ts index f87a4963e..e01fbe1e3 100644 --- a/packages/frontend/core/src/modules/import-template/store/downloader.ts +++ b/packages/frontend/core/src/modules/import-template/store/downloader.ts @@ -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 diff --git a/packages/frontend/core/src/pages/workspace/share/share-header.tsx b/packages/frontend/core/src/pages/workspace/share/share-header.tsx index cc57947a5..f09d01a2a 100644 --- a/packages/frontend/core/src/pages/workspace/share/share-header.tsx +++ b/packages/frontend/core/src/pages/workspace/share/share-header.tsx @@ -11,10 +11,14 @@ export function ShareHeader({ pageId, publishMode, docCollection, + isTemplate, + templateName, }: { pageId: string; publishMode: DocMode; docCollection: DocCollection; + isTemplate?: boolean; + templateName?: string; }) { return (
@@ -23,8 +27,10 @@ export function ShareHeader({
diff --git a/packages/frontend/core/src/pages/workspace/share/share-page.tsx b/packages/frontend/core/src/pages/workspace/share/share-page.tsx index 741909df0..30f11d03a 100644 --- a/packages/frontend/core/src/pages/workspace/share/share-page.tsx +++ b/packages/frontend/core/src/pages/workspace/share/share-page.tsx @@ -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(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} /> 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'), diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 2b8577751..a754b821f 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -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",