mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-09-09 11:17:58 +00:00
Wiring up the toggles
This commit is contained in:
@@ -226,6 +226,16 @@ describe('SettingsComponent', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('enables sidebar item controls on general settings until destroyed', () => {
|
||||
completeSetup()
|
||||
|
||||
expect(settingsService.organizingSidebarItems()).toBe(true)
|
||||
|
||||
component.ngOnDestroy()
|
||||
|
||||
expect(settingsService.organizingSidebarItems()).toBe(false)
|
||||
})
|
||||
|
||||
it('should support tabbed settings & change URL, prevent navigation if dirty confirmation rejected', async () => {
|
||||
completeSetup()
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
|
||||
@@ -297,14 +297,17 @@ export class SettingsComponent
|
||||
|
||||
this.activatedRoute.paramMap.subscribe((paramMap) => {
|
||||
const section = paramMap.get('section')
|
||||
let navID = SettingsNavIDs.General
|
||||
if (section) {
|
||||
const navIDKey: string = Object.keys(SettingsNavIDs).find(
|
||||
(navID) => navID.toLowerCase() == section
|
||||
)
|
||||
if (navIDKey) {
|
||||
this.activeNavID.set(SettingsNavIDs[navIDKey])
|
||||
navID = SettingsNavIDs[navIDKey]
|
||||
}
|
||||
}
|
||||
this.activeNavID.set(navID)
|
||||
this.settings.organizingSidebarItems.set(navID === SettingsNavIDs.General)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -463,6 +466,7 @@ export class SettingsComponent
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.settings.organizingSidebarItems.set(false)
|
||||
if (this.isDirty) this.settings.updateAppearanceSettings() // in case user changed appearance but didn't save
|
||||
this.storeSub && this.storeSub.unsubscribe()
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
<div class="row">
|
||||
<nav id="sidebarMenu" class="d-md-block bg-light sidebar collapse"
|
||||
[ngClass]="slimSidebarEnabled ? 'slim' : 'expanded'" [class.animating]="slimSidebarAnimating()"
|
||||
[class.sidebar-customizing]="settingsService.organizingSidebarItems()"
|
||||
[ngbCollapse]="isMenuCollapsed()">
|
||||
@if (canSaveSettings) {
|
||||
<button class="btn btn-sm btn-dark sidebar-slim-toggler" (click)="toggleSlimSidebar()" [aria-label]="slimSidebarEnabled ? 'Expand sidebar' : 'Collapse sidebar'" i18n-aria-label>
|
||||
@@ -86,12 +87,15 @@
|
||||
}
|
||||
<div class="sidebar-sticky pt-3 pb-1 d-flex flex-column justify-space-around">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item app-link" [class.d-none]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Dashboard)">
|
||||
<a class="nav-link" routerLink="dashboard" routerLinkActive="active" (click)="closeMenu()"
|
||||
<li class="nav-item app-link sidebar-customizable-item" [class.d-none]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Dashboard) && !settingsService.organizingSidebarItems()">
|
||||
<a class="nav-link" [class.opacity-50]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Dashboard)" routerLink="dashboard" routerLinkActive="active" (click)="closeMenu()"
|
||||
ngbPopover="Dashboard" i18n-ngbPopover [disablePopover]="!slimSidebarPopoversEnabled" placement="end"
|
||||
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<i-bs class="me-2" name="house"></i-bs><span class="nav-link-label"><ng-container i18n>Dashboard</ng-container></span>
|
||||
</a>
|
||||
@if (settingsService.organizingSidebarItems()) {
|
||||
<pngx-input-switch class="sidebar-item-toggle" [compact]="true" title="Dashboard" i18n-title [ngModel]="!settingsService.sidebarItemIsHidden(HideableSidebarItemID.Dashboard)" (ngModelChange)="toggleSidebarItem(HideableSidebarItemID.Dashboard, $event)"></pngx-input-switch>
|
||||
}
|
||||
</li>
|
||||
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
<a class="nav-link" routerLink="documents" routerLinkActive="active"
|
||||
@@ -237,29 +241,38 @@
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
<li class="nav-item app-link" [class.d-none]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.SavedViews)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
||||
<a class="nav-link" routerLink="savedviews" routerLinkActive="active" (click)="closeMenu()"
|
||||
<li class="nav-item app-link sidebar-customizable-item" [class.d-none]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.SavedViews) && !settingsService.organizingSidebarItems()" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
||||
<a class="nav-link" [class.opacity-50]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.SavedViews)" routerLink="savedviews" routerLinkActive="active" (click)="closeMenu()"
|
||||
ngbPopover="Saved Views" i18n-ngbPopover [disablePopover]="!slimSidebarPopoversEnabled" placement="end"
|
||||
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<i-bs class="me-2" name="window-stack"></i-bs><span class="nav-link-label"><ng-container i18n>Saved Views</ng-container></span>
|
||||
</a>
|
||||
@if (settingsService.organizingSidebarItems()) {
|
||||
<pngx-input-switch class="sidebar-item-toggle" [compact]="true" title="Saved Views" i18n-title [ngModel]="!settingsService.sidebarItemIsHidden(HideableSidebarItemID.SavedViews)" (ngModelChange)="toggleSidebarItem(HideableSidebarItemID.SavedViews, $event)"></pngx-input-switch>
|
||||
}
|
||||
</li>
|
||||
<li class="nav-item app-link" [class.d-none]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Workflows)"
|
||||
<li class="nav-item app-link sidebar-customizable-item" [class.d-none]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Workflows) && !settingsService.organizingSidebarItems()"
|
||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Workflow }"
|
||||
tourAnchor="tour.workflows">
|
||||
<a class="nav-link" routerLink="workflows" routerLinkActive="active" (click)="closeMenu()"
|
||||
<a class="nav-link" [class.opacity-50]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Workflows)" routerLink="workflows" routerLinkActive="active" (click)="closeMenu()"
|
||||
ngbPopover="Workflows" i18n-ngbPopover [disablePopover]="!slimSidebarPopoversEnabled" placement="end"
|
||||
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<i-bs class="me-2" name="boxes"></i-bs><span class="nav-link-label"><ng-container i18n>Workflows</ng-container></span>
|
||||
</a>
|
||||
@if (settingsService.organizingSidebarItems()) {
|
||||
<pngx-input-switch class="sidebar-item-toggle" [compact]="true" title="Workflows" i18n-title [ngModel]="!settingsService.sidebarItemIsHidden(HideableSidebarItemID.Workflows)" (ngModelChange)="toggleSidebarItem(HideableSidebarItemID.Workflows, $event)"></pngx-input-switch>
|
||||
}
|
||||
</li>
|
||||
<li class="nav-item app-link" [class.d-none]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Mail)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }"
|
||||
<li class="nav-item app-link sidebar-customizable-item" [class.d-none]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Mail) && !settingsService.organizingSidebarItems()" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }"
|
||||
tourAnchor="tour.mail">
|
||||
<a class="nav-link" routerLink="mail" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Mail"
|
||||
<a class="nav-link" [class.opacity-50]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Mail)" routerLink="mail" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Mail"
|
||||
i18n-ngbPopover [disablePopover]="!slimSidebarPopoversEnabled" placement="end" container="body"
|
||||
triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<i-bs class="me-2" name="envelope"></i-bs><span class="nav-link-label"><ng-container i18n>Mail</ng-container></span>
|
||||
</a>
|
||||
@if (settingsService.organizingSidebarItems()) {
|
||||
<pngx-input-switch class="sidebar-item-toggle" [compact]="true" title="Mail" i18n-title [ngModel]="!settingsService.sidebarItemIsHidden(HideableSidebarItemID.Mail)" (ngModelChange)="toggleSidebarItem(HideableSidebarItemID.Mail, $event)"></pngx-input-switch>
|
||||
}
|
||||
</li>
|
||||
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
|
||||
<a class="nav-link" routerLink="trash" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Trash"
|
||||
@@ -322,13 +335,16 @@
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<li class="nav-item mt-2" [class.d-none]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Documentation)" tourAnchor="tour.outro">
|
||||
<a class="text-muted small d-flex align-items-center flex-wrap text-decoration-none nav-anchor"
|
||||
<li class="nav-item mt-2 sidebar-customizable-item" [class.d-none]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Documentation) && !settingsService.organizingSidebarItems()" tourAnchor="tour.outro">
|
||||
<a class="text-muted small d-flex align-items-center flex-wrap text-decoration-none nav-anchor" [class.opacity-50]="settingsService.sidebarItemIsHidden(HideableSidebarItemID.Documentation)"
|
||||
target="_blank" rel="noopener noreferrer" href="https://docs.paperless-ngx.com" ngbPopover="Documentation"
|
||||
i18n-ngbPopover [disablePopover]="!slimSidebarPopoversEnabled" placement="end" container="body"
|
||||
triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<i-bs class="d-flex me-2" name="question-circle"></i-bs><span><ng-container i18n>Documentation</ng-container></span>
|
||||
</a>
|
||||
@if (settingsService.organizingSidebarItems()) {
|
||||
<pngx-input-switch class="sidebar-item-toggle" [compact]="true" title="Documentation" i18n-title [ngModel]="!settingsService.sidebarItemIsHidden(HideableSidebarItemID.Documentation)" (ngModelChange)="toggleSidebarItem(HideableSidebarItemID.Documentation, $event)"></pngx-input-switch>
|
||||
}
|
||||
</li>
|
||||
<li class="nav-item" [class.visually-hidden]="slimSidebarEnabled">
|
||||
<div class="text-muted small d-flex align-items-center flex-wrap nav-label">
|
||||
|
||||
@@ -33,6 +33,22 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
&.sidebar-customizing .sidebar-customizable-item {
|
||||
position: relative;
|
||||
|
||||
.nav-link,
|
||||
.nav-anchor {
|
||||
padding-right: 3.5rem;
|
||||
}
|
||||
|
||||
.sidebar-item-toggle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0.25rem;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
--pngx-sidebar-width: var(--pngx-sidebar-expanded-width);
|
||||
}
|
||||
@@ -121,6 +137,10 @@ main {
|
||||
.sidebar.slim {
|
||||
max-width: 55px;
|
||||
|
||||
.sidebar-item-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
li.nav-item span.badge {
|
||||
display: inline-block;
|
||||
margin-right: -4px;
|
||||
|
||||
@@ -308,6 +308,25 @@ describe('AppFrameComponent', () => {
|
||||
).not.toContain('d-none')
|
||||
})
|
||||
|
||||
it('should show hidden items and visibility switches while customizing', () => {
|
||||
settingsService.set(SETTINGS_KEYS.SIDEBAR_HIDDEN_ITEMS, [
|
||||
HideableSidebarItemID.Dashboard,
|
||||
])
|
||||
settingsService.organizingSidebarItems.set(true)
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(
|
||||
fixture.nativeElement.querySelectorAll('pngx-input-switch').length
|
||||
).toBe(5)
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('[routerLink="dashboard"]')
|
||||
.parentElement.classList
|
||||
).not.toContain('d-none')
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('[routerLink="dashboard"]').classList
|
||||
).toContain('opacity-50')
|
||||
})
|
||||
|
||||
it('should show error on toggle slim sidebar if store settings fails', () => {
|
||||
jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const toastSpy = jest.spyOn(toastService, 'showError')
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@angular/cdk/drag-drop'
|
||||
import { NgClass } from '@angular/common'
|
||||
import { Component, HostListener, inject, OnInit, signal } from '@angular/core'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router'
|
||||
import {
|
||||
NgbCollapseModule,
|
||||
@@ -52,6 +53,7 @@ import { ChatComponent } from '../chat/chat/chat.component'
|
||||
import { BrandMarkComponent } from '../common/logo/brand-mark/brand-mark.component'
|
||||
import { LogoComponent } from '../common/logo/logo.component'
|
||||
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
|
||||
import { SwitchComponent } from '../common/input/switch/switch.component'
|
||||
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
|
||||
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||
import { GlobalSearchComponent } from './global-search/global-search.component'
|
||||
@@ -80,6 +82,8 @@ const SCROLL_THRESHOLD = 16
|
||||
NgxBootstrapIconsModule,
|
||||
DragDropModule,
|
||||
TourNgBootstrap,
|
||||
FormsModule,
|
||||
SwitchComponent,
|
||||
],
|
||||
})
|
||||
export class AppFrameComponent
|
||||
@@ -200,6 +204,25 @@ export class AppFrameComponent
|
||||
}, 200) // slightly longer than css animation for slim sidebar
|
||||
}
|
||||
|
||||
toggleSidebarItem(item: HideableSidebarItemID, visible: boolean): void {
|
||||
const previousHiddenItems = this.settingsService.hiddenSidebarItems()
|
||||
this.settingsService
|
||||
.updateSidebarItemVisibility(item, visible)
|
||||
.pipe(first())
|
||||
.subscribe({
|
||||
error: (error) => {
|
||||
this.settingsService.set(
|
||||
SETTINGS_KEYS.SIDEBAR_HIDDEN_ITEMS,
|
||||
previousHiddenItems
|
||||
)
|
||||
this.toastService.showError(
|
||||
$localize`An error occurred while saving settings.`,
|
||||
error
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
toggleAttributesSections(event?: Event): void {
|
||||
event?.preventDefault()
|
||||
event?.stopPropagation()
|
||||
|
||||
@@ -234,7 +234,7 @@ describe('SettingsService', () => {
|
||||
expect(notesEnabled()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('reports whether a sidebar item is hidden', () => {
|
||||
it('updates sidebar item visibility', () => {
|
||||
httpTestingController
|
||||
.expectOne(`${environment.apiBaseUrl}ui_settings/`)
|
||||
.flush(ui_settings)
|
||||
@@ -243,15 +243,19 @@ describe('SettingsService', () => {
|
||||
settingsService.sidebarItemIsHidden(HideableSidebarItemID.Workflows)
|
||||
).toBe(false)
|
||||
|
||||
settingsService.set(SETTINGS_KEYS.SIDEBAR_HIDDEN_ITEMS, [
|
||||
settingsService.updateSidebarItemVisibility(
|
||||
HideableSidebarItemID.Workflows,
|
||||
])
|
||||
false
|
||||
)
|
||||
|
||||
expect(
|
||||
settingsService.sidebarItemIsHidden(HideableSidebarItemID.Workflows)
|
||||
).toBe(true)
|
||||
|
||||
settingsService.set(SETTINGS_KEYS.SIDEBAR_HIDDEN_ITEMS, [])
|
||||
settingsService.updateSidebarItemVisibility(
|
||||
HideableSidebarItemID.Workflows,
|
||||
true
|
||||
)
|
||||
|
||||
expect(
|
||||
settingsService.sidebarItemIsHidden(HideableSidebarItemID.Workflows)
|
||||
|
||||
@@ -314,6 +314,7 @@ export class SettingsService {
|
||||
readonly globalDropzoneEnabled = signal(true)
|
||||
readonly globalDropzoneActive = signal(false)
|
||||
readonly organizingSidebarSavedViews = signal(false)
|
||||
readonly organizingSidebarItems = signal(false)
|
||||
readonly hiddenSidebarItems = this.getSignal<HideableSidebarItemID[]>(
|
||||
SETTINGS_KEYS.SIDEBAR_HIDDEN_ITEMS
|
||||
)
|
||||
@@ -757,6 +758,20 @@ export class SettingsService {
|
||||
return this.hiddenSidebarItems().includes(item)
|
||||
}
|
||||
|
||||
updateSidebarItemVisibility(
|
||||
item: HideableSidebarItemID,
|
||||
visible: boolean
|
||||
): Observable<any> {
|
||||
const hiddenItems = new Set(this.hiddenSidebarItems())
|
||||
if (visible) {
|
||||
hiddenItems.delete(item)
|
||||
} else {
|
||||
hiddenItems.add(item)
|
||||
}
|
||||
this.set(SETTINGS_KEYS.SIDEBAR_HIDDEN_ITEMS, [...hiddenItems])
|
||||
return this.storeSettings()
|
||||
}
|
||||
|
||||
updateSavedViewsVisibility(
|
||||
dashboardVisibleViewIds: number[],
|
||||
sidebarVisibleViewIds: number[]
|
||||
|
||||
Reference in New Issue
Block a user