github-issue-tower-defence-management 1.92.0 → 1.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/bin/adapter/entry-points/console/consoleServer.js +1 -0
- package/bin/adapter/entry-points/console/consoleServer.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleServer.test.ts +23 -0
- package/src/adapter/entry-points/console/consoleServer.ts +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.test.tsx +49 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.tsx +36 -20
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.stories.tsx +19 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.test.tsx +24 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.tsx +30 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +30 -24
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.stories.tsx +47 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.test.tsx +54 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.tsx +36 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.test.ts +117 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.ts +105 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.test.ts +115 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.ts +115 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.test.ts +153 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.ts +103 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.test.ts +81 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.ts +61 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.test.ts +53 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.ts +33 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.test.ts +34 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.ts +28 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +9 -1
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +51 -6
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +263 -17
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +85 -3
- package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +19 -3
- package/src/adapter/entry-points/console/ui/src/index.css +205 -1
- package/src/adapter/entry-points/console/ui-dist/assets/index-C5nxEEOu.css +1 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-DoJ05EuW.js +101 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/types/adapter/entry-points/console/consoleServer.d.ts.map +1 -1
- package/src/adapter/entry-points/console/ui-dist/assets/index-BJixkRxv.css +0 -1
- package/src/adapter/entry-points/console/ui-dist/assets/index-BSNMvjcB.js +0 -100
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fireEvent, render, waitFor } from '@testing-library/react';
|
|
1
|
+
import { act, fireEvent, render, waitFor } from '@testing-library/react';
|
|
2
2
|
import { ConsolePage } from './ConsolePage';
|
|
3
3
|
|
|
4
4
|
jest.mock('../lib/mermaidLoader', () => ({
|
|
@@ -97,27 +97,103 @@ describe('ConsolePage', () => {
|
|
|
97
97
|
expect(getByText('snapshot: 2026-06-19T00:00:00.000Z')).toBeInTheDocument();
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
it('
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
it('shows a cancellable toast and only drives the tab to zero after the five second window', async () => {
|
|
101
|
+
jest.useFakeTimers();
|
|
102
|
+
try {
|
|
103
|
+
const { getByText, findByText } = render(<ConsolePage />);
|
|
104
|
+
await waitFor(() => {
|
|
105
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
106
|
+
});
|
|
107
|
+
expect(
|
|
108
|
+
getByText('Awaiting Quality Check')
|
|
109
|
+
.closest('a')
|
|
110
|
+
?.querySelector('.console-tab-badge')?.textContent,
|
|
111
|
+
).toBe('1');
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
fireEvent.click(getByText('Add serveConsole subcommand'));
|
|
114
|
+
expect(await findByText('Approve')).toBeInTheDocument();
|
|
115
|
+
fireEvent.click(getByText('Approve'));
|
|
114
116
|
|
|
115
|
-
|
|
117
|
+
expect(getByText('Approved — PR #851')).toBeInTheDocument();
|
|
118
|
+
expect(getByText('Undo')).toBeInTheDocument();
|
|
119
|
+
expect(
|
|
120
|
+
getByText('Awaiting Quality Check')
|
|
121
|
+
.closest('a')
|
|
122
|
+
?.querySelector('.console-tab-badge')?.textContent,
|
|
123
|
+
).toBe('1');
|
|
124
|
+
|
|
125
|
+
act(() => {
|
|
126
|
+
jest.advanceTimersByTime(5100);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await waitFor(() => {
|
|
130
|
+
expect(
|
|
131
|
+
getByText('Awaiting Quality Check')
|
|
132
|
+
.closest('a')
|
|
133
|
+
?.querySelector('.console-tab-badge')?.textContent,
|
|
134
|
+
).toBe('0');
|
|
135
|
+
});
|
|
136
|
+
} finally {
|
|
137
|
+
jest.useRealTimers();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('cancels the command and keeps the item pending when Undo is clicked', async () => {
|
|
142
|
+
jest.useFakeTimers();
|
|
143
|
+
try {
|
|
144
|
+
const fetchMock = jest.fn(
|
|
145
|
+
async (_url: string, init?: { method?: string }) => {
|
|
146
|
+
const listMatch = _url.match(
|
|
147
|
+
/\/projects\/[^/]+\/([^/]+)\/list\.json/,
|
|
148
|
+
);
|
|
149
|
+
if (listMatch !== null) {
|
|
150
|
+
return {
|
|
151
|
+
ok: true,
|
|
152
|
+
status: 200,
|
|
153
|
+
json: async () => listPayload(listMatch[1]),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
void init;
|
|
157
|
+
return {
|
|
158
|
+
ok: true,
|
|
159
|
+
status: 200,
|
|
160
|
+
json: async () => ({ body: '# body' }),
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
global.fetch = fetchMock as unknown as typeof fetch;
|
|
165
|
+
|
|
166
|
+
const { getByText, findByText } = render(<ConsolePage />);
|
|
167
|
+
await waitFor(() => {
|
|
168
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
169
|
+
});
|
|
170
|
+
fireEvent.click(getByText('Add serveConsole subcommand'));
|
|
171
|
+
expect(await findByText('Approve')).toBeInTheDocument();
|
|
172
|
+
fireEvent.click(getByText('Approve'));
|
|
173
|
+
|
|
174
|
+
fireEvent.click(getByText('Undo'));
|
|
175
|
+
act(() => {
|
|
176
|
+
jest.advanceTimersByTime(6000);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const postCalls = fetchMock.mock.calls.filter(
|
|
180
|
+
(call) => call[1]?.method === 'POST',
|
|
181
|
+
);
|
|
182
|
+
expect(postCalls.length).toBe(0);
|
|
116
183
|
expect(
|
|
117
184
|
getByText('Awaiting Quality Check')
|
|
118
185
|
.closest('a')
|
|
119
186
|
?.querySelector('.console-tab-badge')?.textContent,
|
|
120
|
-
).toBe('
|
|
187
|
+
).toBe('1');
|
|
188
|
+
} finally {
|
|
189
|
+
jest.useRealTimers();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('does not revive a zeroed tab badge after switching tabs', async () => {
|
|
194
|
+
const { getByText, queryByText } = render(<ConsolePage />);
|
|
195
|
+
await waitFor(() => {
|
|
196
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
121
197
|
});
|
|
122
198
|
|
|
123
199
|
fireEvent.click(getByText('Unread'));
|
|
@@ -127,7 +203,7 @@ describe('ConsolePage', () => {
|
|
|
127
203
|
).toBeInTheDocument();
|
|
128
204
|
});
|
|
129
205
|
|
|
130
|
-
expect(queryByText('
|
|
206
|
+
expect(queryByText('Triage')).toBeNull();
|
|
131
207
|
});
|
|
132
208
|
|
|
133
209
|
it('hides zero-count tabs but keeps non-zero tabs', async () => {
|
|
@@ -151,3 +227,173 @@ describe('ConsolePage', () => {
|
|
|
151
227
|
expect(queryByText('project: umino')).toBeNull();
|
|
152
228
|
});
|
|
153
229
|
});
|
|
230
|
+
|
|
231
|
+
const twoItemPrPayload = () => ({
|
|
232
|
+
pjcode: 'umino',
|
|
233
|
+
generatedAt: '2026-06-19T00:00:00.000Z',
|
|
234
|
+
statusOptions: [{ id: 's1', name: 'Awaiting Workspace', color: 'BLUE' }],
|
|
235
|
+
storyOptions: [{ id: 'st1', name: 'TDPM Console port', color: 'BLUE' }],
|
|
236
|
+
storyColors: { 'TDPM Console port': { color: 'BLUE' } },
|
|
237
|
+
items: [
|
|
238
|
+
{
|
|
239
|
+
number: 851,
|
|
240
|
+
title: 'Add serveConsole subcommand',
|
|
241
|
+
url: 'https://github.com/o/r/pull/851',
|
|
242
|
+
repo: 'o/r',
|
|
243
|
+
nameWithOwner: 'o/r',
|
|
244
|
+
projectItemId: 'PVTI_1',
|
|
245
|
+
itemId: 'PVTI_1',
|
|
246
|
+
isPr: true,
|
|
247
|
+
story: 'TDPM Console port',
|
|
248
|
+
labels: [],
|
|
249
|
+
createdAt: '2026-06-17T00:00:00.000Z',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
number: 852,
|
|
253
|
+
title: 'Add server-side console API handlers',
|
|
254
|
+
url: 'https://github.com/o/r/pull/852',
|
|
255
|
+
repo: 'o/r',
|
|
256
|
+
nameWithOwner: 'o/r',
|
|
257
|
+
projectItemId: 'PVTI_2',
|
|
258
|
+
itemId: 'PVTI_2',
|
|
259
|
+
isPr: true,
|
|
260
|
+
story: 'TDPM Console port',
|
|
261
|
+
labels: [],
|
|
262
|
+
createdAt: '2026-06-17T01:00:00.000Z',
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const touchEvent = (
|
|
268
|
+
type: string,
|
|
269
|
+
point: { clientX: number; clientY: number },
|
|
270
|
+
property: 'touches' | 'changedTouches',
|
|
271
|
+
): TouchEvent => {
|
|
272
|
+
const event = new Event(type, { bubbles: true }) as TouchEvent;
|
|
273
|
+
Object.defineProperty(event, property, {
|
|
274
|
+
value: [point],
|
|
275
|
+
configurable: true,
|
|
276
|
+
});
|
|
277
|
+
return event;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const swipeDetailScreen = (
|
|
281
|
+
element: HTMLElement,
|
|
282
|
+
from: { clientX: number; clientY: number },
|
|
283
|
+
to: { clientX: number; clientY: number },
|
|
284
|
+
): void => {
|
|
285
|
+
element.dispatchEvent(touchEvent('touchstart', from, 'touches'));
|
|
286
|
+
element.dispatchEvent(touchEvent('touchmove', to, 'touches'));
|
|
287
|
+
element.dispatchEvent(touchEvent('touchend', to, 'changedTouches'));
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
describe('ConsolePage swipe navigation', () => {
|
|
291
|
+
beforeEach(() => {
|
|
292
|
+
localStorage.clear();
|
|
293
|
+
window.history.replaceState({}, '', '/projects/umino/prs?k=token');
|
|
294
|
+
const fetchMock = jest.fn(async (url: string) => {
|
|
295
|
+
const listMatch = url.match(/\/projects\/[^/]+\/([^/]+)\/list\.json/);
|
|
296
|
+
if (listMatch !== null) {
|
|
297
|
+
return {
|
|
298
|
+
ok: true,
|
|
299
|
+
status: 200,
|
|
300
|
+
json: async () =>
|
|
301
|
+
listMatch[1] === 'prs'
|
|
302
|
+
? twoItemPrPayload()
|
|
303
|
+
: { ...twoItemPrPayload(), items: [] },
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return { ok: true, status: 200, json: async () => ({ body: '# body' }) };
|
|
307
|
+
});
|
|
308
|
+
global.fetch = fetchMock as unknown as typeof fetch;
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('navigates to the next item on a left swipe of the opened detail screen', async () => {
|
|
312
|
+
const { container, getByText, findByText } = render(<ConsolePage />);
|
|
313
|
+
await waitFor(() => {
|
|
314
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
315
|
+
});
|
|
316
|
+
fireEvent.click(getByText('Add serveConsole subcommand'));
|
|
317
|
+
expect(await findByText('Approve')).toBeInTheDocument();
|
|
318
|
+
expect(window.location.hash).toBe('#item/PVTI_1');
|
|
319
|
+
|
|
320
|
+
const detailScreen = container.querySelector('.console-detail-screen');
|
|
321
|
+
expect(detailScreen).not.toBeNull();
|
|
322
|
+
swipeDetailScreen(
|
|
323
|
+
detailScreen as HTMLElement,
|
|
324
|
+
{ clientX: 240, clientY: 100 },
|
|
325
|
+
{ clientX: 40, clientY: 110 },
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
await waitFor(() => {
|
|
329
|
+
expect(window.location.hash).toBe('#item/PVTI_2');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('navigates to the previous item on a right swipe of the opened detail screen', async () => {
|
|
334
|
+
const { container, getByText, findByText } = render(<ConsolePage />);
|
|
335
|
+
await waitFor(() => {
|
|
336
|
+
expect(
|
|
337
|
+
getByText('Add server-side console API handlers'),
|
|
338
|
+
).toBeInTheDocument();
|
|
339
|
+
});
|
|
340
|
+
fireEvent.click(getByText('Add server-side console API handlers'));
|
|
341
|
+
expect(await findByText('Approve')).toBeInTheDocument();
|
|
342
|
+
expect(window.location.hash).toBe('#item/PVTI_2');
|
|
343
|
+
|
|
344
|
+
const detailScreen = container.querySelector('.console-detail-screen');
|
|
345
|
+
expect(detailScreen).not.toBeNull();
|
|
346
|
+
swipeDetailScreen(
|
|
347
|
+
detailScreen as HTMLElement,
|
|
348
|
+
{ clientX: 40, clientY: 100 },
|
|
349
|
+
{ clientX: 240, clientY: 110 },
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
await waitFor(() => {
|
|
353
|
+
expect(window.location.hash).toBe('#item/PVTI_1');
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe('ConsolePage auto-advance', () => {
|
|
359
|
+
beforeEach(() => {
|
|
360
|
+
localStorage.clear();
|
|
361
|
+
window.history.replaceState({}, '', '/projects/umino/prs?k=token');
|
|
362
|
+
const fetchMock = jest.fn(async (url: string) => {
|
|
363
|
+
const listMatch = url.match(/\/projects\/[^/]+\/([^/]+)\/list\.json/);
|
|
364
|
+
if (listMatch !== null) {
|
|
365
|
+
return {
|
|
366
|
+
ok: true,
|
|
367
|
+
status: 200,
|
|
368
|
+
json: async () =>
|
|
369
|
+
listMatch[1] === 'prs'
|
|
370
|
+
? twoItemPrPayload()
|
|
371
|
+
: { ...twoItemPrPayload(), items: [] },
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
return { ok: true, status: 200, json: async () => ({ body: '# body' }) };
|
|
375
|
+
});
|
|
376
|
+
global.fetch = fetchMock as unknown as typeof fetch;
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('advances the detail view to the next pending item after an action', async () => {
|
|
380
|
+
jest.useFakeTimers();
|
|
381
|
+
try {
|
|
382
|
+
const { getByText, findByText } = render(<ConsolePage />);
|
|
383
|
+
await waitFor(() => {
|
|
384
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
385
|
+
});
|
|
386
|
+
fireEvent.click(getByText('Add serveConsole subcommand'));
|
|
387
|
+
expect(await findByText('Approve')).toBeInTheDocument();
|
|
388
|
+
expect(window.location.hash).toBe('#item/PVTI_1');
|
|
389
|
+
|
|
390
|
+
fireEvent.click(getByText('Approve'));
|
|
391
|
+
|
|
392
|
+
await waitFor(() => {
|
|
393
|
+
expect(window.location.hash).toBe('#item/PVTI_2');
|
|
394
|
+
});
|
|
395
|
+
} finally {
|
|
396
|
+
jest.useRealTimers();
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
});
|
|
@@ -1,25 +1,42 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
2
|
import { ConsoleTabList } from '../components/layout/ConsoleTabList';
|
|
3
3
|
import { ConsoleItemList } from '../components/list/ConsoleItemList';
|
|
4
|
+
import { ConsoleUndoToast } from '../components/operations/ConsoleUndoToast';
|
|
5
|
+
import { useConsoleActionQueue } from '../hooks/useConsoleActionQueue';
|
|
4
6
|
import { useConsoleCaches } from '../hooks/useConsoleCaches';
|
|
5
7
|
import { useConsoleNavigation } from '../hooks/useConsoleNavigation';
|
|
6
8
|
import { useConsoleOperations } from '../hooks/useConsoleOperations';
|
|
7
9
|
import { useConsoleOverlay } from '../hooks/useConsoleOverlay';
|
|
8
10
|
import { useConsolePjcode } from '../hooks/useConsolePjcode';
|
|
11
|
+
import { useConsoleSwipeNavigation } from '../hooks/useConsoleSwipeNavigation';
|
|
9
12
|
import { useConsoleTabData } from '../hooks/useConsoleTabData';
|
|
13
|
+
import {
|
|
14
|
+
actionAdvances,
|
|
15
|
+
actionToastColor,
|
|
16
|
+
formatActionToast,
|
|
17
|
+
} from '../logic/actionToast';
|
|
10
18
|
import { buildConsoleListRows, resolveItemStory } from '../logic/grouping';
|
|
19
|
+
import {
|
|
20
|
+
nextPendingKeyAfter,
|
|
21
|
+
nextPendingKeyBrowse,
|
|
22
|
+
previousPendingKeyBefore,
|
|
23
|
+
} from '../logic/navigation';
|
|
11
24
|
import {
|
|
12
25
|
countPendingItems,
|
|
13
26
|
filterPendingItems,
|
|
14
27
|
overlayKeyForItem,
|
|
15
28
|
} from '../logic/overlay';
|
|
29
|
+
import type { ConsoleSwipeDirection } from '../logic/swipe';
|
|
16
30
|
import type {
|
|
17
31
|
ConsoleListItem,
|
|
18
32
|
ConsoleOverlayStatus,
|
|
19
33
|
ConsoleTabName,
|
|
20
34
|
} from '../logic/types';
|
|
21
35
|
import { CONSOLE_TABS } from '../logic/types';
|
|
22
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
ConsoleItemDetailContainer,
|
|
38
|
+
type ConsoleQueueActionInput,
|
|
39
|
+
} from './ConsoleItemDetailContainer';
|
|
23
40
|
|
|
24
41
|
const emptyCounts = (): Record<ConsoleTabName, number> => {
|
|
25
42
|
const result = {} as Record<ConsoleTabName, number>;
|
|
@@ -40,6 +57,7 @@ export const ConsolePage = () => {
|
|
|
40
57
|
const overlayState = useConsoleOverlay(pjcode ?? OVERLAY_NAMESPACE_FALLBACK);
|
|
41
58
|
const caches = useConsoleCaches();
|
|
42
59
|
const operations = useConsoleOperations(pjcode, activeTab, overlayState);
|
|
60
|
+
const actionQueue = useConsoleActionQueue();
|
|
43
61
|
const now = Date.now();
|
|
44
62
|
|
|
45
63
|
const counts = useMemo(() => {
|
|
@@ -65,6 +83,11 @@ export const ConsolePage = () => {
|
|
|
65
83
|
return filterPendingItems(activeSnapshot.items, overlayState.overlay);
|
|
66
84
|
}, [activeSnapshot, overlayState.overlay]);
|
|
67
85
|
|
|
86
|
+
const orderedPendingKeys = useMemo(
|
|
87
|
+
() => pendingItems.map((item) => overlayKeyForItem(item)),
|
|
88
|
+
[pendingItems],
|
|
89
|
+
);
|
|
90
|
+
|
|
68
91
|
const rows = useMemo(
|
|
69
92
|
() => buildConsoleListRows(pendingItems, overlayState.overlay),
|
|
70
93
|
[pendingItems, overlayState.overlay],
|
|
@@ -99,8 +122,66 @@ export const ConsolePage = () => {
|
|
|
99
122
|
? resolveItemStory(selectedItem, overlayState.overlay)
|
|
100
123
|
: null;
|
|
101
124
|
|
|
125
|
+
const { openItem, closeItem } = navigation;
|
|
126
|
+
|
|
127
|
+
const advanceToNext = useCallback(
|
|
128
|
+
(actedKey: string): void => {
|
|
129
|
+
const nextKey = nextPendingKeyAfter(orderedPendingKeys, actedKey);
|
|
130
|
+
if (nextKey !== null) {
|
|
131
|
+
openItem(nextKey);
|
|
132
|
+
} else {
|
|
133
|
+
closeItem();
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
[orderedPendingKeys, openItem, closeItem],
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const handleQueueAction = useCallback(
|
|
140
|
+
(input: ConsoleQueueActionInput): void => {
|
|
141
|
+
const actedKey = overlayKeyForItem(input.item);
|
|
142
|
+
actionQueue.enqueue({
|
|
143
|
+
message: formatActionToast(input.kind, input.item, activeTab),
|
|
144
|
+
color: actionToastColor(input.kind),
|
|
145
|
+
commit: input.commit,
|
|
146
|
+
advance: () => {
|
|
147
|
+
if (actionAdvances(input.kind, activeTab)) {
|
|
148
|
+
advanceToNext(actedKey);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
[actionQueue, activeTab, advanceToNext],
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const handleSwipe = useCallback(
|
|
157
|
+
(direction: ConsoleSwipeDirection): void => {
|
|
158
|
+
if (selectedItemKey === null || direction === null) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const targetKey =
|
|
162
|
+
direction === 'next'
|
|
163
|
+
? nextPendingKeyBrowse(orderedPendingKeys, selectedItemKey)
|
|
164
|
+
: previousPendingKeyBefore(orderedPendingKeys, selectedItemKey);
|
|
165
|
+
if (targetKey !== null) {
|
|
166
|
+
openItem(targetKey);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
[selectedItemKey, orderedPendingKeys, openItem],
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const detailScreenRef = useConsoleSwipeNavigation(handleSwipe);
|
|
173
|
+
|
|
102
174
|
return (
|
|
103
175
|
<main className="console-app">
|
|
176
|
+
{actionQueue.pending !== null && (
|
|
177
|
+
<ConsoleUndoToast
|
|
178
|
+
message={actionQueue.pending.message}
|
|
179
|
+
color={actionQueue.pending.color}
|
|
180
|
+
remainingSeconds={actionQueue.pending.remainingSeconds}
|
|
181
|
+
progress={actionQueue.pending.progress}
|
|
182
|
+
onUndo={actionQueue.undo}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
104
185
|
<ConsoleTabList
|
|
105
186
|
activeTab={activeTab}
|
|
106
187
|
counts={counts}
|
|
@@ -120,7 +201,7 @@ export const ConsolePage = () => {
|
|
|
120
201
|
onSelectItem={(item) => navigation.openItem(item.projectItemId)}
|
|
121
202
|
/>
|
|
122
203
|
) : (
|
|
123
|
-
<div className="console-detail-screen">
|
|
204
|
+
<div className="console-detail-screen" ref={detailScreenRef}>
|
|
124
205
|
<ConsoleItemDetailContainer
|
|
125
206
|
tab={activeTab}
|
|
126
207
|
item={selectedItem}
|
|
@@ -132,6 +213,7 @@ export const ConsolePage = () => {
|
|
|
132
213
|
storyName={storyNameForSelected}
|
|
133
214
|
overlayStatus={overlayStatusForSelected}
|
|
134
215
|
now={now}
|
|
216
|
+
onQueueAction={handleQueueAction}
|
|
135
217
|
/>
|
|
136
218
|
</div>
|
|
137
219
|
)}
|
|
@@ -182,21 +182,37 @@ export const consoleChangedFilesFixture: ConsoleChangedFile[] = [
|
|
|
182
182
|
additions: 312,
|
|
183
183
|
deletions: 4,
|
|
184
184
|
status: 'added',
|
|
185
|
-
patch:
|
|
185
|
+
patch: `@@ -54,7 +54,12 @@ export const serveConsole = (
|
|
186
|
+
loose-matching: true
|
|
187
|
+
- name: Install dependencies
|
|
188
|
+
run: |
|
|
189
|
+
- npm install
|
|
190
|
+
+ npm ci
|
|
191
|
+
+ npm run build:console-ui
|
|
192
|
+
- name: Start the token-protected console server
|
|
193
|
+
run: node bin/serveConsole.js --port 9981`,
|
|
186
194
|
},
|
|
187
195
|
{
|
|
188
196
|
path: 'src/adapter/entry-points/console/consoleServer.test.ts',
|
|
189
197
|
additions: 268,
|
|
190
198
|
deletions: 0,
|
|
191
199
|
status: 'added',
|
|
192
|
-
patch:
|
|
200
|
+
patch: `@@ -0,0 +1,4 @@
|
|
201
|
+
+describe('serveConsole', () => {
|
|
202
|
+
+ it('rejects a request without a valid token', async () => {
|
|
203
|
+
+ });
|
|
204
|
+
+});`,
|
|
193
205
|
},
|
|
194
206
|
{
|
|
195
207
|
path: 'package.json',
|
|
196
208
|
additions: 6,
|
|
197
209
|
deletions: 1,
|
|
198
210
|
status: 'modified',
|
|
199
|
-
patch:
|
|
211
|
+
patch: `@@ -12,7 +12,7 @@
|
|
212
|
+
"scripts": {
|
|
213
|
+
- "build": "tsc -p ./tsconfig.build.json"
|
|
214
|
+
+ "build": "tsc -p ./tsconfig.build.json && npm run build:console-ui"
|
|
215
|
+
},`,
|
|
200
216
|
},
|
|
201
217
|
];
|
|
202
218
|
|