From 7281a7128c3478e985af53ff7adb5dc656fc1ce9 Mon Sep 17 00:00:00 2001 From: johaven Date: Fri, 24 Jul 2026 00:55:56 +0200 Subject: [PATCH 01/15] feat(frontend:spaces): add file selection controls with accessible toggle behavior --- .../components/spaces-browser.component.html | 25 +++++-- .../components/spaces-browser.component.scss | 72 +++++++++++++++++++ .../components/spaces-browser.component.ts | 12 +++- 3 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 frontend/src/app/applications/spaces/components/spaces-browser.component.scss diff --git a/frontend/src/app/applications/spaces/components/spaces-browser.component.html b/frontend/src/app/applications/spaces/components/spaces-browser.component.html index 245db6fa7..2215a6768 100644 --- a/frontend/src/app/applications/spaces/components/spaces-browser.component.html +++ b/frontend/src/app/applications/spaces/components/spaces-browser.component.html @@ -391,12 +391,25 @@ @switch (th.key) { @case ('name') {
- + + + + @if (f.isRenamed) { img { + margin-right: 0 !important; + } +} + +.file-selection-overlay { + position: absolute; + inset: 0; + width: 30px; + height: 30px; + padding: 0; + color: rgba(255, 255, 255, .88); + background: transparent; + border: 0; + opacity: 0; + cursor: pointer; + + &:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; + } + + &.is-selected { + color: #fff; + opacity: 1; + + .file-selection-indicator { + background-color: rgba(24, 27, 34, .95); + } + } +} + +.file-selection-indicator { + position: absolute; + top: 0; + left: 0; + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + background-color: rgba(32, 36, 45, .78); + border-radius: .25rem; + font-size: .55rem; + + fa-icon { + margin-right: 0 !important; + } +} + +.file-selection-control:hover .file-selection-overlay, +.file-selection-overlay:focus-visible { + opacity: 1; +} + +@media (hover: none) { + .file-selection-overlay { + opacity: 1; + + .file-selection-indicator { + background-color: rgba(32, 36, 45, .68); + } + } +} diff --git a/frontend/src/app/applications/spaces/components/spaces-browser.component.ts b/frontend/src/app/applications/spaces/components/spaces-browser.component.ts index d66cdce22..f940a1c1b 100644 --- a/frontend/src/app/applications/spaces/components/spaces-browser.component.ts +++ b/frontend/src/app/applications/spaces/components/spaces-browser.component.ts @@ -10,6 +10,7 @@ import { faArrowsAlt, faArrowUp, faBan, + faCheck, faCircleInfo, faCirclePlus, faClipboardList, @@ -108,7 +109,8 @@ import { SpaceAnchorFileDialogComponent } from './dialogs/space-anchor-file-dial TapDirective, FileLockFormatPipe ], - templateUrl: 'spaces-browser.component.html' + templateUrl: 'spaces-browser.component.html', + styleUrl: 'spaces-browser.component.scss' }) export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild(VirtualScrollComponent) scrollView: { element: ElementRef; viewPortItems: FileModel[]; scrollInto: (arg: FileModel | number) => void } @@ -147,6 +149,7 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy faArrowsAlt, faCircleInfo, faBan, + faCheck, faArrowUp, faArrowDown, faLock, @@ -395,6 +398,13 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy } } + toggleFileSelection(ev: MouseEvent, file: FileModel) { + ev.stopPropagation() + if (!this.loading) { + this.modifySelection(file) + } + } + onContextMenu(ev: MouseEvent | Event) { ev.preventDefault() ev.stopPropagation() From 1547273a0a994527bfa4b848ed964b12e14c0652 Mon Sep 17 00:00:00 2001 From: johaven Date: Sat, 25 Jul 2026 00:09:50 +0200 Subject: [PATCH 02/15] fix(frontend:spaces): refine file selection check behavior --- .../components/spaces-browser.component.html | 2 +- .../components/spaces-browser.component.scss | 9 ++---- .../components/spaces-browser.component.ts | 30 +++++++++++++++---- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/applications/spaces/components/spaces-browser.component.html b/frontend/src/app/applications/spaces/components/spaces-browser.component.html index 2215a6768..d8f42b8b0 100644 --- a/frontend/src/app/applications/spaces/components/spaces-browser.component.html +++ b/frontend/src/app/applications/spaces/components/spaces-browser.component.html @@ -402,7 +402,7 @@ (click)="toggleFileSelection($event, f)" [attr.aria-label]="f.name" [attr.aria-pressed]="f.isSelected" - [class.is-selected]="f.isSelected" + [class.is-selected]="f.isSelected && showSelectionChecks" class="file-selection-overlay" type="button"> diff --git a/frontend/src/app/applications/spaces/components/spaces-browser.component.scss b/frontend/src/app/applications/spaces/components/spaces-browser.component.scss index a8e2f1bb2..54aba273b 100644 --- a/frontend/src/app/applications/spaces/components/spaces-browser.component.scss +++ b/frontend/src/app/applications/spaces/components/spaces-browser.component.scss @@ -56,17 +56,12 @@ } } -.file-selection-control:hover .file-selection-overlay, .file-selection-overlay:focus-visible { opacity: 1; } -@media (hover: none) { - .file-selection-overlay { +@media (hover: hover) { + .file-selection-control:hover .file-selection-overlay { opacity: 1; - - .file-selection-indicator { - background-color: rgba(32, 36, 45, .68); - } } } diff --git a/frontend/src/app/applications/spaces/components/spaces-browser.component.ts b/frontend/src/app/applications/spaces/components/spaces-browser.component.ts index f940a1c1b..5de39cea2 100644 --- a/frontend/src/app/applications/spaces/components/spaces-browser.component.ts +++ b/frontend/src/app/applications/spaces/components/spaces-browser.component.ts @@ -173,6 +173,7 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy // Actions protected multipleSelection = false protected hasSelection = false + protected showSelectionChecks = false protected hasDisabledItemsInSelection = false protected canCompress = true protected renamingInProgress = false @@ -249,6 +250,7 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy private focusOnSelect: string private selectionAnchor: FileModel | null = null private selectionFocus: FileModel | null = null + private imageSelectionMode = false // Sort private readonly sortSettings: SortSettings = { default: [ @@ -401,6 +403,14 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy toggleFileSelection(ev: MouseEvent, file: FileModel) { ev.stopPropagation() if (!this.loading) { + if (!this.imageSelectionMode && this.selection.length === 1 && !file.isSelected) { + this.imageSelectionMode = true + this.setSelection([file], file, file, true) + return + } + if (!this.selection.length) { + this.imageSelectionMode = true + } this.modifySelection(file) } } @@ -802,19 +812,29 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy this.setSelection( this.selection.filter((f) => f !== file), file, - file + file, + true ) } else { - this.setSelection([file, ...this.selection], file, file) + this.setSelection([file, ...this.selection], file, file, true) } } - private setSelection(selection: FileModel[], selectionAnchor: FileModel | null = null, selectionFocus: FileModel | null = selectionAnchor) { + private setSelection( + selection: FileModel[], + selectionAnchor: FileModel | null = null, + selectionFocus: FileModel | null = selectionAnchor, + keepImageSelectionMode = false + ) { const selected = new Set(selection) this.selection = this.files.filter((file: FileModel) => { file.isSelected = selected.has(file) return file.isSelected }) + if (!keepImageSelectionMode || !this.selection.length) { + this.imageSelectionMode = false + } + this.showSelectionChecks = this.selection.length > 1 || this.imageSelectionMode this.setSelectionCursor(selectionAnchor, selectionFocus) // update states this.hasSelection = !!this.selection.length @@ -844,7 +864,7 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy const files = this.getFilteredFiles() const fileIndex = files.indexOf(file) if (fileIndex === -1) { - this.setSelection([file], file, file) + this.setSelection([file], file, file, true) return } let anchor = this.selectionAnchor @@ -857,7 +877,7 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy const maxIndex = Math.max(anchorIndex, fileIndex) const filteredFiles = new Set(files) const hiddenSelection = this.selection.filter((f: FileModel) => !filteredFiles.has(f)) - this.setSelection([...hiddenSelection, ...files.slice(minIndex, maxIndex + 1)], anchor, file) + this.setSelection([...hiddenSelection, ...files.slice(minIndex, maxIndex + 1)], anchor, file, true) } private getKeyboardNavigationTarget(files: FileModel[], keyCode: number, extendSelection: boolean): { file: FileModel; keyCode: number } { From da798452a1925bb6796af45d8909452e247a7cf8 Mon Sep 17 00:00:00 2001 From: johaven Date: Sat, 25 Jul 2026 00:25:54 +0200 Subject: [PATCH 03/15] fix(frontend:spaces): expand file selection hit area --- .../spaces/components/spaces-browser.component.scss | 13 ++++++++----- frontend/src/styles/components/_app.scss | 8 ++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/applications/spaces/components/spaces-browser.component.scss b/frontend/src/app/applications/spaces/components/spaces-browser.component.scss index 54aba273b..66f90fa18 100644 --- a/frontend/src/app/applications/spaces/components/spaces-browser.component.scss +++ b/frontend/src/app/applications/spaces/components/spaces-browser.component.scss @@ -5,6 +5,7 @@ width: 30px; height: 30px; margin-right: .3rem; + overflow: visible; > img { margin-right: 0 !important; @@ -13,15 +14,17 @@ .file-selection-overlay { position: absolute; - inset: 0; - width: 30px; - height: 30px; + top: 50%; + left: calc(0px - var(--app-table-cell-inline-padding)); + width: calc(30px + var(--app-table-cell-inline-padding)); + height: var(--app-table-row-height); padding: 0; color: rgba(255, 255, 255, .88); background: transparent; border: 0; opacity: 0; cursor: pointer; + transform: translateY(-50%); &:focus-visible { outline: 2px solid currentColor; @@ -40,8 +43,8 @@ .file-selection-indicator { position: absolute; - top: 0; - left: 0; + top: calc((var(--app-table-row-height) - 30px) / 2); + left: var(--app-table-cell-inline-padding); display: inline-flex; align-items: center; justify-content: center; diff --git a/frontend/src/styles/components/_app.scss b/frontend/src/styles/components/_app.scss index e7bd82f07..5bad56ecc 100755 --- a/frontend/src/styles/components/_app.scss +++ b/frontend/src/styles/components/_app.scss @@ -225,6 +225,9 @@ } .app-table { + --app-table-cell-inline-padding: .5rem; + --app-table-row-height: 36px; + @extend .table; @include no-select-utility; table-layout: fixed; @@ -243,13 +246,13 @@ white-space: nowrap; font-weight: 500; border: none; - padding: .25rem 0.5rem; + padding: .25rem var(--app-table-cell-inline-padding); } } } tbody tr { - height: 36px !important; + height: var(--app-table-row-height) !important; border-bottom: .5px solid; &:hover { @@ -275,6 +278,7 @@ vertical-align: middle; max-width: 0; padding-bottom: 1px; + padding-inline: var(--app-table-cell-inline-padding); padding-top: 1px; span { From c0ea86f112f679ccec723dda64ddd661bc5f01f6 Mon Sep 17 00:00:00 2001 From: johaven Date: Sat, 25 Jul 2026 01:29:45 +0200 Subject: [PATCH 04/15] fix(frontend:spaces): preserve selection when activating file check --- .../components/spaces-browser.component.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/applications/spaces/components/spaces-browser.component.ts b/frontend/src/app/applications/spaces/components/spaces-browser.component.ts index 5de39cea2..81098cae6 100644 --- a/frontend/src/app/applications/spaces/components/spaces-browser.component.ts +++ b/frontend/src/app/applications/spaces/components/spaces-browser.component.ts @@ -250,7 +250,7 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy private focusOnSelect: string private selectionAnchor: FileModel | null = null private selectionFocus: FileModel | null = null - private imageSelectionMode = false + private selectionControlMode = false // Sort private readonly sortSettings: SortSettings = { default: [ @@ -403,13 +403,13 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy toggleFileSelection(ev: MouseEvent, file: FileModel) { ev.stopPropagation() if (!this.loading) { - if (!this.imageSelectionMode && this.selection.length === 1 && !file.isSelected) { - this.imageSelectionMode = true + if (!this.selectionControlMode && this.selection.length === 1) { + this.selectionControlMode = true this.setSelection([file], file, file, true) return } if (!this.selection.length) { - this.imageSelectionMode = true + this.selectionControlMode = true } this.modifySelection(file) } @@ -824,17 +824,17 @@ export class SpacesBrowserComponent implements OnInit, AfterViewInit, OnDestroy selection: FileModel[], selectionAnchor: FileModel | null = null, selectionFocus: FileModel | null = selectionAnchor, - keepImageSelectionMode = false + keepSelectionControlMode = false ) { const selected = new Set(selection) this.selection = this.files.filter((file: FileModel) => { file.isSelected = selected.has(file) return file.isSelected }) - if (!keepImageSelectionMode || !this.selection.length) { - this.imageSelectionMode = false + if (!keepSelectionControlMode || !this.selection.length) { + this.selectionControlMode = false } - this.showSelectionChecks = this.selection.length > 1 || this.imageSelectionMode + this.showSelectionChecks = this.selection.length > 1 || this.selectionControlMode this.setSelectionCursor(selectionAnchor, selectionFocus) // update states this.hasSelection = !!this.selection.length From 9e3db4e9fb8ba4925c31537daaac75d6110fff04 Mon Sep 17 00:00:00 2001 From: johaven Date: Sat, 25 Jul 2026 01:48:59 +0200 Subject: [PATCH 05/15] feat(frontend:gallery): add selection controls and centralize view sizing --- .../links/components/links.component.html | 13 +- .../shares/components/shared.component.html | 16 ++- .../components/spaces-browser.component.html | 38 +++--- .../components/spaces-browser.component.scss | 71 +++++++++-- .../spaces/components/spaces.component.html | 17 ++- .../spaces/components/trash.component.html | 19 ++- frontend/src/styles/components/_app.scss | 89 -------------- frontend/src/styles/components/_gallery.scss | 111 ++++++++++++++++++ .../src/styles/components/_theme_dark.scss | 2 +- .../src/styles/components/_theme_light.scss | 2 +- frontend/src/styles/components/index.scss | 1 + 11 files changed, 225 insertions(+), 154 deletions(-) create mode 100644 frontend/src/styles/components/_gallery.scss diff --git a/frontend/src/app/applications/links/components/links.component.html b/frontend/src/app/applications/links/components/links.component.html index 32bb43e93..12f945e5e 100644 --- a/frontend/src/app/applications/links/components/links.component.html +++ b/frontend/src/app/applications/links/components/links.component.html @@ -44,7 +44,10 @@ @if (galleryMode?.enabled) { -
+ } diff --git a/frontend/src/app/applications/shares/components/shared.component.html b/frontend/src/app/applications/shares/components/shared.component.html index fe7b73c7e..f972aee86 100644 --- a/frontend/src/app/applications/shares/components/shared.component.html +++ b/frontend/src/app/applications/shares/components/shared.component.html @@ -51,7 +51,10 @@ @if (galleryMode?.enabled) { -
+