From 1031fbc7ecbcd8985ea44b025b8ea133ee1e011d Mon Sep 17 00:00:00 2001 From: Himself65 Date: Fri, 28 Apr 2023 15:59:59 -0500 Subject: [PATCH] refactor: guide atoms (#2196) --- apps/web/src/atoms/first-load.ts | 29 -------- apps/web/src/atoms/guide.ts | 54 ++++++++++++++ .../affine/sidebar-switch/index.tsx | 28 +------ .../blocksuite/workspace-header/index.tsx | 19 ++--- .../workspace-slider-bar/changeLog/index.tsx | 27 ++----- apps/web/src/hooks/__tests__/index.spec.tsx | 50 ------------- apps/web/src/hooks/use-is-first-load.ts | 74 ------------------- .../src/components/app-sidebar/index.jotai.ts | 3 +- .../src/components/app-sidebar/index.tsx | 11 ++- playwright.config.ts | 14 ---- tests/parallels/quick-search.spec.ts | 7 +- 11 files changed, 85 insertions(+), 231 deletions(-) delete mode 100644 apps/web/src/atoms/first-load.ts create mode 100644 apps/web/src/atoms/guide.ts delete mode 100644 apps/web/src/hooks/use-is-first-load.ts diff --git a/apps/web/src/atoms/first-load.ts b/apps/web/src/atoms/first-load.ts deleted file mode 100644 index 9d6702e0d0..0000000000 --- a/apps/web/src/atoms/first-load.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { atomWithStorage } from 'jotai/utils'; - -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( - 'lastVersion', - getInitialValue() -); - -export const guideHiddenAtom = atomWithStorage('guideHidden', {}); - -export const guideHiddenUntilNextUpdateAtom = atomWithStorage( - 'guideHiddenUntilNextUpdate', - {} -); diff --git a/apps/web/src/atoms/guide.ts b/apps/web/src/atoms/guide.ts new file mode 100644 index 0000000000..9b872b3e42 --- /dev/null +++ b/apps/web/src/atoms/guide.ts @@ -0,0 +1,54 @@ +// 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; +}; + +const guidePrimitiveAtom = atomWithStorage('helper-guide', { + quickSearchTips: true, + changeLog: true, + onBoarding: 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, + })); + } +); diff --git a/apps/web/src/components/affine/sidebar-switch/index.tsx b/apps/web/src/components/affine/sidebar-switch/index.tsx index 93c155846a..cd09c60d69 100644 --- a/apps/web/src/components/affine/sidebar-switch/index.tsx +++ b/apps/web/src/components/affine/sidebar-switch/index.tsx @@ -5,11 +5,6 @@ import { useTranslation } from '@affine/i18n'; import { useAtom } from 'jotai'; import { useCallback, useEffect, useState } from 'react'; -import { - useGuideHidden, - useGuideHiddenUntilNextUpdate, - useUpdateTipsOnVersionChange, -} from '../../../hooks/use-is-first-load'; import { SidebarSwitchIcon } from './icons'; import { StyledSidebarSwitch } from './style'; type SidebarSwitchProps = { @@ -18,18 +13,14 @@ type SidebarSwitchProps = { }; // fixme: the following code is not correct, SSR will fail because hydrate will not match the client side render -// in `StyledSidebarSwitch` component +// in `StyledSidebarSwitch` a component export const SidebarSwitch = ({ visible = true, tooltipContent, ...props }: SidebarSwitchProps) => { - useUpdateTipsOnVersionChange(); const [open, setOpen] = useAtom(appSidebarOpenAtom); const [tooltipVisible, setTooltipVisible] = useState(false); - const [guideHidden, setGuideHidden] = useGuideHidden(); - const [guideHiddenUntilNextUpdate, setGuideHiddenUntilNextUpdate] = - useGuideHiddenUntilNextUpdate(); const { t } = useTranslation(); const checkIsMac = () => { const env = getEnvironment(); @@ -59,22 +50,7 @@ export const SidebarSwitch = ({ onClick={useCallback(() => { setOpen(open => !open); setTooltipVisible(false); - if (!guideHiddenUntilNextUpdate['quickSearchTips']) { - setGuideHiddenUntilNextUpdate({ - ...guideHiddenUntilNextUpdate, - quickSearchTips: true, - }); - setTimeout(() => { - setGuideHidden({ ...guideHidden, quickSearchTips: false }); - }, 200); - } - }, [ - guideHidden, - guideHiddenUntilNextUpdate, - setGuideHidden, - setGuideHiddenUntilNextUpdate, - setOpen, - ])} + }, [setOpen])} onMouseEnter={useCallback(() => { setTooltipVisible(true); }, [])} diff --git a/apps/web/src/components/blocksuite/workspace-header/index.tsx b/apps/web/src/components/blocksuite/workspace-header/index.tsx index 467accf319..029d10957e 100644 --- a/apps/web/src/components/blocksuite/workspace-header/index.tsx +++ b/apps/web/src/components/blocksuite/workspace-header/index.tsx @@ -4,12 +4,12 @@ import { getEnvironment } from '@affine/env'; import { ArrowDownSmallIcon } from '@blocksuite/icons'; import { assertExists } from '@blocksuite/store'; import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta'; -import { useAtomValue, useSetAtom } from 'jotai'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import type { HTMLAttributes, PropsWithChildren } from 'react'; import { forwardRef, useCallback, useRef } from 'react'; import { currentEditorAtom, openQuickSearchModalAtom } from '../../../atoms'; -import { useGuideHidden } from '../../../hooks/use-is-first-load'; +import { guideQuickSearchTipsAtom } from '../../../atoms/guide'; import { useElementResizeEffect } from '../../../hooks/use-workspaces'; import { QuickSearchButton } from '../../pure/quick-search-button'; import { EditorModeSwitch } from './editor-mode-switch'; @@ -39,7 +39,6 @@ export const WorkspaceHeader = forwardRef< ); assertExists(pageMeta); const title = pageMeta.title; - const [isTipsHidden, setTipsHidden] = useGuideHidden(); const isMac = () => { const env = getEnvironment(); return env.isBrowser && env.isMacOs; @@ -47,14 +46,18 @@ export const WorkspaceHeader = forwardRef< const popperRef: PopperProps['popperRef'] = useRef(null); + const [showQuickSearchTips, setShowQuickSearchTips] = useAtom( + guideQuickSearchTipsAtom + ); + useElementResizeEffect( useAtomValue(currentEditorAtom), useCallback(() => { - if (isTipsHidden.quickSearchTips || !popperRef.current) { + if (showQuickSearchTips || !popperRef.current) { return; } popperRef.current.update(); - }, [isTipsHidden.quickSearchTips]) + }, [showQuickSearchTips]) ); const TipsContent = ( @@ -77,9 +80,7 @@ export const WorkspaceHeader = forwardRef< - setTipsHidden({ ...isTipsHidden, quickSearchTips: true }) - } + onClick={() => setShowQuickSearchTips(false)} > Got it @@ -107,7 +108,7 @@ export const WorkspaceHeader = forwardRef< content={TipsContent} placement="bottom" popperRef={popperRef} - open={!isTipsHidden.quickSearchTips} + open={showQuickSearchTips} offset={[0, -5]} > diff --git a/apps/web/src/components/pure/workspace-slider-bar/changeLog/index.tsx b/apps/web/src/components/pure/workspace-slider-bar/changeLog/index.tsx index 2ecabaeb4e..a1e22d1455 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/changeLog/index.tsx +++ b/apps/web/src/components/pure/workspace-slider-bar/changeLog/index.tsx @@ -1,30 +1,15 @@ import ChangeLogComponent from '@affine/component/changeLog'; +import { useAtom } from 'jotai'; import { useCallback } from 'react'; -import { - useGuideHidden, - useGuideHiddenUntilNextUpdate, -} from '../../../../hooks/use-is-first-load'; +import { guideChangeLogAtom } from '../../../../atoms/guide'; export const ChangeLog = () => { - const [guideHidden, setGuideHidden] = useGuideHidden(); - const [guideHiddenUntilNextUpdate, setGuideHiddenUntilNextUpdate] = - useGuideHiddenUntilNextUpdate(); + const [showChangeLogTips, setShowChangeLogTips] = useAtom(guideChangeLogAtom); const onCloseWhatsNew = useCallback(() => { - setTimeout(() => { - setGuideHiddenUntilNextUpdate({ - ...guideHiddenUntilNextUpdate, - changeLog: true, - }); - setGuideHidden({ ...guideHidden, changeLog: true }); - }, 300); - }, [ - guideHidden, - guideHiddenUntilNextUpdate, - setGuideHidden, - setGuideHiddenUntilNextUpdate, - ]); - if (guideHiddenUntilNextUpdate.changeLog) { + setShowChangeLogTips(false); + }, [setShowChangeLogTips]); + if (!showChangeLogTips) { return <>; } return ; diff --git a/apps/web/src/hooks/__tests__/index.spec.tsx b/apps/web/src/hooks/__tests__/index.spec.tsx index 2be0571802..4405f1e7c7 100644 --- a/apps/web/src/hooks/__tests__/index.spec.tsx +++ b/apps/web/src/hooks/__tests__/index.spec.tsx @@ -34,12 +34,6 @@ import { currentWorkspaceAtom, useCurrentWorkspace, } from '../current/use-current-workspace'; -import { - useGuideHidden, - useGuideHiddenUntilNextUpdate, - useLastVersion, - useTipsDisplayStatus, -} from '../use-is-first-load'; import { useRecentlyViewed, useSyncRecentViewsWithRouter, @@ -278,47 +272,3 @@ describe('useRecentlyViewed', () => { ]); }); }); -describe('useIsFirstLoad', () => { - test('useLastVersion', async () => { - const lastVersion = renderHook(() => useLastVersion()); - const setLastVersion = lastVersion.result.current[1]; - expect(lastVersion.result.current[0]).toEqual('0.0.0'); - setLastVersion('testVersion'); - lastVersion.rerender(); - expect(lastVersion.result.current[0]).toEqual('testVersion'); - }); - test('useGuideHidden', async () => { - const guideHidden = renderHook(() => useGuideHidden()); - const setGuideHidden = guideHidden.result.current[1]; - expect(guideHidden.result.current[0]).toEqual({}); - setGuideHidden({ test: true }); - guideHidden.rerender(); - expect(guideHidden.result.current[0]).toEqual({ test: true }); - }); - test('useGuideHiddenUntilNextUpdate', async () => { - const guideHiddenUntilNextUpdate = renderHook(() => - useGuideHiddenUntilNextUpdate() - ); - const setGuideHiddenUntilNextUpdate = - guideHiddenUntilNextUpdate.result.current[1]; - expect(guideHiddenUntilNextUpdate.result.current[0]).toEqual({}); - setGuideHiddenUntilNextUpdate({ test: true }); - guideHiddenUntilNextUpdate.rerender(); - expect(guideHiddenUntilNextUpdate.result.current[0]).toEqual({ - test: true, - }); - }); - test('useTipsDisplayStatus', async () => { - const tipsDisplayStatus = renderHook(() => useTipsDisplayStatus()); - expect(tipsDisplayStatus.result.current).toEqual({ - quickSearchTips: { - permanentlyHidden: true, - hiddenUntilNextUpdate: true, - }, - changeLog: { - permanentlyHidden: true, - hiddenUntilNextUpdate: true, - }, - }); - }); -}); diff --git a/apps/web/src/hooks/use-is-first-load.ts b/apps/web/src/hooks/use-is-first-load.ts deleted file mode 100644 index 3436f8f7e9..0000000000 --- a/apps/web/src/hooks/use-is-first-load.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { config } from '@affine/env'; -import { useAtom, useAtomValue, useSetAtom } from 'jotai'; -import { useEffect } from 'react'; - -import { - guideHiddenAtom, - guideHiddenUntilNextUpdateAtom, - lastVersionAtom, -} from '../atoms/first-load'; - -export function useLastVersion() { - return useAtom(lastVersionAtom); -} - -export function useGuideHidden() { - return useAtom(guideHiddenAtom); -} - -export function useGuideHiddenUntilNextUpdate() { - return useAtom(guideHiddenUntilNextUpdateAtom); -} - -const TIPS = { - quickSearchTips: true, - changeLog: true, -}; - -export function useTipsDisplayStatus() { - const permanentlyHiddenTips = useAtomValue(guideHiddenAtom); - const hiddenUntilNextUpdateTips = useAtomValue( - guideHiddenUntilNextUpdateAtom - ); - - return { - quickSearchTips: { - permanentlyHidden: permanentlyHiddenTips.quickSearchTips || true, - hiddenUntilNextUpdate: hiddenUntilNextUpdateTips.quickSearchTips || true, - }, - changeLog: { - permanentlyHidden: permanentlyHiddenTips.changeLog || true, - hiddenUntilNextUpdate: hiddenUntilNextUpdateTips.changeLog || true, - }, - }; -} - -export function useUpdateTipsOnVersionChange() { - const [lastVersion, setLastVersion] = useLastVersion(); - const currentVersion = config.gitVersion; - const tipsDisplayStatus = useTipsDisplayStatus(); - const setPermanentlyHiddenTips = useSetAtom(guideHiddenAtom); - const setHiddenUntilNextUpdateTips = useSetAtom( - guideHiddenUntilNextUpdateAtom - ); - - useEffect(() => { - if (lastVersion !== currentVersion) { - setLastVersion(currentVersion); - const newHiddenUntilNextUpdateTips = { ...TIPS }; - const newPermanentlyHiddenTips = { ...TIPS, changeLog: false }; - Object.keys(tipsDisplayStatus).forEach(tipKey => { - newHiddenUntilNextUpdateTips[tipKey as keyof typeof TIPS] = false; - }); - setHiddenUntilNextUpdateTips(newHiddenUntilNextUpdateTips); - setPermanentlyHiddenTips(newPermanentlyHiddenTips); - } - }, [ - currentVersion, - lastVersion, - setLastVersion, - setPermanentlyHiddenTips, - setHiddenUntilNextUpdateTips, - tipsDisplayStatus, - ]); -} diff --git a/packages/component/src/components/app-sidebar/index.jotai.ts b/packages/component/src/components/app-sidebar/index.jotai.ts index 603beaf964..e0521c8f5a 100644 --- a/packages/component/src/components/app-sidebar/index.jotai.ts +++ b/packages/component/src/components/app-sidebar/index.jotai.ts @@ -1,7 +1,8 @@ import { atomWithStorage } from 'jotai/utils'; +export const APP_SIDEBAR_OPEN = 'app-sidebar-open'; export const appSidebarOpenAtom = atomWithStorage( - 'app-sidebar-open', + APP_SIDEBAR_OPEN, undefined as boolean | undefined ); export const appSidebarWidthAtom = atomWithStorage( diff --git a/packages/component/src/components/app-sidebar/index.tsx b/packages/component/src/components/app-sidebar/index.tsx index 7d7398eca3..96fb07bd90 100644 --- a/packages/component/src/components/app-sidebar/index.tsx +++ b/packages/component/src/components/app-sidebar/index.tsx @@ -21,7 +21,11 @@ import { sidebarButtonStyle, sidebarFloatMaskStyle, } from './index.css'; -import { appSidebarOpenAtom, appSidebarWidthAtom } from './index.jotai'; +import { + APP_SIDEBAR_OPEN, + appSidebarOpenAtom, + appSidebarWidthAtom, +} from './index.jotai'; export { appSidebarOpenAtom }; @@ -41,7 +45,10 @@ export const AppSidebar = forwardRef( }, [setOpen]); useEffect(() => { - if (open === undefined) { + if ( + open === undefined && + localStorage.getItem(APP_SIDEBAR_OPEN) === null + ) { // give the initial value, // so that the sidebar can be closed on mobile by default const { matches } = window.matchMedia( diff --git a/playwright.config.ts b/playwright.config.ts index 44460de742..dabd9a8951 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,5 +1,3 @@ -import { resolve } from 'node:path'; - import type { PlaywrightTestConfig, PlaywrightWorkerOptions, @@ -44,18 +42,6 @@ const config: PlaywrightTestConfig = { reporter: process.env.CI ? 'github' : 'list', webServer: [ - { - command: 'cargo run -p affine-cloud', - port: 3000, - timeout: 10 * 1000, - reuseExistingServer: true, - cwd: process.env.OCTOBASE_CWD ?? resolve(process.cwd(), 'apps', 'server'), - env: { - SIGN_KEY: 'test123', - RUST_LOG: 'debug', - JWST_DEV: '1', - }, - }, { // Intentionally not building the storybook, reminds you to run it by yourself. command: 'yarn run start:storybook', diff --git a/tests/parallels/quick-search.spec.ts b/tests/parallels/quick-search.spec.ts index 17f313065f..797d5c2b77 100644 --- a/tests/parallels/quick-search.spec.ts +++ b/tests/parallels/quick-search.spec.ts @@ -179,15 +179,13 @@ test('When opening the website for the first time, the first folding sidebar wil const quickSearchTips = page.locator('[data-testid=quick-search-tips]'); await expect(quickSearchTips).not.toBeVisible(); await page.getByTestId('app-sidebar-arrow-button-collapse').click(); - // fixme: when first close, the tooltip will not show - await page.getByTestId('sliderBar-arrowButton-expand').click(); - await page.getByTestId('app-sidebar-arrow-button-collapse').click(); const sliderBarArea = page.getByTestId('sliderBar-inner'); await expect(sliderBarArea).not.toBeInViewport(); await expect(quickSearchTips).toBeVisible(); await page.locator('[data-testid=quick-search-got-it]').click(); await expect(quickSearchTips).not.toBeVisible(); }); + test('After appearing once, it will not appear a second time', async ({ page, }) => { @@ -196,8 +194,6 @@ test('After appearing once, it will not appear a second time', async ({ const quickSearchTips = page.locator('[data-testid=quick-search-tips]'); await expect(quickSearchTips).not.toBeVisible(); await page.getByTestId('app-sidebar-arrow-button-collapse').click(); - await page.getByTestId('sliderBar-arrowButton-expand').click(); - await page.getByTestId('app-sidebar-arrow-button-collapse').click(); const sliderBarArea = page.getByTestId('sliderBar'); await expect(sliderBarArea).not.toBeVisible(); await expect(quickSearchTips).toBeVisible(); @@ -205,6 +201,7 @@ test('After appearing once, it will not appear a second time', async ({ await expect(quickSearchTips).not.toBeVisible(); await page.reload(); await page.waitForSelector('v-line'); + await page.getByTestId('sliderBar-arrowButton-expand').click(); await page.getByTestId('app-sidebar-arrow-button-collapse').click(); await expect(quickSearchTips).not.toBeVisible(); });