github-issue-tower-defence-management 1.94.4 → 1.95.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 +7 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-BeJzGnfH.js +101 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +14 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +48 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.ts +19 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +39 -4
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +29 -1
- package/src/adapter/entry-points/console/ui-dist/assets/index-BeJzGnfH.js +101 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-fKvnL036.js +0 -101
- package/src/adapter/entry-points/console/ui-dist/assets/index-fKvnL036.js +0 -101
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>TDPM Console</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-BeJzGnfH.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-CO3Vvzqr.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -59,6 +59,20 @@ export const ZeroCountActiveTabStaysVisible: Story = {
|
|
|
59
59
|
},
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
export const AfterAutoAdvanceToNextTab: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
activeTab: 'unread',
|
|
65
|
+
counts: {
|
|
66
|
+
prs: 0,
|
|
67
|
+
triage: 0,
|
|
68
|
+
unread: 7,
|
|
69
|
+
'failed-preparation': 2,
|
|
70
|
+
'todo-by-human': 4,
|
|
71
|
+
},
|
|
72
|
+
onSelectTab: () => {},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
62
76
|
export const Interactive: Story = {
|
|
63
77
|
render: (args) => {
|
|
64
78
|
const [activeTab, setActiveTab] = useState<ConsoleTabName>('prs');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { findNextNonEmptyTabToRight } from './tabAdvance';
|
|
2
|
+
import type { ConsoleTabName } from './types';
|
|
3
|
+
|
|
4
|
+
const counts = (
|
|
5
|
+
overrides: Partial<Record<ConsoleTabName, number>>,
|
|
6
|
+
): Record<ConsoleTabName, number> => ({
|
|
7
|
+
prs: 0,
|
|
8
|
+
triage: 0,
|
|
9
|
+
unread: 0,
|
|
10
|
+
'failed-preparation': 0,
|
|
11
|
+
'todo-by-human': 0,
|
|
12
|
+
...overrides,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('findNextNonEmptyTabToRight', () => {
|
|
16
|
+
it('returns the first non-empty tab to the right of the active tab', () => {
|
|
17
|
+
expect(findNextNonEmptyTabToRight('prs', counts({ unread: 7 }))).toBe(
|
|
18
|
+
'unread',
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('skips empty tabs and returns the next non-empty tab further right', () => {
|
|
23
|
+
expect(
|
|
24
|
+
findNextNonEmptyTabToRight(
|
|
25
|
+
'prs',
|
|
26
|
+
counts({ triage: 0, unread: 0, 'todo-by-human': 4 }),
|
|
27
|
+
),
|
|
28
|
+
).toBe('todo-by-human');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns the immediately adjacent tab when it is non-empty', () => {
|
|
32
|
+
expect(
|
|
33
|
+
findNextNonEmptyTabToRight('prs', counts({ triage: 12, unread: 7 })),
|
|
34
|
+
).toBe('triage');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns null when no tab to the right has any items', () => {
|
|
38
|
+
expect(
|
|
39
|
+
findNextNonEmptyTabToRight('unread', counts({ prs: 35 })),
|
|
40
|
+
).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns null when the active tab is the last tab', () => {
|
|
44
|
+
expect(
|
|
45
|
+
findNextNonEmptyTabToRight('todo-by-human', counts({ prs: 35 })),
|
|
46
|
+
).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ConsoleTabName } from './types';
|
|
2
|
+
import { CONSOLE_TABS } from './types';
|
|
3
|
+
|
|
4
|
+
export const findNextNonEmptyTabToRight = (
|
|
5
|
+
activeTab: ConsoleTabName,
|
|
6
|
+
counts: Record<ConsoleTabName, number>,
|
|
7
|
+
): ConsoleTabName | null => {
|
|
8
|
+
const activeIndex = CONSOLE_TABS.findIndex((tab) => tab.name === activeTab);
|
|
9
|
+
if (activeIndex === -1) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
for (let index = activeIndex + 1; index < CONSOLE_TABS.length; index += 1) {
|
|
13
|
+
const candidate = CONSOLE_TABS[index].name;
|
|
14
|
+
if ((counts[candidate] ?? 0) > 0) {
|
|
15
|
+
return candidate;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
};
|
|
@@ -128,10 +128,8 @@ describe('ConsolePage', () => {
|
|
|
128
128
|
|
|
129
129
|
await waitFor(() => {
|
|
130
130
|
expect(
|
|
131
|
-
getByText('
|
|
132
|
-
|
|
133
|
-
?.querySelector('.console-tab-badge')?.textContent,
|
|
134
|
-
).toBe('0');
|
|
131
|
+
getByText('Unread').closest('a')?.getAttribute('aria-current'),
|
|
132
|
+
).toBe('page');
|
|
135
133
|
});
|
|
136
134
|
} finally {
|
|
137
135
|
jest.useRealTimers();
|
|
@@ -397,3 +395,40 @@ describe('ConsolePage auto-advance', () => {
|
|
|
397
395
|
}
|
|
398
396
|
});
|
|
399
397
|
});
|
|
398
|
+
|
|
399
|
+
describe('ConsolePage auto-advance tab', () => {
|
|
400
|
+
beforeEach(() => {
|
|
401
|
+
localStorage.clear();
|
|
402
|
+
window.history.replaceState({}, '', '/projects/umino/prs?k=token');
|
|
403
|
+
installFetch();
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('auto-advances to the next non-empty tab on the right after the active tab is driven to zero', async () => {
|
|
407
|
+
jest.useFakeTimers();
|
|
408
|
+
try {
|
|
409
|
+
const { getByText, findByText } = render(<ConsolePage />);
|
|
410
|
+
await waitFor(() => {
|
|
411
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
fireEvent.click(getByText('Add serveConsole subcommand'));
|
|
415
|
+
expect(await findByText('← Back to list')).toBeInTheDocument();
|
|
416
|
+
fireEvent.click(getByText('Approve'));
|
|
417
|
+
|
|
418
|
+
act(() => {
|
|
419
|
+
jest.advanceTimersByTime(5100);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
await waitFor(() => {
|
|
423
|
+
expect(
|
|
424
|
+
getByText('Notify finished issue preparation'),
|
|
425
|
+
).toBeInTheDocument();
|
|
426
|
+
});
|
|
427
|
+
expect(
|
|
428
|
+
getByText('Unread').closest('a')?.getAttribute('aria-current'),
|
|
429
|
+
).toBe('page');
|
|
430
|
+
} finally {
|
|
431
|
+
jest.useRealTimers();
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useMemo } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import { ConsoleTabList } from '../components/layout/ConsoleTabList';
|
|
3
3
|
import { ConsoleItemList } from '../components/list/ConsoleItemList';
|
|
4
4
|
import { ConsoleUndoToast } from '../components/operations/ConsoleUndoToast';
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
overlayKeyForItem,
|
|
28
28
|
} from '../logic/overlay';
|
|
29
29
|
import type { ConsoleSwipeDirection } from '../logic/swipe';
|
|
30
|
+
import { findNextNonEmptyTabToRight } from '../logic/tabAdvance';
|
|
30
31
|
import type {
|
|
31
32
|
ConsoleListItem,
|
|
32
33
|
ConsoleOverlayStatus,
|
|
@@ -109,6 +110,26 @@ export const ConsolePage = () => {
|
|
|
109
110
|
);
|
|
110
111
|
}, [selectedItemKey, activeSnapshot]);
|
|
111
112
|
|
|
113
|
+
const activeCount = counts[activeTab];
|
|
114
|
+
const previousActiveTabCountRef = useRef<{
|
|
115
|
+
tab: ConsoleTabName;
|
|
116
|
+
count: number;
|
|
117
|
+
}>({ tab: activeTab, count: activeCount });
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
const previous = previousActiveTabCountRef.current;
|
|
120
|
+
previousActiveTabCountRef.current = { tab: activeTab, count: activeCount };
|
|
121
|
+
if (previous.tab !== activeTab) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (previous.count > 0 && activeCount === 0) {
|
|
125
|
+
const nextTab = findNextNonEmptyTabToRight(activeTab, counts);
|
|
126
|
+
if (nextTab !== null) {
|
|
127
|
+
navigation.selectTab(nextTab);
|
|
128
|
+
navigation.closeItem();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}, [activeTab, activeCount, counts, navigation]);
|
|
132
|
+
|
|
112
133
|
const overlayStatusForSelected = ((): ConsoleOverlayStatus | null => {
|
|
113
134
|
if (selectedItem === null) {
|
|
114
135
|
return null;
|
|
@@ -202,6 +223,13 @@ export const ConsolePage = () => {
|
|
|
202
223
|
/>
|
|
203
224
|
) : (
|
|
204
225
|
<div className="console-detail-screen" ref={detailScreenRef}>
|
|
226
|
+
<button
|
|
227
|
+
type="button"
|
|
228
|
+
className="console-back-button"
|
|
229
|
+
onClick={closeItem}
|
|
230
|
+
>
|
|
231
|
+
← Back to list
|
|
232
|
+
</button>
|
|
205
233
|
<ConsoleItemDetailContainer
|
|
206
234
|
tab={activeTab}
|
|
207
235
|
item={selectedItem}
|