refactor!: remove next.js (#3267)

This commit is contained in:
Alex Yang
2023-07-19 00:53:10 +08:00
committed by GitHub
parent 79227a1e7c
commit 47f12f77f2
296 changed files with 4115 additions and 3617 deletions

View 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' },
]);
});
});

View 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,
}));
}
);

View 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;
}

View 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');

View 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;
});
}
);

View 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;
});

View 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);
};