feat(electron): track router history (#2336)

Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
Himself65
2023-05-14 23:13:30 -07:00
committed by LongYinan
parent 8527c5bfac
commit a34e2eb57d
7 changed files with 219 additions and 7 deletions

View File

@@ -1,3 +1,5 @@
import { platform } from 'node:os';
import { expect } from '@playwright/test';
import { test } from './fixture';
@@ -11,6 +13,73 @@ test('new page', async ({ page, workspace }) => {
expect(flavour).toBe('local');
});
// macOS only
if (platform() === 'darwin') {
test('app sidebar router forward/back', async ({ page }) => {
await page.getByTestId('help-island').click();
await page.getByTestId('easy-guide').click();
await page.getByTestId('onboarding-modal-next-button').click();
await page.getByTestId('onboarding-modal-close-button').click();
{
// create pages
await page.waitForTimeout(500);
await page.getByTestId('new-page-button').click({
delay: 100,
});
await page.waitForSelector('v-line');
await page.focus('.affine-default-page-block-title');
await page.type('.affine-default-page-block-title', 'test1', {
delay: 100,
});
await page.waitForTimeout(500);
await page.getByTestId('new-page-button').click({
delay: 100,
});
await page.waitForSelector('v-line');
await page.focus('.affine-default-page-block-title');
await page.type('.affine-default-page-block-title', 'test2', {
delay: 100,
});
await page.waitForTimeout(500);
await page.getByTestId('new-page-button').click({
delay: 100,
});
await page.waitForSelector('v-line');
await page.focus('.affine-default-page-block-title');
await page.type('.affine-default-page-block-title', 'test3', {
delay: 100,
});
}
{
const title = (await page
.locator('.affine-default-page-block-title')
.textContent()) as string;
expect(title.trim()).toBe('test3');
}
await page.click('[data-testid="app-sidebar-arrow-button-back"]');
await page.waitForTimeout(1000);
await page.click('[data-testid="app-sidebar-arrow-button-back"]');
await page.waitForTimeout(1000);
{
const title = (await page
.locator('.affine-default-page-block-title')
.textContent()) as string;
expect(title.trim()).toBe('test1');
}
await page.click('[data-testid="app-sidebar-arrow-button-forward"]');
await page.waitForTimeout(1000);
await page.click('[data-testid="app-sidebar-arrow-button-forward"]');
await page.waitForTimeout(1000);
{
const title = (await page
.locator('.affine-default-page-block-title')
.textContent()) as string;
expect(title.trim()).toBe('test3');
}
});
}
test('app theme', async ({ page, electronApp }) => {
const root = page.locator('html');
{

View File

@@ -9,6 +9,7 @@ import {
enableCoverage,
istanbulTempDir,
test as base,
testResultDir,
} from '@affine-test/kit/playwright';
import fs from 'fs-extra';
import type { ElectronApplication, Page } from 'playwright';
@@ -90,6 +91,9 @@ export const test = base.extend<{
'.bin',
`electron${ext}`
),
recordVideo: {
dir: testResultDir,
},
colorScheme: 'light',
});
await use(electronApp);

View File

@@ -0,0 +1,99 @@
import { atom, useAtom, useSetAtom } from 'jotai';
import { useRouter } from 'next/router';
import { useCallback, useEffect } from 'react';
export type History = {
stack: string[];
current: number;
skip: boolean;
};
export const MAX_HISTORY = 50;
export const historyBaseAtom = atom<History>({
stack: [],
current: 0,
skip: false,
});
// fixme(himself65): don't use hooks, use atom lifecycle instead
export function useTrackRouterHistoryEffect() {
const setBase = useSetAtom(historyBaseAtom);
const router = useRouter();
useEffect(() => {
const callback = (url: string) => {
setBase(prev => {
console.log('push', url, prev.skip, prev.stack.length, prev.current);
if (prev.skip) {
return {
stack: [...prev.stack],
current: prev.current,
skip: false,
};
} else {
if (prev.current < prev.stack.length - 1) {
const newStack = prev.stack.slice(0, prev.current);
newStack.push(url);
if (newStack.length > MAX_HISTORY) {
newStack.shift();
}
return {
stack: newStack,
current: newStack.length - 1,
skip: false,
};
} else {
const newStack = [...prev.stack, url];
if (newStack.length > MAX_HISTORY) {
newStack.shift();
}
return {
stack: newStack,
current: newStack.length - 1,
skip: false,
};
}
}
});
};
router.events.on('routeChangeComplete', callback);
return () => {
router.events.off('routeChangeComplete', callback);
};
}, [router.events, setBase]);
}
export function useHistoryAtom() {
const router = useRouter();
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];
void router.push(url);
return {
...prev,
current: target,
skip: true,
};
} else {
const target = Math.max(0, prev.current - 1);
const url = prev.stack[target];
void router.push(url);
return {
...prev,
current: target,
skip: true,
};
}
});
},
[router, setBase]
),
] as const;
}

View File

@@ -23,8 +23,9 @@ import type { Page } from '@blocksuite/store';
import { useAtom, useAtomValue } from 'jotai';
import type { ReactElement } from 'react';
import type React from 'react';
import { useCallback, useEffect } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useHistoryAtom } from '../../atoms/history';
import type { AllWorkspace } from '../../shared';
import FavoriteList from '../pure/workspace-slider-bar/favorite/favorite-list';
import { WorkspaceSelector } from '../pure/workspace-slider-bar/WorkspaceSelector';
@@ -115,10 +116,22 @@ export const RootAppSidebar = ({
}, [sidebarOpen, setSidebarOpen]);
const clientUpdateAvailable = useAtomValue(updateAvailableAtom);
const [history, setHistory] = useHistoryAtom();
const router = useMemo(() => {
return {
forward: () => {
setHistory(true);
},
back: () => {
setHistory(false);
},
history,
};
}, [history, setHistory]);
return (
<>
<AppSidebar>
<AppSidebar router={router}>
<SidebarContainer>
<WorkspaceSelector
currentWorkspace={currentWorkspace}

View File

@@ -27,6 +27,7 @@ import type { FC, PropsWithChildren, ReactElement } from 'react';
import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react';
import { openQuickSearchModalAtom, openWorkspacesModalAtom } from '../atoms';
import { useTrackRouterHistoryEffect } from '../atoms/history';
import {
publicWorkspaceAtom,
publicWorkspaceIdAtom,
@@ -166,6 +167,7 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
// todo(himself65): this is a hack, we should use a better way to set the language
setUpLanguage(i18n);
}, [i18n]);
useTrackRouterHistoryEffect();
const currentWorkspaceId = useAtomValue(rootCurrentWorkspaceIdAtom);
const jotaiWorkspaces = useAtomValue(rootWorkspacesMetadataAtom);
const meta = useMemo(