diff --git a/web/e2e/smoke.spec.js b/web/e2e/smoke.spec.js index 6d8fdcd..f6ed4f8 100644 --- a/web/e2e/smoke.spec.js +++ b/web/e2e/smoke.spec.js @@ -363,7 +363,9 @@ test('en projektgruppe kan foldes, fyldes og slettes uden at tage projekterne me // Colour, which is chosen while renaming rather than from the row. Both are // edits to the same thing, and a heading carrying four controls crowded the // name it exists to show. - await sidebar.getByRole('button', { name: 'Omdøb' }).click(); + // Double-click on the row opens it; the two words that used to sit on top of + // the name are gone from the sidebar entirely. + await sidebar.getByRole('link', { name: 'Arbejde', exact: true }).dblclick(); await sidebar.getByRole('button', { name: 'Petrol' }).click(); // Still open: picking a colour must not close the form under the pointer, which // is what a plain blur-to-cancel does in the two browsers that do not focus a @@ -407,7 +409,7 @@ test('en projektgruppe kan foldes, fyldes og slettes uden at tage projekterne me // Renaming writes one row rather than one per project, which is the reason a // group is a table and not a string repeated on every project. - await sidebar.getByRole('button', { name: 'Omdøb' }).click(); + await sidebar.getByRole('link', { name: 'Arbejde', exact: true }).dblclick(); const name = sidebar.getByLabel('Gruppens navn'); await name.fill('Kontoret'); await name.press('Enter'); @@ -416,8 +418,11 @@ test('en projektgruppe kan foldes, fyldes og slettes uden at tage projekterne me // Deleting the heading must not take the project filed under it. It is // ungrouped here, which is the case that would look identical either way if // the assertion were only "the group is gone". + // Deleting is on the group's own page now, where the projects it will not take + // with it are on the screen above the button. + await sidebar.getByRole('link', { name: 'Kontoret', exact: true }).click(); page.once('dialog', (dialog) => dialog.accept()); - await sidebar.getByRole('button', { name: 'Slet' }).click(); + await page.getByRole('button', { name: 'Slet' }).click(); await expect(sidebar.getByRole('link', { name: 'Kontoret', exact: true })).toBeHidden(); await expect(sidebar.getByRole('link', { name: 'Regnskab' })).toBeVisible(); diff --git a/web/src/lib/components/Sidebar.svelte b/web/src/lib/components/Sidebar.svelte index a85cda9..d5f17d6 100644 --- a/web/src/lib/components/Sidebar.svelte +++ b/web/src/lib/components/Sidebar.svelte @@ -105,17 +105,18 @@ await app.renameGroup(group.id, name); } - async function removeGroup(group) { - const count = inGroup(group.id).length; - // Says what happens to the projects, because "slet gruppen" reads like it - // might take them with it. It does not: only the heading goes. - const kept = - count === 1 ? t('sidebar.deleteGroupOne') : t('sidebar.deleteGroupMany', { n: count }); - const question = t('sidebar.deleteGroup', { name: group.name }); - if (!confirm(count ? `${question} ${kept}` : question)) return; - await app.deleteGroup(group.id); + /** Double-click, or F2 with the row focused. The colour is chosen in the same + * form, because it is the same edit to the same thing. */ + /** Cleared by a second click, which means the double was a rename. */ + let navTimer; + + function startRename(group) { + clearTimeout(navTimer); + renamingGroup = group.id; + groupName = group.name; } + // --- resizing ------------------------------------------------------------------- let resizing = $state(false); @@ -547,6 +548,13 @@ overId = null; }} ondrop={(e) => onGroupDrop(e, group)} + ondblclick={() => startRename(group)} + onkeydown={(e) => { + if (e.key === 'F2') { + e.preventDefault(); + startRename(group); + } + }} > {#if renamingGroup === group.id} + { + e.preventDefault(); + clearTimeout(navTimer); + if (e.detail > 1) return; + navTimer = setTimeout(() => { + onnavigate(); + goto(`/gruppe/${group.id}`); + }, 200); + }} + > {inside.length} - - {/if} @@ -1013,43 +1030,6 @@ /* Hidden until the heading is hovered, like the section actions on a project page: two buttons permanently beside a label stop it reading as a label. */ - /* Hidden but still in the flow, these two reserved their own width at all - times — enough that ARBEJDE was drawn as "ARBE". They are lifted out of the - flow and sit over the right end of the row, so the name has the whole line - until somebody actually reaches for them. */ - .group-action { - position: absolute; - right: var(--s2); - font-size: var(--text-xs); - color: var(--ink-faint); - padding: 0 var(--s1); - flex: none; - opacity: 0; - background: var(--surface); - transition: opacity var(--fast) var(--ease); - } - - .group-action.remove { - right: var(--s2); - } - - .group-action:not(.remove) { - right: calc(var(--s2) + 3.2em); - } - - .folder-head:hover .group-action, - .group-action:focus-visible { - opacity: 1; - } - - .group-action:hover { - color: var(--ink); - } - - .group-action.remove:hover { - color: var(--danger); - } - .folder-head .count { margin-left: auto; } diff --git a/web/src/routes/gruppe/[id]/+page.svelte b/web/src/routes/gruppe/[id]/+page.svelte index a1b3c99..aa62d9b 100644 --- a/web/src/routes/gruppe/[id]/+page.svelte +++ b/web/src/routes/gruppe/[id]/+page.svelte @@ -12,6 +12,7 @@ * projects answers is what is left. */ import { page } from '$app/stores'; + import { goto } from '$app/navigation'; import { api, humanMessage } from '$lib/api.js'; import { app, projectName } from '$lib/stores.svelte.js'; import { colorVar } from '$lib/colors.js'; @@ -29,6 +30,18 @@ load(id); }); + /** Says what happens to the projects, because "delete the group" reads like it + * might take them with it. It does not: only the heading goes. */ + async function removeGroup() { + const count = projects.length; + const kept = + count === 1 ? t('sidebar.deleteGroupOne') : t('sidebar.deleteGroupMany', { n: count }); + const question = t('sidebar.deleteGroup', { name: group.name }); + if (!confirm(count ? `${question} ${kept}` : question)) return; + await app.deleteGroup(group.id); + goto('/'); + } + async function load(groupId) { if (!groupId) return; status = 'loading'; @@ -208,6 +221,14 @@ {uploading ? t('group.uploading') : t('group.addFile')} + + +
+ +
{/if} @@ -230,6 +251,22 @@ gap: var(--s3); } + .danger { + margin-top: var(--s4); + padding-top: var(--s4); + border-top: 1px solid var(--line); + } + + .danger .remove { + font-size: var(--text-sm); + color: var(--ink-faint); + padding: var(--s2) 0; + } + + .danger .remove:hover { + color: var(--danger); + } + h1 { font-size: var(--text-2xl); min-width: 0;