From 7299efe16a71db101ea2a1d7e673c5b7136b8f58 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Sat, 1 Apr 2023 01:40:30 +0800 Subject: [PATCH] fix: first workspace create logic (#1773) --- apps/web/package.json | 1 + apps/web/src/atoms/first-load.ts | 25 ++----- apps/web/src/atoms/index.ts | 14 ++-- .../src/hooks/affine/use-sidebar-status.ts | 6 +- .../hooks/current/use-current-workspace.ts | 4 +- .../src/hooks/use-create-first-workspace.ts | 73 +++++++++---------- apps/web/src/hooks/use-workspaces.ts | 4 + apps/web/src/pages/index.tsx | 6 ++ .../pages/workspace/[workspaceId]/setting.tsx | 4 +- apps/web/src/plugins/affine/index.tsx | 3 + apps/web/src/plugins/local/index.tsx | 10 --- packages/component/package.json | 1 + packages/jotai/README.md | 3 + packages/jotai/package.json | 9 +++ packages/jotai/src/index.ts | 48 ++++++++++++ packages/jotai/tsconfig.json | 10 +++ packages/workspace/src/affine/atom.ts | 8 +- packages/workspace/src/atom.ts | 4 +- tsconfig.json | 1 + yarn.lock | 11 +++ 20 files changed, 158 insertions(+), 87 deletions(-) create mode 100644 packages/jotai/README.md create mode 100644 packages/jotai/package.json create mode 100644 packages/jotai/src/index.ts create mode 100644 packages/jotai/tsconfig.json diff --git a/apps/web/package.json b/apps/web/package.json index cbb14b3631..39515fb238 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -14,6 +14,7 @@ "@affine/debug": "workspace:*", "@affine/env": "workspace:*", "@affine/i18n": "workspace:*", + "@affine/jotai": "workspace:*", "@affine/templates": "workspace:*", "@affine/workspace": "workspace:*", "@blocksuite/blocks": "0.5.0-20230324040005-14417c2", diff --git a/apps/web/src/atoms/first-load.ts b/apps/web/src/atoms/first-load.ts index e94138af5f..5687cb8ee8 100644 --- a/apps/web/src/atoms/first-load.ts +++ b/apps/web/src/atoms/first-load.ts @@ -1,28 +1,19 @@ -import { atomWithStorage } from 'jotai/utils'; +import { atomWithSyncStorage } from '@affine/jotai'; export type Visibility = Record; const DEFAULT_VALUE = '0.0.0'; -//atomWithStorage always uses initial value when first render -//https://github.com/pmndrs/jotai/discussions/1737 -function getInitialValue() { - if (typeof window !== 'undefined') { - const storedValue = window.localStorage.getItem('lastVersion'); - if (storedValue) { - return JSON.parse(storedValue); - } - } - return DEFAULT_VALUE; -} - -export const lastVersionAtom = atomWithStorage( +export const lastVersionAtom = atomWithSyncStorage( 'lastVersion', - getInitialValue() + DEFAULT_VALUE +); +export const guideHiddenAtom = atomWithSyncStorage( + 'guideHidden', + {} ); -export const guideHiddenAtom = atomWithStorage('guideHidden', {}); -export const guideHiddenUntilNextUpdateAtom = atomWithStorage( +export const guideHiddenUntilNextUpdateAtom = atomWithSyncStorage( 'guideHiddenUntilNextUpdate', {} ); diff --git a/apps/web/src/atoms/index.ts b/apps/web/src/atoms/index.ts index cc0e9dcbfd..d44fb0579e 100644 --- a/apps/web/src/atoms/index.ts +++ b/apps/web/src/atoms/index.ts @@ -1,9 +1,9 @@ +import { atomWithSyncStorage } from '@affine/jotai'; import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom'; import type { EditorContainer } from '@blocksuite/editor'; import type { Page } from '@blocksuite/store'; import { assertExists } from '@blocksuite/store'; import { atom } from 'jotai'; -import { atomWithStorage } from 'jotai/utils'; import { unstable_batchedUpdates } from 'react-dom'; import { WorkspacePlugins } from '../plugins'; @@ -57,16 +57,12 @@ type View = { id: string; mode: 'page' | 'edgeless' }; export type WorkspaceRecentViews = Record; -export const workspaceRecentViewsAtom = atomWithStorage( - 'recentViews', - {} -); +export const workspaceRecentViewsAtom = + atomWithSyncStorage('recentViews', {}); export type PreferredModeRecord = Record; -export const workspacePreferredModeAtom = atomWithStorage( - 'preferredMode', - {} -); +export const workspacePreferredModeAtom = + atomWithSyncStorage('preferredMode', {}); export const workspaceRecentViresWriteAtom = atom( null, diff --git a/apps/web/src/hooks/affine/use-sidebar-status.ts b/apps/web/src/hooks/affine/use-sidebar-status.ts index 11642f2724..cce4b10191 100644 --- a/apps/web/src/hooks/affine/use-sidebar-status.ts +++ b/apps/web/src/hooks/affine/use-sidebar-status.ts @@ -1,8 +1,8 @@ +import { atomWithSyncStorage } from '@affine/jotai'; import { atom, useAtom } from 'jotai'; -import { atomWithStorage } from 'jotai/utils'; -const sideBarOpenAtom = atomWithStorage('sidebarOpen', true); -const sideBarWidthAtom = atomWithStorage('sidebarWidth', 256); +const sideBarOpenAtom = atomWithSyncStorage('sidebarOpen', true); +const sideBarWidthAtom = atomWithSyncStorage('sidebarWidth', 256); const sidebarResizingAtom = atom(false); export function useSidebarStatus() { diff --git a/apps/web/src/hooks/current/use-current-workspace.ts b/apps/web/src/hooks/current/use-current-workspace.ts index c787dd302f..4992bb8e06 100644 --- a/apps/web/src/hooks/current/use-current-workspace.ts +++ b/apps/web/src/hooks/current/use-current-workspace.ts @@ -1,5 +1,5 @@ +import { atomWithSyncStorage } from '@affine/jotai'; import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; -import { atomWithStorage } from 'jotai/utils'; import { useCallback } from 'react'; import { @@ -17,7 +17,7 @@ export const currentWorkspaceAtom = atom>( } ); -export const lastWorkspaceIdAtom = atomWithStorage( +export const lastWorkspaceIdAtom = atomWithSyncStorage( 'last_workspace_id', null ); diff --git a/apps/web/src/hooks/use-create-first-workspace.ts b/apps/web/src/hooks/use-create-first-workspace.ts index 3bbc0b9892..4388532853 100644 --- a/apps/web/src/hooks/use-create-first-workspace.ts +++ b/apps/web/src/hooks/use-create-first-workspace.ts @@ -1,47 +1,46 @@ +import { DebugLogger } from '@affine/debug'; import { DEFAULT_WORKSPACE_NAME } from '@affine/env'; -import { jotaiWorkspacesAtom } from '@affine/workspace/atom'; +import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom'; import { WorkspaceFlavour } from '@affine/workspace/type'; import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils'; import { assertEquals, assertExists, nanoid } from '@blocksuite/store'; -import { useAtom } from 'jotai'; import { useEffect } from 'react'; import { LocalPlugin } from '../plugins/local'; -export function useCreateFirstWorkspace() { - const [jotaiWorkspaces, set] = useAtom(jotaiWorkspacesAtom); - useEffect(() => { - const controller = new AbortController(); +const logger = new DebugLogger('use-create-first-workspace'); - /** - * Create a first workspace, only just once for a browser - */ - async function createFirst() { - const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace( - nanoid(), - (_: string) => undefined - ); - blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME); - const id = await LocalPlugin.CRUD.create(blockSuiteWorkspace); - const workspace = await LocalPlugin.CRUD.get(id); - assertExists(workspace); - assertEquals(workspace.id, id); - set([ - { - id: workspace.id, - flavour: WorkspaceFlavour.LOCAL, - }, - ]); - } - if ( - jotaiWorkspaces.length === 0 && - localStorage.getItem('first') !== 'true' - ) { - localStorage.setItem('first', 'true'); - createFirst(); - } - return () => { - controller.abort(); - }; - }, [jotaiWorkspaces.length, set]); +export function useCreateFirstWorkspace() { + // may not need use effect at all, right? + useEffect(() => { + return jotaiStore.sub(jotaiWorkspacesAtom, () => { + const workspaces = jotaiStore.get(jotaiWorkspacesAtom); + + if (workspaces.length === 0) { + createFirst(); + } + + /** + * Create a first workspace, only just once for a browser + */ + async function createFirst() { + const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace( + nanoid(), + (_: string) => undefined + ); + blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME); + const id = await LocalPlugin.CRUD.create(blockSuiteWorkspace); + const workspace = await LocalPlugin.CRUD.get(id); + assertExists(workspace); + assertEquals(workspace.id, id); + jotaiStore.set(jotaiWorkspacesAtom, [ + { + id: workspace.id, + flavour: WorkspaceFlavour.LOCAL, + }, + ]); + logger.info('created local workspace', id); + } + }); + }, []); } diff --git a/apps/web/src/hooks/use-workspaces.ts b/apps/web/src/hooks/use-workspaces.ts index 437ca646b0..27a6eb3df4 100644 --- a/apps/web/src/hooks/use-workspaces.ts +++ b/apps/web/src/hooks/use-workspaces.ts @@ -1,3 +1,4 @@ +import { DebugLogger } from '@affine/debug'; import { jotaiWorkspacesAtom } from '@affine/workspace/atom'; import type { LocalWorkspace } from '@affine/workspace/type'; import { WorkspaceFlavour } from '@affine/workspace/type'; @@ -15,6 +16,8 @@ export function useWorkspaces(): AllWorkspace[] { return useAtomValue(workspacesAtom); } +const logger = new DebugLogger('use-workspaces'); + export function useWorkspacesHelper() { const workspaces = useWorkspaces(); const jotaiWorkspaces = useAtomValue(jotaiWorkspacesAtom); @@ -48,6 +51,7 @@ export function useWorkspacesHelper() { flavour: WorkspaceFlavour.LOCAL, }, ]); + logger.debug('created local workspace', id); return id; }, [set] diff --git a/apps/web/src/pages/index.tsx b/apps/web/src/pages/index.tsx index 4a7195e210..23eb280205 100644 --- a/apps/web/src/pages/index.tsx +++ b/apps/web/src/pages/index.tsx @@ -1,3 +1,4 @@ +import { DebugLogger } from '@affine/debug'; import type { NextPage } from 'next'; import { useRouter } from 'next/router'; import React, { Suspense, useEffect } from 'react'; @@ -9,6 +10,8 @@ import { RouteLogic, useRouterHelper } from '../hooks/use-router-helper'; import { useWorkspaces } from '../hooks/use-workspaces'; import { WorkspaceSubPath } from '../shared'; +const logger = new DebugLogger('IndexPage'); + const IndexPageInner = () => { const router = useRouter(); const { jumpToPage, jumpToSubPath } = useRouterHelper(router); @@ -28,11 +31,13 @@ const IndexPageInner = () => { const pageId = targetWorkspace.blockSuiteWorkspace.meta.pageMetas.at(0)?.id; if (pageId) { + logger.debug('Found target workspace. Jump to page', pageId); jumpToPage(targetWorkspace.id, pageId, RouteLogic.REPLACE); return; } else { const clearId = setTimeout(() => { dispose.dispose(); + logger.debug('Found target workspace. Jump to all pages'); jumpToSubPath( targetWorkspace.id, WorkspaceSubPath.ALL, @@ -50,6 +55,7 @@ const IndexPageInner = () => { }; } } else { + logger.debug('No target workspace. jump to all pages'); // fixme: should create new workspace jumpToSubPath('ERROR', WorkspaceSubPath.ALL, RouteLogic.REPLACE); } diff --git a/apps/web/src/pages/workspace/[workspaceId]/setting.tsx b/apps/web/src/pages/workspace/[workspaceId]/setting.tsx index d167c865da..963b162dc7 100644 --- a/apps/web/src/pages/workspace/[workspaceId]/setting.tsx +++ b/apps/web/src/pages/workspace/[workspaceId]/setting.tsx @@ -1,4 +1,5 @@ import { useTranslation } from '@affine/i18n'; +import { atomWithSyncStorage } from '@affine/jotai'; import { currentAffineUserAtom } from '@affine/workspace/affine/atom'; import { getLoginStorage, @@ -15,7 +16,6 @@ import { import { SettingsIcon } from '@blocksuite/icons'; import { assertExists } from '@blocksuite/store'; import { useAtom, useSetAtom } from 'jotai'; -import { atomWithStorage } from 'jotai/utils'; import Head from 'next/head'; import { useRouter } from 'next/router'; import React, { useCallback, useEffect } from 'react'; @@ -32,7 +32,7 @@ import { WorkspaceLayout } from '../../../layouts'; import { WorkspacePlugins } from '../../../plugins'; import type { NextPageWithLayout } from '../../../shared'; -const settingPanelAtom = atomWithStorage( +const settingPanelAtom = atomWithSyncStorage( 'workspaceId', settingPanel.General ); diff --git a/apps/web/src/plugins/affine/index.tsx b/apps/web/src/plugins/affine/index.tsx index a694a01ca0..ec1abbf781 100644 --- a/apps/web/src/plugins/affine/index.tsx +++ b/apps/web/src/plugins/affine/index.tsx @@ -131,6 +131,9 @@ export const AffinePlugin: WorkspacePlugin = { }, list: async () => { const allWorkspaces = getPersistenceAllWorkspace(); + if (!getLoginStorage()) { + return allWorkspaces; + } try { const workspaces = await affineApis.getWorkspaces().then(workspaces => { return workspaces.map(workspace => { diff --git a/apps/web/src/plugins/local/index.tsx b/apps/web/src/plugins/local/index.tsx index e776a008f0..4788c7ff3e 100644 --- a/apps/web/src/plugins/local/index.tsx +++ b/apps/web/src/plugins/local/index.tsx @@ -1,4 +1,3 @@ -import { DEFAULT_WORKSPACE_NAME } from '@affine/env'; import type { LocalWorkspace } from '@affine/workspace/type'; import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type'; import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils'; @@ -90,15 +89,6 @@ export const LocalPlugin: WorkspacePlugin = { ) ) ).filter(item => item !== null) as LocalWorkspace[]; - if (data.length === 0) { - const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace( - nanoid(), - (_: string) => undefined - ); - blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME); - await LocalPlugin.CRUD.create(blockSuiteWorkspace); - return LocalPlugin.CRUD.list(); - } return data; }, }, diff --git a/packages/component/package.json b/packages/component/package.json index 2f542e3be3..156b0e6bab 100644 --- a/packages/component/package.json +++ b/packages/component/package.json @@ -15,6 +15,7 @@ "dependencies": { "@affine/debug": "workspace:*", "@affine/i18n": "workspace:*", + "@affine/jotai": "workspace:*", "@blocksuite/blocks": "0.5.0-20230324040005-14417c2", "@blocksuite/editor": "0.5.0-20230324040005-14417c2", "@blocksuite/global": "0.5.0-20230324040005-14417c2", diff --git a/packages/jotai/README.md b/packages/jotai/README.md new file mode 100644 index 0000000000..35e0d151b3 --- /dev/null +++ b/packages/jotai/README.md @@ -0,0 +1,3 @@ +# @affine/jotai + +Custom Jotai utilities. diff --git a/packages/jotai/package.json b/packages/jotai/package.json new file mode 100644 index 0000000000..5d57036b6c --- /dev/null +++ b/packages/jotai/package.json @@ -0,0 +1,9 @@ +{ + "name": "@affine/jotai", + "private": true, + "main": "./src/index.ts", + "dependencies": { + "@affine/env": "workspace:*", + "jotai": "^2.0.3" + } +} diff --git a/packages/jotai/src/index.ts b/packages/jotai/src/index.ts new file mode 100644 index 0000000000..01312073d9 --- /dev/null +++ b/packages/jotai/src/index.ts @@ -0,0 +1,48 @@ +import { createJSONStorage, RESET } from 'jotai/utils'; +import { atom } from 'jotai/vanilla'; + +const storage = createJSONStorage(() => + typeof window !== 'undefined' + ? window.localStorage + : (undefined as unknown as Storage) +); + +type SetStateActionWithReset = + | Value + | typeof RESET + | ((prev: Value) => Value | typeof RESET); + +// similar to atomWithStorage, but will not trigger twice on init +// https://github.com/pmndrs/jotai/discussions/1737 +export function atomWithSyncStorage(key: string, initialValue: Value) { + const storedValue = storage.getItem(key) as Value; + const _value = + typeof storedValue === 'symbol' + ? initialValue + : (storage.getItem(key) as Value); + const baseAtom = atom(_value); + + baseAtom.onMount = setAtom => { + if (storage.subscribe) { + return storage.subscribe(key, setAtom); + } + }; + + const anAtom = atom( + get => get(baseAtom), + (get, set, update: SetStateActionWithReset) => { + const nextValue = + typeof update === 'function' + ? (update as (prev: Value) => Value | typeof RESET)(get(baseAtom)) + : update; + if (nextValue === RESET) { + set(baseAtom, initialValue); + return storage.removeItem(key); + } + set(baseAtom, nextValue); + return storage.setItem(key, nextValue); + } + ); + + return anAtom; +} diff --git a/packages/jotai/tsconfig.json b/packages/jotai/tsconfig.json new file mode 100644 index 0000000000..d55dc90ebc --- /dev/null +++ b/packages/jotai/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "target": "ESNext", + "sourceMap": true + }, + "include": ["./src"], + "exclude": ["node_modules"] +} diff --git a/packages/workspace/src/affine/atom.ts b/packages/workspace/src/affine/atom.ts index 7ccf20bdb6..c5f58ee7a0 100644 --- a/packages/workspace/src/affine/atom.ts +++ b/packages/workspace/src/affine/atom.ts @@ -1,7 +1,5 @@ +import { atomWithSyncStorage } from '@affine/jotai'; import type { AccessTokenMessage } from '@affine/workspace/affine/login'; -import { atomWithStorage } from 'jotai/utils'; -export const currentAffineUserAtom = atomWithStorage( - 'affine-user-atom', - null -); +export const currentAffineUserAtom = + atomWithSyncStorage('affine-user-atom', null); diff --git a/packages/workspace/src/atom.ts b/packages/workspace/src/atom.ts index 6ce7776136..91210d34f8 100644 --- a/packages/workspace/src/atom.ts +++ b/packages/workspace/src/atom.ts @@ -1,6 +1,6 @@ +import { atomWithSyncStorage } from '@affine/jotai'; import type { WorkspaceFlavour } from '@affine/workspace/type'; import { createStore } from 'jotai/index'; -import { atomWithStorage } from 'jotai/utils'; export type JotaiWorkspace = { id: string; @@ -9,7 +9,7 @@ export type JotaiWorkspace = { // root primitive atom that stores the list of workspaces which could be used in the app // if a workspace is not in this list, it should not be used in the app -export const jotaiWorkspacesAtom = atomWithStorage( +export const jotaiWorkspacesAtom = atomWithSyncStorage( 'jotai-workspaces', [] ); diff --git a/tsconfig.json b/tsconfig.json index 83c1964aa1..de7be6d136 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,7 @@ "@affine/templates/*": ["./packages/templates/src/*"], "@affine/i18n": ["./packages/i18n/src"], "@affine/debug": ["./packages/debug"], + "@affine/jotai": ["./packages/jotai"], "@affine/env": ["./packages/env"], "@affine/env/*": ["./packages/env/src/*"], "@affine/utils": ["./packages/utils"], diff --git a/yarn.lock b/yarn.lock index 3fd0003d73..4b9264b05d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -69,6 +69,7 @@ __metadata: dependencies: "@affine/debug": "workspace:*" "@affine/i18n": "workspace:*" + "@affine/jotai": "workspace:*" "@blocksuite/blocks": 0.5.0-20230324040005-14417c2 "@blocksuite/editor": 0.5.0-20230324040005-14417c2 "@blocksuite/global": 0.5.0-20230324040005-14417c2 @@ -151,6 +152,15 @@ __metadata: languageName: unknown linkType: soft +"@affine/jotai@workspace:*, @affine/jotai@workspace:packages/jotai": + version: 0.0.0-use.local + resolution: "@affine/jotai@workspace:packages/jotai" + dependencies: + "@affine/env": "workspace:*" + jotai: ^2.0.3 + languageName: unknown + linkType: soft + "@affine/octobase-node@workspace:packages/octobase-node": version: 0.0.0-use.local resolution: "@affine/octobase-node@workspace:packages/octobase-node" @@ -175,6 +185,7 @@ __metadata: "@affine/debug": "workspace:*" "@affine/env": "workspace:*" "@affine/i18n": "workspace:*" + "@affine/jotai": "workspace:*" "@affine/templates": "workspace:*" "@affine/workspace": "workspace:*" "@blocksuite/blocks": 0.5.0-20230324040005-14417c2