refactor!: remove next.js (#3267)
This commit is contained in:
45
apps/core/src/atoms/__tests__/atom.spec.ts
Normal file
45
apps/core/src/atoms/__tests__/atom.spec.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import { createStore } from 'jotai';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import {
|
||||
pageSettingFamily,
|
||||
pageSettingsAtom,
|
||||
recentPageSettingsAtom,
|
||||
} from '../index';
|
||||
|
||||
describe('page mode atom', () => {
|
||||
test('basic', () => {
|
||||
const store = createStore();
|
||||
const page0SettingAtom = pageSettingFamily('page0');
|
||||
store.set(page0SettingAtom, {
|
||||
mode: 'page',
|
||||
});
|
||||
|
||||
expect(store.get(pageSettingsAtom)).toEqual({
|
||||
page0: {
|
||||
mode: 'page',
|
||||
},
|
||||
});
|
||||
|
||||
expect(store.get(recentPageSettingsAtom)).toEqual([
|
||||
{
|
||||
id: 'page0',
|
||||
mode: 'page',
|
||||
},
|
||||
]);
|
||||
|
||||
const page1SettingAtom = pageSettingFamily('page1');
|
||||
store.set(page1SettingAtom, {
|
||||
mode: 'edgeless',
|
||||
});
|
||||
expect(store.get(recentPageSettingsAtom)).toEqual([
|
||||
{ id: 'page1', mode: 'edgeless' },
|
||||
{ id: 'page0', mode: 'page' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
87
apps/core/src/atoms/guide.ts
Normal file
87
apps/core/src/atoms/guide.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
// these atoms cannot be moved to @affine/jotai since they use atoms from @affine/component
|
||||
import { appSidebarOpenAtom } from '@affine/component/app-sidebar';
|
||||
import { atom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
|
||||
export type Guide = {
|
||||
// should show quick search tips
|
||||
quickSearchTips: boolean;
|
||||
// should show change log
|
||||
changeLog: boolean;
|
||||
// should show recording tips
|
||||
onBoarding: boolean;
|
||||
// should show download client tips
|
||||
downloadClientTip: boolean;
|
||||
};
|
||||
|
||||
const guidePrimitiveAtom = atomWithStorage<Guide>('helper-guide', {
|
||||
quickSearchTips: true,
|
||||
changeLog: true,
|
||||
onBoarding: true,
|
||||
downloadClientTip: true,
|
||||
});
|
||||
|
||||
export const guideQuickSearchTipsAtom = atom<
|
||||
Guide['quickSearchTips'],
|
||||
[open: boolean],
|
||||
void
|
||||
>(
|
||||
get => {
|
||||
const open = get(appSidebarOpenAtom);
|
||||
const guide = get(guidePrimitiveAtom);
|
||||
// only show the tips when the sidebar is closed
|
||||
return guide.quickSearchTips && open === false;
|
||||
},
|
||||
(_, set, open) => {
|
||||
set(guidePrimitiveAtom, tips => ({
|
||||
...tips,
|
||||
quickSearchTips: open,
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
export const guideChangeLogAtom = atom<
|
||||
Guide['changeLog'],
|
||||
[open: boolean],
|
||||
void
|
||||
>(
|
||||
get => {
|
||||
return get(guidePrimitiveAtom).changeLog;
|
||||
},
|
||||
(_, set, open) => {
|
||||
set(guidePrimitiveAtom, tips => ({
|
||||
...tips,
|
||||
changeLog: open,
|
||||
}));
|
||||
}
|
||||
);
|
||||
export const guideOnboardingAtom = atom<
|
||||
Guide['onBoarding'],
|
||||
[open: boolean],
|
||||
void
|
||||
>(
|
||||
get => {
|
||||
return get(guidePrimitiveAtom).onBoarding;
|
||||
},
|
||||
(_, set, open) => {
|
||||
set(guidePrimitiveAtom, tips => ({
|
||||
...tips,
|
||||
onBoarding: open,
|
||||
}));
|
||||
}
|
||||
);
|
||||
export const guideDownloadClientTipAtom = atom<
|
||||
Guide['downloadClientTip'],
|
||||
[open: boolean],
|
||||
void
|
||||
>(
|
||||
get => {
|
||||
return get(guidePrimitiveAtom).downloadClientTip;
|
||||
},
|
||||
(_, set, open) => {
|
||||
set(guidePrimitiveAtom, tips => ({
|
||||
...tips,
|
||||
downloadClientTip: open,
|
||||
}));
|
||||
}
|
||||
);
|
||||
52
apps/core/src/atoms/history.ts
Normal file
52
apps/core/src/atoms/history.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useAtom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export type History = {
|
||||
stack: string[];
|
||||
current: number;
|
||||
skip: boolean;
|
||||
};
|
||||
|
||||
export const MAX_HISTORY = 50;
|
||||
|
||||
export const historyBaseAtom = atomWithStorage<History>('router-history', {
|
||||
stack: [],
|
||||
current: 0,
|
||||
skip: false,
|
||||
});
|
||||
|
||||
export function useHistoryAtom() {
|
||||
const navigate = useNavigate();
|
||||
const [base, setBase] = useAtom(historyBaseAtom);
|
||||
return [
|
||||
base,
|
||||
useCallback(
|
||||
(forward: boolean) => {
|
||||
setBase(prev => {
|
||||
if (forward) {
|
||||
const target = Math.min(prev.stack.length - 1, prev.current + 1);
|
||||
const url = prev.stack[target];
|
||||
navigate(url);
|
||||
return {
|
||||
...prev,
|
||||
current: target,
|
||||
skip: true,
|
||||
};
|
||||
} else {
|
||||
const target = Math.max(0, prev.current - 1);
|
||||
const url = prev.stack[target];
|
||||
navigate(url);
|
||||
return {
|
||||
...prev,
|
||||
current: target,
|
||||
skip: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
[setBase, navigate]
|
||||
),
|
||||
] as const;
|
||||
}
|
||||
102
apps/core/src/atoms/index.ts
Normal file
102
apps/core/src/atoms/index.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { atom } from 'jotai';
|
||||
import { atomFamily, atomWithStorage } from 'jotai/utils';
|
||||
|
||||
import type { CreateWorkspaceMode } from '../components/affine/create-workspace-modal';
|
||||
import type { SettingProps } from '../components/affine/setting-modal';
|
||||
|
||||
// modal atoms
|
||||
export const openWorkspacesModalAtom = atom(false);
|
||||
export const openCreateWorkspaceModalAtom = atom<CreateWorkspaceMode>(false);
|
||||
export const openQuickSearchModalAtom = atom(false);
|
||||
export const openOnboardingModalAtom = atom(false);
|
||||
|
||||
export type SettingAtom = Pick<SettingProps, 'activeTab' | 'workspaceId'> & {
|
||||
open: boolean;
|
||||
};
|
||||
|
||||
export const openSettingModalAtom = atom<SettingAtom>({
|
||||
activeTab: 'appearance',
|
||||
workspaceId: null,
|
||||
open: false,
|
||||
});
|
||||
|
||||
export const openDisableCloudAlertModalAtom = atom(false);
|
||||
|
||||
type PageMode = 'page' | 'edgeless';
|
||||
type PageLocalSetting = {
|
||||
mode: PageMode;
|
||||
};
|
||||
|
||||
type PartialPageLocalSettingWithPageId = Partial<PageLocalSetting> & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const pageSettingsBaseAtom = atomWithStorage(
|
||||
'pageSettings',
|
||||
{} as Record<string, PageLocalSetting>
|
||||
);
|
||||
|
||||
// readonly atom by design
|
||||
export const pageSettingsAtom = atom(get => get(pageSettingsBaseAtom));
|
||||
|
||||
const recentPageSettingsBaseAtom = atomWithStorage<string[]>(
|
||||
'recentPageSettings',
|
||||
[]
|
||||
);
|
||||
|
||||
export const recentPageSettingsAtom = atom<PartialPageLocalSettingWithPageId[]>(
|
||||
get => {
|
||||
const recentPageIDs = get(recentPageSettingsBaseAtom);
|
||||
const pageSettings = get(pageSettingsAtom);
|
||||
return recentPageIDs.map(id => ({
|
||||
...pageSettings[id],
|
||||
id,
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
const defaultPageSetting = {
|
||||
mode: 'page',
|
||||
} satisfies PageLocalSetting;
|
||||
|
||||
export const pageSettingFamily = atomFamily((pageId: string) =>
|
||||
atom(
|
||||
get =>
|
||||
get(pageSettingsBaseAtom)[pageId] ?? {
|
||||
...defaultPageSetting,
|
||||
},
|
||||
(
|
||||
get,
|
||||
set,
|
||||
patch:
|
||||
| Partial<PageLocalSetting>
|
||||
| ((prevSetting: PageLocalSetting | undefined) => void)
|
||||
) => {
|
||||
set(recentPageSettingsBaseAtom, ids => {
|
||||
// pick 3 recent page ids
|
||||
return [...new Set([pageId, ...ids]).values()].slice(0, 3);
|
||||
});
|
||||
const prevSetting = {
|
||||
...defaultPageSetting,
|
||||
...get(pageSettingsBaseAtom)[pageId],
|
||||
};
|
||||
set(pageSettingsBaseAtom, settings => ({
|
||||
...settings,
|
||||
[pageId]: {
|
||||
...prevSetting,
|
||||
...(typeof patch === 'function' ? patch(prevSetting) : patch),
|
||||
},
|
||||
}));
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export const setPageModeAtom = atom(
|
||||
void 0,
|
||||
(get, set, pageId: string, mode: PageMode) => {
|
||||
set(pageSettingFamily(pageId), { mode });
|
||||
}
|
||||
);
|
||||
|
||||
export type PageModeOption = 'all' | 'page' | 'edgeless';
|
||||
export const allPageModeSelectAtom = atom<PageModeOption>('all');
|
||||
34
apps/core/src/atoms/layout.ts
Normal file
34
apps/core/src/atoms/layout.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { ExpectedLayout } from '@toeverything/plugin-infra/type';
|
||||
import { atom } from 'jotai';
|
||||
|
||||
export const contentLayoutBaseAtom = atom<ExpectedLayout>('editor');
|
||||
|
||||
type SetStateAction<Value> = Value | ((prev: Value) => Value);
|
||||
export const contentLayoutAtom = atom<
|
||||
ExpectedLayout,
|
||||
[SetStateAction<ExpectedLayout>],
|
||||
void
|
||||
>(
|
||||
get => get(contentLayoutBaseAtom),
|
||||
(get, set, layout) => {
|
||||
set(contentLayoutBaseAtom, prev => {
|
||||
let setV: (prev: ExpectedLayout) => ExpectedLayout;
|
||||
if (typeof layout !== 'function') {
|
||||
setV = () => layout;
|
||||
} else {
|
||||
setV = layout;
|
||||
}
|
||||
const nextValue = setV(prev);
|
||||
if (nextValue === 'editor') {
|
||||
return nextValue;
|
||||
}
|
||||
if (nextValue.first !== 'editor') {
|
||||
throw new Error('The first element of the layout should be editor.');
|
||||
}
|
||||
if (nextValue.splitPercentage && nextValue.splitPercentage < 70) {
|
||||
throw new Error('The split percentage should be greater than 70.');
|
||||
}
|
||||
return nextValue;
|
||||
});
|
||||
}
|
||||
);
|
||||
12
apps/core/src/atoms/mode.ts
Normal file
12
apps/core/src/atoms/mode.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/manager';
|
||||
import { atom } from 'jotai/vanilla';
|
||||
|
||||
import { pageSettingFamily } from './index';
|
||||
|
||||
export const currentModeAtom = atom<'page' | 'edgeless'>(get => {
|
||||
const pageId = get(currentPageIdAtom);
|
||||
if (!pageId) {
|
||||
return 'page';
|
||||
}
|
||||
return get(pageSettingFamily(pageId)).mode;
|
||||
});
|
||||
82
apps/core/src/atoms/settings.ts
Normal file
82
apps/core/src/atoms/settings.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { isDesktop } from '@affine/env/constant';
|
||||
import { atom, useAtom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
|
||||
export type DateFormats =
|
||||
| 'MM/dd/YYYY'
|
||||
| 'dd/MM/YYYY'
|
||||
| 'YYYY-MM-dd'
|
||||
| 'YYYY.MM.dd'
|
||||
| 'YYYY/MM/dd'
|
||||
| 'dd-MMM-YYYY'
|
||||
| 'dd MMMM YYYY';
|
||||
|
||||
export type AppSetting = {
|
||||
clientBorder: boolean;
|
||||
fullWidthLayout: boolean;
|
||||
windowFrameStyle: 'frameless' | 'NativeTitleBar';
|
||||
fontStyle: FontFamily;
|
||||
dateFormat: DateFormats;
|
||||
startWeekOnMonday: boolean;
|
||||
enableBlurBackground: boolean;
|
||||
enableNoisyBackground: boolean;
|
||||
autoCheckUpdate: boolean;
|
||||
autoDownloadUpdate: boolean;
|
||||
};
|
||||
export const windowFrameStyleOptions: AppSetting['windowFrameStyle'][] = [
|
||||
'frameless',
|
||||
'NativeTitleBar',
|
||||
];
|
||||
|
||||
export const dateFormatOptions: DateFormats[] = [
|
||||
'MM/dd/YYYY',
|
||||
'dd/MM/YYYY',
|
||||
'YYYY-MM-dd',
|
||||
'YYYY.MM.dd',
|
||||
'YYYY/MM/dd',
|
||||
'dd-MMM-YYYY',
|
||||
'dd MMMM YYYY',
|
||||
];
|
||||
|
||||
export type FontFamily = 'Sans' | 'Serif' | 'Mono';
|
||||
|
||||
export const fontStyleOptions = [
|
||||
{ key: 'Sans', value: 'var(--affine-font-sans-family)' },
|
||||
{ key: 'Serif', value: 'var(--affine-font-serif-family)' },
|
||||
{ key: 'Mono', value: 'var(--affine-font-mono-family)' },
|
||||
] satisfies {
|
||||
key: FontFamily;
|
||||
value: string;
|
||||
}[];
|
||||
|
||||
const appSettingBaseAtom = atomWithStorage<AppSetting>('affine-settings', {
|
||||
clientBorder: isDesktop,
|
||||
fullWidthLayout: false,
|
||||
windowFrameStyle: 'frameless',
|
||||
fontStyle: 'Sans',
|
||||
dateFormat: dateFormatOptions[0],
|
||||
startWeekOnMonday: false,
|
||||
enableBlurBackground: true,
|
||||
enableNoisyBackground: true,
|
||||
autoCheckUpdate: true,
|
||||
autoDownloadUpdate: true,
|
||||
});
|
||||
|
||||
type SetStateAction<Value> = Value | ((prev: Value) => Value);
|
||||
|
||||
const appSettingAtom = atom<
|
||||
AppSetting,
|
||||
[SetStateAction<Partial<AppSetting>>],
|
||||
void
|
||||
>(
|
||||
get => get(appSettingBaseAtom),
|
||||
(get, set, apply) => {
|
||||
const prev = get(appSettingBaseAtom);
|
||||
const next = typeof apply === 'function' ? apply(prev) : apply;
|
||||
set(appSettingBaseAtom, { ...prev, ...next });
|
||||
}
|
||||
);
|
||||
|
||||
export const useAppSetting = () => {
|
||||
return useAtom(appSettingAtom);
|
||||
};
|
||||
Reference in New Issue
Block a user