diff --git a/src-ui/src/app/components/admin/settings/settings.component.html b/src-ui/src/app/components/admin/settings/settings.component.html index 305bb8a96..b520e8218 100644 --- a/src-ui/src/app/components/admin/settings/settings.component.html +++ b/src-ui/src/app/components/admin/settings/settings.component.html @@ -112,6 +112,22 @@ +

Sidebar items to show:

+ @for (option of sidebarItemOptions; track option.id) { +
+ + +
+ } + diff --git a/src-ui/src/app/components/admin/settings/settings.component.spec.ts b/src-ui/src/app/components/admin/settings/settings.component.spec.ts index 7b65281d0..71889e74b 100644 --- a/src-ui/src/app/components/admin/settings/settings.component.spec.ts +++ b/src-ui/src/app/components/admin/settings/settings.component.spec.ts @@ -24,7 +24,7 @@ import { SystemStatus, SystemStatusItemStatus, } from 'src/app/data/system-status' -import { SETTINGS_KEYS } from 'src/app/data/ui-settings' +import { HideableSidebarItemID, SETTINGS_KEYS } from 'src/app/data/ui-settings' import { IfOwnerDirective } from 'src/app/directives/if-owner.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { PermissionsGuard } from 'src/app/guards/permissions.guard' @@ -209,6 +209,45 @@ describe('SettingsComponent', () => { fixture.detectChanges() } + it('supports configuring sidebar items and canceling changes', () => { + completeSetup() + + component.toggleSidebarItem(HideableSidebarItemID.Workflows, false) + fixture.detectChanges() + + expect(component.settingsForm.value.sidebarHiddenItems).toContain( + HideableSidebarItemID.Workflows + ) + + settingsService.updateSidebarItemVisibility( + HideableSidebarItemID.Mail, + false + ) + + expect(component.settingsForm.value.sidebarHiddenItems).toContain( + HideableSidebarItemID.Mail + ) + + component.reset() + + expect(component.settingsForm.value.sidebarHiddenItems).not.toContain( + HideableSidebarItemID.Workflows + ) + expect(component.settingsForm.value.sidebarHiddenItems).not.toContain( + HideableSidebarItemID.Mail + ) + }) + + 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') @@ -249,6 +288,7 @@ describe('SettingsComponent', () => { it('should support save local settings updating appearance settings and calling API, show error', () => { completeSetup() + component.toggleSidebarItem(HideableSidebarItemID.Workflows, false) const toastErrorSpy = jest.spyOn(toastService, 'showError') const toastSpy = jest.spyOn(toastService, 'show') const storeSpy = jest.spyOn(settingsService, 'storeSettings') @@ -267,7 +307,10 @@ describe('SettingsComponent', () => { expect(toastErrorSpy).toHaveBeenCalled() expect(storeSpy).toHaveBeenCalled() expect(appearanceSettingsSpy).not.toHaveBeenCalled() - expect(setSpy).toHaveBeenCalledTimes(33) + expect(setSpy).toHaveBeenCalledTimes(34) + expect(setSpy).toHaveBeenCalledWith(SETTINGS_KEYS.SIDEBAR_HIDDEN_ITEMS, [ + HideableSidebarItemID.Workflows, + ]) // succeed storeSpy.mockReturnValueOnce(of(true)) diff --git a/src-ui/src/app/components/admin/settings/settings.component.ts b/src-ui/src/app/components/admin/settings/settings.component.ts index 218f6683c..9076d2a3e 100644 --- a/src-ui/src/app/components/admin/settings/settings.component.ts +++ b/src-ui/src/app/components/admin/settings/settings.component.ts @@ -39,7 +39,12 @@ import { SystemStatus, SystemStatusItemStatus, } from 'src/app/data/system-status' -import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings' +import { + GlobalSearchType, + HIDEABLE_SIDEBAR_ITEM_IDS, + HideableSidebarItemID, + SETTINGS_KEYS, +} from 'src/app/data/ui-settings' import { User } from 'src/app/data/user' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' @@ -102,6 +107,14 @@ const documentDetailFieldOptions = [ { id: DocumentDetailFieldID.Tags, label: $localize`Tags` }, ] +const sidebarItemLabels: Record = { + [HideableSidebarItemID.Dashboard]: $localize`Dashboard`, + [HideableSidebarItemID.SavedViews]: $localize`Saved Views`, + [HideableSidebarItemID.Workflows]: $localize`Workflows`, + [HideableSidebarItemID.Mail]: $localize`Mail`, + [HideableSidebarItemID.Documentation]: $localize`Documentation`, +} + @Component({ selector: 'pngx-settings', templateUrl: './settings.component.html', @@ -149,6 +162,7 @@ export class SettingsComponent bulkEditApplyOnClose: new FormControl(null), documentListItemPerPage: new FormControl(null), slimSidebarEnabled: new FormControl(null), + sidebarHiddenItems: new FormControl([]), darkModeUseSystem: new FormControl(null), darkModeEnabled: new FormControl(null), darkModeInvertThumbs: new FormControl(null), @@ -186,6 +200,7 @@ export class SettingsComponent store: BehaviorSubject storeSub: Subscription + sidebarItemsSub: Subscription isDirty$: Observable isDirty: boolean = false unsubscribeNotifier: Subject = new Subject() @@ -203,6 +218,10 @@ export class SettingsComponent public readonly PdfEditorEditMode = PdfEditorEditMode public readonly documentDetailFieldOptions = documentDetailFieldOptions + public readonly sidebarItemOptions = HIDEABLE_SIDEBAR_ITEM_IDS.map((id) => ({ + id, + label: sidebarItemLabels[id], + })) get systemStatusHasErrors(): boolean { const status = this.systemStatus() @@ -230,6 +249,10 @@ export class SettingsComponent constructor() { super() + this.sidebarItemsSub = + this.settings.sidebarHiddenItemsEditingChanged.subscribe((hiddenItems) => + this.settingsForm.controls.sidebarHiddenItems.setValue(hiddenItems) + ) this.settings.settingsSaved.subscribe(() => { if (!this.savePending) this.initialize() this.savedViewsService.maybeRefreshDocumentCounts() @@ -279,14 +302,21 @@ 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.sidebarHiddenItemsEditing.set( + navID === SettingsNavIDs.General + ? [...this.settingsForm.controls.sidebarHiddenItems.value] + : null + ) }) } @@ -310,6 +340,7 @@ export class SettingsComponent SETTINGS_KEYS.DOCUMENT_LIST_SIZE ), slimSidebarEnabled: this.settings.get(SETTINGS_KEYS.SLIM_SIDEBAR), + sidebarHiddenItems: this.settings.get(SETTINGS_KEYS.SIDEBAR_HIDDEN_ITEMS), darkModeUseSystem: this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM), darkModeEnabled: this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED), darkModeInvertThumbs: this.settings.get( @@ -436,6 +467,12 @@ export class SettingsComponent this.settingsForm.patchValue(currentFormValue) } + if (this.settings.organizingSidebarItems()) { + this.settings.sidebarHiddenItemsEditing.set([ + ...this.settingsForm.controls.sidebarHiddenItems.value, + ]) + } + if (this.canViewSystemStatus) { this.systemStatusService.get().subscribe((status) => { this.systemStatus.set(status) @@ -444,8 +481,18 @@ export class SettingsComponent } ngOnDestroy() { + this.settings.sidebarHiddenItemsEditing.set(null) if (this.isDirty) this.settings.updateAppearanceSettings() // in case user changed appearance but didn't save this.storeSub && this.storeSub.unsubscribe() + this.sidebarItemsSub.unsubscribe() + } + + isSidebarItemShown(item: HideableSidebarItemID): boolean { + return !(this.settingsForm.value.sidebarHiddenItems || []).includes(item) + } + + toggleSidebarItem(item: HideableSidebarItemID, checked: boolean): void { + this.settings.updateSidebarItemVisibility(item, checked) } public saveSettings() { @@ -473,6 +520,10 @@ export class SettingsComponent SETTINGS_KEYS.SLIM_SIDEBAR, this.settingsForm.value.slimSidebarEnabled ) + this.settings.set( + SETTINGS_KEYS.SIDEBAR_HIDDEN_ITEMS, + this.settingsForm.value.sidebarHiddenItems + ) this.settings.set( SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, this.settingsForm.value.darkModeUseSystem @@ -632,6 +683,11 @@ export class SettingsComponent reset() { this.settingsForm.patchValue(this.store.getValue()) + if (this.settings.organizingSidebarItems()) { + this.settings.sidebarHiddenItemsEditing.set([ + ...this.settingsForm.controls.sidebarHiddenItems.value, + ]) + } } clearThemeColor() { diff --git a/src-ui/src/app/components/app-frame/app-frame.component.html b/src-ui/src/app/components/app-frame/app-frame.component.html index 6faddbb8a..a8dd12d34 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.html +++ b/src-ui/src/app/components/app-frame/app-frame.component.html @@ -86,12 +86,15 @@ }