fix: first workspace create logic (#1773)

This commit is contained in:
Peng Xiao
2023-04-01 01:40:30 +08:00
committed by GitHub
parent fd65dd66a1
commit 7299efe16a
20 changed files with 158 additions and 87 deletions

View File

@@ -14,6 +14,7 @@
"@affine/debug": "workspace:*", "@affine/debug": "workspace:*",
"@affine/env": "workspace:*", "@affine/env": "workspace:*",
"@affine/i18n": "workspace:*", "@affine/i18n": "workspace:*",
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*", "@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*", "@affine/workspace": "workspace:*",
"@blocksuite/blocks": "0.5.0-20230324040005-14417c2", "@blocksuite/blocks": "0.5.0-20230324040005-14417c2",

View File

@@ -1,28 +1,19 @@
import { atomWithStorage } from 'jotai/utils'; import { atomWithSyncStorage } from '@affine/jotai';
export type Visibility = Record<string, boolean>; export type Visibility = Record<string, boolean>;
const DEFAULT_VALUE = '0.0.0'; const DEFAULT_VALUE = '0.0.0';
//atomWithStorage always uses initial value when first render
//https://github.com/pmndrs/jotai/discussions/1737
function getInitialValue() { export const lastVersionAtom = atomWithSyncStorage(
if (typeof window !== 'undefined') {
const storedValue = window.localStorage.getItem('lastVersion');
if (storedValue) {
return JSON.parse(storedValue);
}
}
return DEFAULT_VALUE;
}
export const lastVersionAtom = atomWithStorage(
'lastVersion', 'lastVersion',
getInitialValue() DEFAULT_VALUE
);
export const guideHiddenAtom = atomWithSyncStorage<Visibility>(
'guideHidden',
{}
); );
export const guideHiddenAtom = atomWithStorage<Visibility>('guideHidden', {});
export const guideHiddenUntilNextUpdateAtom = atomWithStorage<Visibility>( export const guideHiddenUntilNextUpdateAtom = atomWithSyncStorage<Visibility>(
'guideHiddenUntilNextUpdate', 'guideHiddenUntilNextUpdate',
{} {}
); );

View File

@@ -1,9 +1,9 @@
import { atomWithSyncStorage } from '@affine/jotai';
import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom'; import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom';
import type { EditorContainer } from '@blocksuite/editor'; import type { EditorContainer } from '@blocksuite/editor';
import type { Page } from '@blocksuite/store'; import type { Page } from '@blocksuite/store';
import { assertExists } from '@blocksuite/store'; import { assertExists } from '@blocksuite/store';
import { atom } from 'jotai'; import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import { unstable_batchedUpdates } from 'react-dom'; import { unstable_batchedUpdates } from 'react-dom';
import { WorkspacePlugins } from '../plugins'; import { WorkspacePlugins } from '../plugins';
@@ -57,16 +57,12 @@ type View = { id: string; mode: 'page' | 'edgeless' };
export type WorkspaceRecentViews = Record<string, View[]>; export type WorkspaceRecentViews = Record<string, View[]>;
export const workspaceRecentViewsAtom = atomWithStorage<WorkspaceRecentViews>( export const workspaceRecentViewsAtom =
'recentViews', atomWithSyncStorage<WorkspaceRecentViews>('recentViews', {});
{}
);
export type PreferredModeRecord = Record<Page['id'], 'page' | 'edgeless'>; export type PreferredModeRecord = Record<Page['id'], 'page' | 'edgeless'>;
export const workspacePreferredModeAtom = atomWithStorage<PreferredModeRecord>( export const workspacePreferredModeAtom =
'preferredMode', atomWithSyncStorage<PreferredModeRecord>('preferredMode', {});
{}
);
export const workspaceRecentViresWriteAtom = atom<null, [string, View], View[]>( export const workspaceRecentViresWriteAtom = atom<null, [string, View], View[]>(
null, null,

View File

@@ -1,8 +1,8 @@
import { atomWithSyncStorage } from '@affine/jotai';
import { atom, useAtom } from 'jotai'; import { atom, useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
const sideBarOpenAtom = atomWithStorage('sidebarOpen', true); const sideBarOpenAtom = atomWithSyncStorage('sidebarOpen', true);
const sideBarWidthAtom = atomWithStorage('sidebarWidth', 256); const sideBarWidthAtom = atomWithSyncStorage('sidebarWidth', 256);
const sidebarResizingAtom = atom(false); const sidebarResizingAtom = atom(false);
export function useSidebarStatus() { export function useSidebarStatus() {

View File

@@ -1,5 +1,5 @@
import { atomWithSyncStorage } from '@affine/jotai';
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { import {
@@ -17,7 +17,7 @@ export const currentWorkspaceAtom = atom<Promise<AllWorkspace | null>>(
} }
); );
export const lastWorkspaceIdAtom = atomWithStorage<string | null>( export const lastWorkspaceIdAtom = atomWithSyncStorage<string | null>(
'last_workspace_id', 'last_workspace_id',
null null
); );

View File

@@ -1,47 +1,46 @@
import { DebugLogger } from '@affine/debug';
import { DEFAULT_WORKSPACE_NAME } from '@affine/env'; 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 { WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils'; import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { assertEquals, assertExists, nanoid } from '@blocksuite/store'; import { assertEquals, assertExists, nanoid } from '@blocksuite/store';
import { useAtom } from 'jotai';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { LocalPlugin } from '../plugins/local'; import { LocalPlugin } from '../plugins/local';
export function useCreateFirstWorkspace() { const logger = new DebugLogger('use-create-first-workspace');
const [jotaiWorkspaces, set] = useAtom(jotaiWorkspacesAtom);
useEffect(() => {
const controller = new AbortController();
/** export function useCreateFirstWorkspace() {
* Create a first workspace, only just once for a browser // may not need use effect at all, right?
*/ useEffect(() => {
async function createFirst() { return jotaiStore.sub(jotaiWorkspacesAtom, () => {
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace( const workspaces = jotaiStore.get(jotaiWorkspacesAtom);
nanoid(),
(_: string) => undefined if (workspaces.length === 0) {
); createFirst();
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME); }
const id = await LocalPlugin.CRUD.create(blockSuiteWorkspace);
const workspace = await LocalPlugin.CRUD.get(id); /**
assertExists(workspace); * Create a first workspace, only just once for a browser
assertEquals(workspace.id, id); */
set([ async function createFirst() {
{ const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
id: workspace.id, nanoid(),
flavour: WorkspaceFlavour.LOCAL, (_: string) => undefined
}, );
]); blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
} const id = await LocalPlugin.CRUD.create(blockSuiteWorkspace);
if ( const workspace = await LocalPlugin.CRUD.get(id);
jotaiWorkspaces.length === 0 && assertExists(workspace);
localStorage.getItem('first') !== 'true' assertEquals(workspace.id, id);
) { jotaiStore.set(jotaiWorkspacesAtom, [
localStorage.setItem('first', 'true'); {
createFirst(); id: workspace.id,
} flavour: WorkspaceFlavour.LOCAL,
return () => { },
controller.abort(); ]);
}; logger.info('created local workspace', id);
}, [jotaiWorkspaces.length, set]); }
});
}, []);
} }

View File

@@ -1,3 +1,4 @@
import { DebugLogger } from '@affine/debug';
import { jotaiWorkspacesAtom } from '@affine/workspace/atom'; import { jotaiWorkspacesAtom } from '@affine/workspace/atom';
import type { LocalWorkspace } from '@affine/workspace/type'; import type { LocalWorkspace } from '@affine/workspace/type';
import { WorkspaceFlavour } from '@affine/workspace/type'; import { WorkspaceFlavour } from '@affine/workspace/type';
@@ -15,6 +16,8 @@ export function useWorkspaces(): AllWorkspace[] {
return useAtomValue(workspacesAtom); return useAtomValue(workspacesAtom);
} }
const logger = new DebugLogger('use-workspaces');
export function useWorkspacesHelper() { export function useWorkspacesHelper() {
const workspaces = useWorkspaces(); const workspaces = useWorkspaces();
const jotaiWorkspaces = useAtomValue(jotaiWorkspacesAtom); const jotaiWorkspaces = useAtomValue(jotaiWorkspacesAtom);
@@ -48,6 +51,7 @@ export function useWorkspacesHelper() {
flavour: WorkspaceFlavour.LOCAL, flavour: WorkspaceFlavour.LOCAL,
}, },
]); ]);
logger.debug('created local workspace', id);
return id; return id;
}, },
[set] [set]

View File

@@ -1,3 +1,4 @@
import { DebugLogger } from '@affine/debug';
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { Suspense, useEffect } from 'react'; 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 { useWorkspaces } from '../hooks/use-workspaces';
import { WorkspaceSubPath } from '../shared'; import { WorkspaceSubPath } from '../shared';
const logger = new DebugLogger('IndexPage');
const IndexPageInner = () => { const IndexPageInner = () => {
const router = useRouter(); const router = useRouter();
const { jumpToPage, jumpToSubPath } = useRouterHelper(router); const { jumpToPage, jumpToSubPath } = useRouterHelper(router);
@@ -28,11 +31,13 @@ const IndexPageInner = () => {
const pageId = const pageId =
targetWorkspace.blockSuiteWorkspace.meta.pageMetas.at(0)?.id; targetWorkspace.blockSuiteWorkspace.meta.pageMetas.at(0)?.id;
if (pageId) { if (pageId) {
logger.debug('Found target workspace. Jump to page', pageId);
jumpToPage(targetWorkspace.id, pageId, RouteLogic.REPLACE); jumpToPage(targetWorkspace.id, pageId, RouteLogic.REPLACE);
return; return;
} else { } else {
const clearId = setTimeout(() => { const clearId = setTimeout(() => {
dispose.dispose(); dispose.dispose();
logger.debug('Found target workspace. Jump to all pages');
jumpToSubPath( jumpToSubPath(
targetWorkspace.id, targetWorkspace.id,
WorkspaceSubPath.ALL, WorkspaceSubPath.ALL,
@@ -50,6 +55,7 @@ const IndexPageInner = () => {
}; };
} }
} else { } else {
logger.debug('No target workspace. jump to all pages');
// fixme: should create new workspace // fixme: should create new workspace
jumpToSubPath('ERROR', WorkspaceSubPath.ALL, RouteLogic.REPLACE); jumpToSubPath('ERROR', WorkspaceSubPath.ALL, RouteLogic.REPLACE);
} }

View File

@@ -1,4 +1,5 @@
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { atomWithSyncStorage } from '@affine/jotai';
import { currentAffineUserAtom } from '@affine/workspace/affine/atom'; import { currentAffineUserAtom } from '@affine/workspace/affine/atom';
import { import {
getLoginStorage, getLoginStorage,
@@ -15,7 +16,6 @@ import {
import { SettingsIcon } from '@blocksuite/icons'; import { SettingsIcon } from '@blocksuite/icons';
import { assertExists } from '@blocksuite/store'; import { assertExists } from '@blocksuite/store';
import { useAtom, useSetAtom } from 'jotai'; import { useAtom, useSetAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
@@ -32,7 +32,7 @@ import { WorkspaceLayout } from '../../../layouts';
import { WorkspacePlugins } from '../../../plugins'; import { WorkspacePlugins } from '../../../plugins';
import type { NextPageWithLayout } from '../../../shared'; import type { NextPageWithLayout } from '../../../shared';
const settingPanelAtom = atomWithStorage<SettingPanel>( const settingPanelAtom = atomWithSyncStorage<SettingPanel>(
'workspaceId', 'workspaceId',
settingPanel.General settingPanel.General
); );

View File

@@ -131,6 +131,9 @@ export const AffinePlugin: WorkspacePlugin<WorkspaceFlavour.AFFINE> = {
}, },
list: async () => { list: async () => {
const allWorkspaces = getPersistenceAllWorkspace(); const allWorkspaces = getPersistenceAllWorkspace();
if (!getLoginStorage()) {
return allWorkspaces;
}
try { try {
const workspaces = await affineApis.getWorkspaces().then(workspaces => { const workspaces = await affineApis.getWorkspaces().then(workspaces => {
return workspaces.map(workspace => { return workspaces.map(workspace => {

View File

@@ -1,4 +1,3 @@
import { DEFAULT_WORKSPACE_NAME } from '@affine/env';
import type { LocalWorkspace } from '@affine/workspace/type'; import type { LocalWorkspace } from '@affine/workspace/type';
import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type'; import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils'; import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
@@ -90,15 +89,6 @@ export const LocalPlugin: WorkspacePlugin<WorkspaceFlavour.LOCAL> = {
) )
) )
).filter(item => item !== null) as LocalWorkspace[]; ).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; return data;
}, },
}, },

View File

@@ -15,6 +15,7 @@
"dependencies": { "dependencies": {
"@affine/debug": "workspace:*", "@affine/debug": "workspace:*",
"@affine/i18n": "workspace:*", "@affine/i18n": "workspace:*",
"@affine/jotai": "workspace:*",
"@blocksuite/blocks": "0.5.0-20230324040005-14417c2", "@blocksuite/blocks": "0.5.0-20230324040005-14417c2",
"@blocksuite/editor": "0.5.0-20230324040005-14417c2", "@blocksuite/editor": "0.5.0-20230324040005-14417c2",
"@blocksuite/global": "0.5.0-20230324040005-14417c2", "@blocksuite/global": "0.5.0-20230324040005-14417c2",

3
packages/jotai/README.md Normal file
View File

@@ -0,0 +1,3 @@
# @affine/jotai
Custom Jotai utilities.

View File

@@ -0,0 +1,9 @@
{
"name": "@affine/jotai",
"private": true,
"main": "./src/index.ts",
"dependencies": {
"@affine/env": "workspace:*",
"jotai": "^2.0.3"
}
}

View File

@@ -0,0 +1,48 @@
import { createJSONStorage, RESET } from 'jotai/utils';
import { atom } from 'jotai/vanilla';
const storage = createJSONStorage<any>(() =>
typeof window !== 'undefined'
? window.localStorage
: (undefined as unknown as Storage)
);
type SetStateActionWithReset<Value> =
| 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<Value>(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<Value>) => {
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;
}

View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"target": "ESNext",
"sourceMap": true
},
"include": ["./src"],
"exclude": ["node_modules"]
}

View File

@@ -1,7 +1,5 @@
import { atomWithSyncStorage } from '@affine/jotai';
import type { AccessTokenMessage } from '@affine/workspace/affine/login'; import type { AccessTokenMessage } from '@affine/workspace/affine/login';
import { atomWithStorage } from 'jotai/utils';
export const currentAffineUserAtom = atomWithStorage<AccessTokenMessage | null>( export const currentAffineUserAtom =
'affine-user-atom', atomWithSyncStorage<AccessTokenMessage | null>('affine-user-atom', null);
null
);

View File

@@ -1,6 +1,6 @@
import { atomWithSyncStorage } from '@affine/jotai';
import type { WorkspaceFlavour } from '@affine/workspace/type'; import type { WorkspaceFlavour } from '@affine/workspace/type';
import { createStore } from 'jotai/index'; import { createStore } from 'jotai/index';
import { atomWithStorage } from 'jotai/utils';
export type JotaiWorkspace = { export type JotaiWorkspace = {
id: string; 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 // 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 // if a workspace is not in this list, it should not be used in the app
export const jotaiWorkspacesAtom = atomWithStorage<JotaiWorkspace[]>( export const jotaiWorkspacesAtom = atomWithSyncStorage<JotaiWorkspace[]>(
'jotai-workspaces', 'jotai-workspaces',
[] []
); );

View File

@@ -22,6 +22,7 @@
"@affine/templates/*": ["./packages/templates/src/*"], "@affine/templates/*": ["./packages/templates/src/*"],
"@affine/i18n": ["./packages/i18n/src"], "@affine/i18n": ["./packages/i18n/src"],
"@affine/debug": ["./packages/debug"], "@affine/debug": ["./packages/debug"],
"@affine/jotai": ["./packages/jotai"],
"@affine/env": ["./packages/env"], "@affine/env": ["./packages/env"],
"@affine/env/*": ["./packages/env/src/*"], "@affine/env/*": ["./packages/env/src/*"],
"@affine/utils": ["./packages/utils"], "@affine/utils": ["./packages/utils"],

View File

@@ -69,6 +69,7 @@ __metadata:
dependencies: dependencies:
"@affine/debug": "workspace:*" "@affine/debug": "workspace:*"
"@affine/i18n": "workspace:*" "@affine/i18n": "workspace:*"
"@affine/jotai": "workspace:*"
"@blocksuite/blocks": 0.5.0-20230324040005-14417c2 "@blocksuite/blocks": 0.5.0-20230324040005-14417c2
"@blocksuite/editor": 0.5.0-20230324040005-14417c2 "@blocksuite/editor": 0.5.0-20230324040005-14417c2
"@blocksuite/global": 0.5.0-20230324040005-14417c2 "@blocksuite/global": 0.5.0-20230324040005-14417c2
@@ -151,6 +152,15 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft 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": "@affine/octobase-node@workspace:packages/octobase-node":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@affine/octobase-node@workspace:packages/octobase-node" resolution: "@affine/octobase-node@workspace:packages/octobase-node"
@@ -175,6 +185,7 @@ __metadata:
"@affine/debug": "workspace:*" "@affine/debug": "workspace:*"
"@affine/env": "workspace:*" "@affine/env": "workspace:*"
"@affine/i18n": "workspace:*" "@affine/i18n": "workspace:*"
"@affine/jotai": "workspace:*"
"@affine/templates": "workspace:*" "@affine/templates": "workspace:*"
"@affine/workspace": "workspace:*" "@affine/workspace": "workspace:*"
"@blocksuite/blocks": 0.5.0-20230324040005-14417c2 "@blocksuite/blocks": 0.5.0-20230324040005-14417c2