feat(web): unify guided workspace states
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
||||||
import { MemoryRouter, useNavigate } from 'react-router-dom';
|
import { MemoryRouter, useNavigate } from 'react-router-dom';
|
||||||
import type { TrackPlaybackResponse } from '../../api/types';
|
import type { TrackPlaybackResponse } from '../../api/types';
|
||||||
@@ -316,6 +316,35 @@ test('uses the shared Semi editor SideSheet for mobile track criteria and eviden
|
|||||||
expect(screen.getByRole('button', { name: '关闭轨迹查询与明细' })).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: '关闭轨迹查询与明细' })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('keeps the mobile track canvas focused when the viewport crosses the responsive breakpoint', async () => {
|
||||||
|
let mobile = false;
|
||||||
|
let mobileListener: (() => void) | undefined;
|
||||||
|
Object.defineProperty(window, 'matchMedia', {
|
||||||
|
configurable: true,
|
||||||
|
value: vi.fn((query: string) => ({
|
||||||
|
get matches() { return mobile; },
|
||||||
|
addEventListener: (_type: string, listener: () => void) => {
|
||||||
|
if (query.includes('680px')) mobileListener = listener;
|
||||||
|
},
|
||||||
|
removeEventListener: vi.fn()
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||||
|
const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/tracks']}><TrackPage /></MemoryRouter></QueryClientProvider>);
|
||||||
|
|
||||||
|
expect(view.container.querySelector('.v2-track-page')).not.toHaveClass('is-mobile-layout', 'is-rail-collapsed');
|
||||||
|
expect(screen.getByRole('textbox', { name: '搜索轨迹车辆' })).toBeInTheDocument();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
mobile = true;
|
||||||
|
mobileListener?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(view.container.querySelector('.v2-track-page')).toHaveClass('is-mobile-layout', 'is-rail-collapsed');
|
||||||
|
expect(document.querySelector('.v2-track-detail-sidesheet .semi-sidesheet-animation-content_hide_bottom')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /选择车辆/ })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
test('keeps one clear mobile query entry before a vehicle is selected', async () => {
|
test('keeps one clear mobile query entry before a vehicle is selected', async () => {
|
||||||
Object.defineProperty(window, 'matchMedia', { configurable: true, value: vi.fn(() => ({ matches: true, addEventListener: vi.fn(), removeEventListener: vi.fn() })) });
|
Object.defineProperty(window, 'matchMedia', { configurable: true, value: vi.fn(() => ({ matches: true, addEventListener: vi.fn(), removeEventListener: vi.fn() })) });
|
||||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||||
|
|||||||
@@ -357,6 +357,7 @@ export default function TrackPage() {
|
|||||||
const [queryCollapsed, setQueryCollapsed] = useState(() => Boolean(criteria.keyword));
|
const [queryCollapsed, setQueryCollapsed] = useState(() => Boolean(criteria.keyword));
|
||||||
const animationRef = useRef<number>();
|
const animationRef = useRef<number>();
|
||||||
const lastFrameRef = useRef(0);
|
const lastFrameRef = useRef(0);
|
||||||
|
const previousMobileLayoutRef = useRef(mobileLayout);
|
||||||
|
|
||||||
const params = useMemo(() => {
|
const params = useMemo(() => {
|
||||||
const next = new URLSearchParams({ keyword: criteria.keyword, maxPoints: '1600' });
|
const next = new URLSearchParams({ keyword: criteria.keyword, maxPoints: '1600' });
|
||||||
@@ -393,6 +394,12 @@ export default function TrackPage() {
|
|||||||
});
|
});
|
||||||
const addressSettling = Boolean(!playing && currentAddressPoint && currentAddressPoint.key !== addressPoint?.key);
|
const addressSettling = Boolean(!playing && currentAddressPoint && currentAddressPoint.key !== addressPoint?.key);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const previousMobileLayout = previousMobileLayoutRef.current;
|
||||||
|
previousMobileLayoutRef.current = mobileLayout;
|
||||||
|
if (previousMobileLayout === mobileLayout || track?.points.length) return;
|
||||||
|
setRailCollapsed(mobileLayout);
|
||||||
|
}, [mobileLayout, track?.points.length]);
|
||||||
useEffect(() => { setActiveIndex(0); setPlaying(false); setFollow(true); setAddressPoint(undefined); if (track?.points.length) setRailCollapsed(window.matchMedia('(max-width: 960px)').matches); }, [track?.asOf]);
|
useEffect(() => { setActiveIndex(0); setPlaying(false); setFollow(true); setAddressPoint(undefined); if (track?.points.length) setRailCollapsed(window.matchMedia('(max-width: 960px)').matches); }, [track?.asOf]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!playing || points.length < 2) return;
|
if (!playing || points.length < 2) return;
|
||||||
|
|||||||
@@ -7096,6 +7096,13 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-mobile-navigation .v2-mobile-nav-item:focus-visible,
|
||||||
|
.v2-mobile-navigation > .semi-button.v2-mobile-nav-item:focus-visible {
|
||||||
|
outline: 2px solid rgba(18, 104, 243, .42);
|
||||||
|
outline-offset: -3px;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .7);
|
||||||
|
}
|
||||||
|
|
||||||
.v2-mobile-navigation > .semi-button.v2-mobile-nav-item .semi-button-content {
|
.v2-mobile-navigation > .semi-button.v2-mobile-nav-item .semi-button-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -25294,17 +25301,26 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: 8px;
|
gap: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #e0e7f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: linear-gradient(180deg, #fff 0%, #fbfcfe 100%);
|
||||||
margin-top: 21px;
|
margin-top: 21px;
|
||||||
|
box-shadow: 0 8px 24px rgba(31, 48, 70, .045);
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-empty-guide-step.semi-card {
|
.v2-workspace-empty-guide-step.semi-card {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #e0e7f0;
|
border: 0;
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
background: #fff;
|
background: transparent;
|
||||||
box-shadow: 0 3px 12px rgba(31, 48, 70, .04);
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-workspace-empty-guide-step.semi-card + .v2-workspace-empty-guide-step.semi-card {
|
||||||
|
border-left: 1px solid #e7edf4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-empty-guide-step > .semi-card-body {
|
.v2-workspace-empty-guide-step > .semi-card-body {
|
||||||
@@ -25420,7 +25436,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-empty-guide-steps.semi-card-group {
|
.v2-workspace-empty-guide-steps.semi-card-group {
|
||||||
gap: 6px;
|
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user