Public Access
fix(import): readable dropdowns in dark mode, select all and none
This commit is contained in:
1 parent
24a98062ee
commit
111ab1e200
5 files changed
+49
-1
No files matched your search
@@ -654,6 +654,8 @@
|
|||||||
"SAVE": "{{count}} Spiele hinzufügen",
|
"SAVE": "{{count}} Spiele hinzufügen",
|
||||||
"SAVE_FAILED": "Der Import konnte nicht gespeichert werden.",
|
"SAVE_FAILED": "Der Import konnte nicht gespeichert werden.",
|
||||||
"NEW": "Neuer Import",
|
"NEW": "Neuer Import",
|
||||||
|
"SELECT_ALL": "Alle auswählen",
|
||||||
|
"SELECT_NONE": "Alle abwählen",
|
||||||
"LINE": "Zeile {{line}}",
|
"LINE": "Zeile {{line}}",
|
||||||
"SEARCH_FAILED": "Suche fehlgeschlagen",
|
"SEARCH_FAILED": "Suche fehlgeschlagen",
|
||||||
"SEARCH_AGAIN": "Neu suchen",
|
"SEARCH_AGAIN": "Neu suchen",
|
||||||
|
|||||||
@@ -654,6 +654,8 @@
|
|||||||
"SAVE": "Add {{count}} games",
|
"SAVE": "Add {{count}} games",
|
||||||
"SAVE_FAILED": "Could not save the import.",
|
"SAVE_FAILED": "Could not save the import.",
|
||||||
"NEW": "New import",
|
"NEW": "New import",
|
||||||
|
"SELECT_ALL": "Select all",
|
||||||
|
"SELECT_NONE": "Deselect all",
|
||||||
"LINE": "Line {{line}}",
|
"LINE": "Line {{line}}",
|
||||||
"SEARCH_FAILED": "Search failed",
|
"SEARCH_FAILED": "Search failed",
|
||||||
"SEARCH_AGAIN": "Search again",
|
"SEARCH_AGAIN": "Search again",
|
||||||
|
|||||||
@@ -371,6 +371,12 @@ function Review({data, favorite, onSaved, onDone, expireSession}) {
|
|||||||
return {...choice, candidates, igdbId: candidate.igdbId,
|
return {...choice, candidates, igdbId: candidate.igdbId,
|
||||||
platform: preselectPlatform(data.rows[index].platform, candidate.platforms, favorite)};
|
platform: preselectPlatform(data.rows[index].platform, candidate.platforms, favorite)};
|
||||||
})), [data.rows, favorite]);
|
})), [data.rows, favorite]);
|
||||||
|
// A line without a match cannot be checked, one in the library only by hand; a hand check stays.
|
||||||
|
const checkAll = (checked) => setChoices((current) => current.map((choice) => {
|
||||||
|
const candidate = choice.candidates.find((known) => known.igdbId === choice.igdbId);
|
||||||
|
const next = checked && (choice.checked || (!!candidate && !candidate.inLibrary));
|
||||||
|
return choice.checked === next ? choice : {...choice, checked: next};
|
||||||
|
}));
|
||||||
const chosen = choices.map((choice, index) => ({choice, row: data.rows[index]}))
|
const chosen = choices.map((choice, index) => ({choice, row: data.rows[index]}))
|
||||||
.filter(({choice}) => choice.checked && choice.igdbId && choice.platform.trim());
|
.filter(({choice}) => choice.checked && choice.igdbId && choice.platform.trim());
|
||||||
|
|
||||||
@@ -402,7 +408,9 @@ function Review({data, favorite, onSaved, onDone, expireSession}) {
|
|||||||
<Card className="gap-0 overflow-hidden p-0">
|
<Card className="gap-0 overflow-hidden p-0">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3 border-b px-5 py-4">
|
<div className="flex flex-wrap items-center justify-between gap-3 border-b px-5 py-4">
|
||||||
<p className="text-sm text-muted-foreground">{t('IMPORT.REVIEW_HINT')}</p>
|
<p className="text-sm text-muted-foreground">{t('IMPORT.REVIEW_HINT')}</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Button variant="outline" onClick={() => checkAll(true)} disabled={busy}>{t('IMPORT.SELECT_ALL')}</Button>
|
||||||
|
<Button variant="outline" onClick={() => checkAll(false)} disabled={busy}>{t('IMPORT.SELECT_NONE')}</Button>
|
||||||
<Button variant="ghost" onClick={onDone} disabled={busy}>{t('IMPORT.NEW')}</Button>
|
<Button variant="ghost" onClick={onDone} disabled={busy}>{t('IMPORT.NEW')}</Button>
|
||||||
<Button onClick={save} disabled={busy || chosen.length === 0}>
|
<Button onClick={save} disabled={busy || chosen.length === 0}>
|
||||||
{busy && <Loader2 className="animate-spin"/>}{t('IMPORT.SAVE', {count: chosen.length})}
|
{busy && <Loader2 className="animate-spin"/>}{t('IMPORT.SAVE', {count: chosen.length})}
|
||||||
|
|||||||
@@ -171,6 +171,32 @@ describe('ImportPage', () => {
|
|||||||
]});
|
]});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('checks every line with a new match at once, and none', async () => {
|
||||||
|
renderPage({'GET ': json(200, {imports: []}), 'GET /5': json(200, ready)}, '/import?id=5');
|
||||||
|
const checked = () => ['Hades', 'Celeste', 'Hade'].map((title) => screen.getByLabelText(`IMPORT.LINE: ${title}`).checked);
|
||||||
|
await screen.findByLabelText('IMPORT.LINE: Hades');
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', {name: 'IMPORT.SELECT_ALL'}));
|
||||||
|
// Celeste is in the library already.
|
||||||
|
expect(checked()).toEqual([true, false, true]);
|
||||||
|
expect(screen.getByRole('button', {name: 'IMPORT.SAVE:2'})).toBeTruthy();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', {name: 'IMPORT.SELECT_NONE'}));
|
||||||
|
expect(checked()).toEqual([false, false, false]);
|
||||||
|
expect(screen.getByRole('button', {name: 'IMPORT.SAVE:0'}).disabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps a line in the library that was checked by hand on select all', async () => {
|
||||||
|
renderPage({'GET ': json(200, {imports: []}), 'GET /5': json(200, ready)}, '/import?id=5');
|
||||||
|
const celesteBox = await screen.findByLabelText('IMPORT.LINE: Celeste');
|
||||||
|
fireEvent.click(celesteBox);
|
||||||
|
expect(celesteBox.checked).toBe(true);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', {name: 'IMPORT.SELECT_ALL'}));
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('IMPORT.LINE: Celeste').checked).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('undoes a saved import after a confirm with the changed count', async () => {
|
it('undoes a saved import after a confirm with the changed count', async () => {
|
||||||
renderPage({
|
renderPage({
|
||||||
'GET ': json(200, {imports: [{id: 4, source: 'CSV', status: 'SAVED', total: 3, matched: 3, createdAt: '2026-09-29T10:00:00Z'}]}),
|
'GET ': json(200, {imports: [{id: 4, source: 'CSV', status: 'SAVED', total: 3, matched: 3, createdAt: '2026-09-29T10:00:00Z'}]}),
|
||||||
|
|||||||
@@ -222,6 +222,16 @@
|
|||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground antialiased;
|
@apply bg-background text-foreground antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The open list of a native select is drawn by the browser: without these it stays light in dark mode, with the
|
||||||
|
page's light text on it. */
|
||||||
|
.dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
@apply bg-popover text-popover-foreground;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Radix menus and selects lock the page scroll and add a right margin as wide as the scrollbar, measured as
|
/* Radix menus and selects lock the page scroll and add a right margin as wide as the scrollbar, measured as
|
||||||
|
|||||||
Reference in new issue
Block a user