mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-08-14 06:43:18 +00:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35ea1c38c5 | ||
|
|
01c12d9ea4 | ||
|
|
f5c0d118f7 | ||
|
|
ff13847d0a | ||
|
|
634f803872 | ||
|
|
639d566a7c | ||
|
|
0a94f8f0d4 | ||
|
|
2a8579f610 | ||
|
|
4789fe9a52 | ||
|
|
e150c8c7c0 | ||
|
|
879cd4a30a | ||
|
|
6a02b87dde | ||
|
|
59a2651804 | ||
|
|
a99f63e059 | ||
|
|
939cb52f6e | ||
|
|
855669ddf9 |
@@ -299,6 +299,8 @@ optional arguments:
|
|||||||
-sm, --split-manifest
|
-sm, --split-manifest
|
||||||
-z, --zip
|
-z, --zip
|
||||||
-zn, --zip-name
|
-zn, --zip-name
|
||||||
|
--zip-compression
|
||||||
|
--zip-compression-level
|
||||||
--data-only
|
--data-only
|
||||||
--no-progress-bar
|
--no-progress-bar
|
||||||
--passphrase
|
--passphrase
|
||||||
@@ -361,6 +363,19 @@ If `-z` or `--zip` is provided, the export will be a zip file
|
|||||||
in the target directory, named according to the current local date or the
|
in the target directory, named according to the current local date or the
|
||||||
value set in `-zn` or `--zip-name`.
|
value set in `-zn` or `--zip-name`.
|
||||||
|
|
||||||
|
The compression method for the zip can be set with `--zip-compression`
|
||||||
|
(`stored`, `deflated` (default), `bzip2`, `lzma`, or `zstd`) and tuned with
|
||||||
|
`--zip-compression-level` (deflated: 0–9, bzip2: 1–9, zstd: -22–22; ignored
|
||||||
|
for `stored` and `lzma`). Both options require `--zip`.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
|
||||||
|
`zstd` compression requires Python 3.14 or newer on **both** the machine
|
||||||
|
creating the export and any machine importing it. An archive compressed with
|
||||||
|
`zstd` (or `lzma`/`bzip2` where those modules are unavailable) cannot be
|
||||||
|
imported on a runtime that lacks the codec; the importer will refuse it with
|
||||||
|
a clear error. The default `deflated` is universally readable.
|
||||||
|
|
||||||
If `--data-only` is provided, only the database will be exported. This option is intended
|
If `--data-only` is provided, only the database will be exported. This option is intended
|
||||||
to facilitate database upgrades without needing to clean documents and thumbnails from the media directory.
|
to facilitate database upgrades without needing to clean documents and thumbnails from the media directory.
|
||||||
|
|
||||||
@@ -699,6 +714,7 @@ document_fuzzy_match [--ratio] [--processes N]
|
|||||||
| --ratio | No | 85.0 | a number between 0 and 100, setting how similar a document must be for it to be reported. Higher numbers mean more similarity. |
|
| --ratio | No | 85.0 | a number between 0 and 100, setting how similar a document must be for it to be reported. Higher numbers mean more similarity. |
|
||||||
| --processes | No | 1/4 of system cores | Number of processes to use for matching. Setting 1 disables multiple processes |
|
| --processes | No | 1/4 of system cores | Number of processes to use for matching. Setting 1 disables multiple processes |
|
||||||
| --delete | No | False | If provided, one document of a matched pair above the ratio will be deleted. |
|
| --delete | No | False | If provided, one document of a matched pair above the ratio will be deleted. |
|
||||||
|
| --url | No | blank | If an instance URL is provided, the output table will show URLs to each documents instead of the document ID and name. |
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
|
|
||||||
|
|||||||
@@ -948,10 +948,11 @@ for display in the web interface.
|
|||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
The **remote OCR parser** (Azure AI) always produces a searchable
|
The **remote OCR parser** (Azure AI) also honors this setting: when
|
||||||
PDF and stores it as the archive copy, regardless of this setting.
|
no archive is requested (`never`, or `auto` with a born-digital PDF),
|
||||||
`ARCHIVE_FILE_GENERATION=never` has no effect when the remote
|
the remote engine is skipped entirely and locally-extracted text is
|
||||||
parser handles a document.
|
used instead, avoiding an unnecessary API call and a duplicate text
|
||||||
|
layer.
|
||||||
|
|
||||||
#### [`PAPERLESS_OCR_CLEAN=<mode>`](#PAPERLESS_OCR_CLEAN) {#PAPERLESS_OCR_CLEAN}
|
#### [`PAPERLESS_OCR_CLEAN=<mode>`](#PAPERLESS_OCR_CLEAN) {#PAPERLESS_OCR_CLEAN}
|
||||||
|
|
||||||
|
|||||||
@@ -187,10 +187,11 @@ PAPERLESS_ARCHIVE_FILE_GENERATION=auto
|
|||||||
|
|
||||||
### Remote OCR parser
|
### Remote OCR parser
|
||||||
|
|
||||||
If you use the **remote OCR parser** (Azure AI), note that it always produces a
|
If you use the **remote OCR parser** (Azure AI), `ARCHIVE_FILE_GENERATION` is
|
||||||
searchable PDF and stores it as the archive copy. `ARCHIVE_FILE_GENERATION=never`
|
honored the same way as for the local engine: when no archive is requested
|
||||||
has no effect for documents handled by the remote parser - the archive is produced
|
(`never`, or `auto` with a born-digital PDF), the remote engine is skipped
|
||||||
unconditionally by the remote engine.
|
entirely and locally-extracted text is used instead, avoiding an unnecessary
|
||||||
|
API call and a duplicate text layer.
|
||||||
|
|
||||||
## Search Index (Whoosh -> Tantivy)
|
## Search Index (Whoosh -> Tantivy)
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -576,7 +576,9 @@ The following workflow action types are available:
|
|||||||
- Tags, correspondent, document type and storage path
|
- Tags, correspondent, document type and storage path
|
||||||
- Document owner
|
- Document owner
|
||||||
- View and / or edit permissions to users or groups
|
- View and / or edit permissions to users or groups
|
||||||
- Custom fields. Note that no value for the field will be set
|
- Custom fields, optionally with a value. If no value is set, the field is only added to the
|
||||||
|
document and any value it may already have is left untouched. If a value is set, it will
|
||||||
|
overwrite an existing value of that field on the document.
|
||||||
|
|
||||||
##### Removal {#workflow-action-removal}
|
##### Removal {#workflow-action-removal}
|
||||||
|
|
||||||
|
|||||||
+544
-152
File diff suppressed because it is too large
Load Diff
@@ -576,7 +576,7 @@ describe('TasksComponent', () => {
|
|||||||
|
|
||||||
expect(dismissSpy).toHaveBeenCalledWith(new Set([tasks[0].id, tasks[1].id]))
|
expect(dismissSpy).toHaveBeenCalledWith(new Set([tasks[0].id, tasks[1].id]))
|
||||||
expect(toastSpy).toHaveBeenCalledWith('Error dismissing tasks', error)
|
expect(toastSpy).toHaveBeenCalledWith('Error dismissing tasks', error)
|
||||||
expect(modal.componentInstance.buttonsEnabled).toBe(true)
|
expect(modal.componentInstance.buttonsEnabled()).toBe(true)
|
||||||
expect(component.selectedTasks.size).toBe(0)
|
expect(component.selectedTasks.size).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -642,7 +642,7 @@ describe('TasksComponent', () => {
|
|||||||
|
|
||||||
expect(dismissSpy).toHaveBeenCalled()
|
expect(dismissSpy).toHaveBeenCalled()
|
||||||
expect(toastSpy).toHaveBeenCalledWith('Error dismissing tasks', error)
|
expect(toastSpy).toHaveBeenCalledWith('Error dismissing tasks', error)
|
||||||
expect(modal.componentInstance.buttonsEnabled).toBe(true)
|
expect(modal.componentInstance.buttonsEnabled()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should dismiss the currently visible scoped and filtered tasks', () => {
|
it('should dismiss the currently visible scoped and filtered tasks', () => {
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ export class TasksComponent
|
|||||||
modal.componentInstance.btnClass = 'btn-warning'
|
modal.componentInstance.btnClass = 'btn-warning'
|
||||||
modal.componentInstance.btnCaption = $localize`Dismiss`
|
modal.componentInstance.btnCaption = $localize`Dismiss`
|
||||||
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
modal.close()
|
modal.close()
|
||||||
this.tasksService.dismissTasks(tasks).subscribe({
|
this.tasksService.dismissTasks(tasks).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
@@ -324,7 +324,7 @@ export class TasksComponent
|
|||||||
},
|
},
|
||||||
error: (e) => {
|
error: (e) => {
|
||||||
this.toastService.showError($localize`Error dismissing tasks`, e)
|
this.toastService.showError($localize`Error dismissing tasks`, e)
|
||||||
modal.componentInstance.buttonsEnabled = true
|
modal.componentInstance.buttonsEnabled.set(true)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
this.clearSelection()
|
this.clearSelection()
|
||||||
@@ -350,7 +350,7 @@ export class TasksComponent
|
|||||||
modal.componentInstance.btnClass = 'btn-warning'
|
modal.componentInstance.btnClass = 'btn-warning'
|
||||||
modal.componentInstance.btnCaption = $localize`Dismiss`
|
modal.componentInstance.btnCaption = $localize`Dismiss`
|
||||||
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
modal.close()
|
modal.close()
|
||||||
this.tasksService.dismissAllTasks().subscribe({
|
this.tasksService.dismissAllTasks().subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
@@ -358,7 +358,7 @@ export class TasksComponent
|
|||||||
},
|
},
|
||||||
error: (e) => {
|
error: (e) => {
|
||||||
this.toastService.showError($localize`Error dismissing tasks`, e)
|
this.toastService.showError($localize`Error dismissing tasks`, e)
|
||||||
modal.componentInstance.buttonsEnabled = true
|
modal.componentInstance.buttonsEnabled.set(true)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
this.clearSelection()
|
this.clearSelection()
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export class TrashComponent
|
|||||||
modal.componentInstance.confirmClicked
|
modal.componentInstance.confirmClicked
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.trashService.emptyTrash([document.id]).subscribe({
|
this.trashService.emptyTrash([document.id]).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.toastService.showInfo(
|
this.toastService.showInfo(
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export class UsersAndGroupsComponent
|
|||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.usersService.delete(user).subscribe({
|
this.usersService.delete(user).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
modal.close()
|
modal.close()
|
||||||
@@ -199,7 +199,7 @@ export class UsersAndGroupsComponent
|
|||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.groupsService.delete(group).subscribe({
|
this.groupsService.delete(group).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
modal.close()
|
modal.close()
|
||||||
|
|||||||
@@ -111,7 +111,7 @@
|
|||||||
routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name"
|
routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name"
|
||||||
[disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave"
|
[disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave"
|
||||||
popoverClass="popover-slim">
|
popoverClass="popover-slim">
|
||||||
<i-bs class="me-2" name="funnel"></i-bs><span><div class="d-inline-flex view-name"><span class="overflow-hidden" [class.text-wrap]="!slimSidebarEnabled">{{view.name}}</span></div>
|
<i-bs class="me-2" [name]="view.icon || 'funnel'"></i-bs><span><div class="d-inline-flex view-name"><span class="overflow-hidden" [class.text-wrap]="!slimSidebarEnabled">{{view.name}}</span></div>
|
||||||
@if (showSidebarCounts && !slimSidebarEnabled) {
|
@if (showSidebarCounts && !slimSidebarEnabled) {
|
||||||
<span class="badge bg-info text-dark ms-2 d-inline">{{ savedViewService.getDocumentCount(view) }}</span>
|
<span class="badge bg-info text-dark ms-2 d-inline">{{ savedViewService.getDocumentCount(view) }}</span>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,11 +47,12 @@
|
|||||||
|
|
||||||
.search-container {
|
.search-container {
|
||||||
max-height: 4.5rem;
|
max-height: 4.5rem;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
transition: max-height .2s ease, opacity .2s ease, padding-top .2s ease, padding-bottom .2s ease;
|
transition: max-height .2s ease, opacity .2s ease, padding-top .2s ease, padding-bottom .2s ease;
|
||||||
|
|
||||||
&.mobile-hidden {
|
&.mobile-hidden {
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
padding-bottom: 0 !important;
|
padding-bottom: 0 !important;
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
|
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled()">
|
||||||
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
|
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled()">
|
||||||
<span>
|
<span>
|
||||||
{{btnCaption}}
|
{{btnCaption}}
|
||||||
<span class="visually-hidden">{{ seconds | number: '1.0-0' }} seconds</span>
|
<span class="visually-hidden">{{ seconds | number: '1.0-0' }} seconds</span>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
@if (alternativeBtnCaption) {
|
@if (alternativeBtnCaption) {
|
||||||
<button type="button" class="btn" [class]="alternativeBtnClass" (click)="alternative()" [disabled]="!alternativeButtonEnabled || !buttonsEnabled">
|
<button type="button" class="btn" [class]="alternativeBtnClass" (click)="alternative()" [disabled]="!alternativeButtonEnabled || !buttonsEnabled()">
|
||||||
{{alternativeBtnCaption}}
|
{{alternativeBtnCaption}}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,22 @@ describe('ConfirmDialogComponent', () => {
|
|||||||
expect(confirmSubjectResult).toBeTruthy()
|
expect(confirmSubjectResult).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should re-render the buttons when they are toggled from outside', async () => {
|
||||||
|
const confirmButton: HTMLButtonElement =
|
||||||
|
fixture.nativeElement.querySelectorAll('.modal-footer button')[1]
|
||||||
|
expect(confirmButton.disabled).toBeFalsy()
|
||||||
|
|
||||||
|
// Deliberately no detectChanges: a request callback toggling this is all
|
||||||
|
// that happens, and nothing else schedules a render for the modal
|
||||||
|
component.buttonsEnabled.set(false)
|
||||||
|
await fixture.whenStable()
|
||||||
|
expect(confirmButton.disabled).toBeTruthy()
|
||||||
|
|
||||||
|
component.buttonsEnabled.set(true)
|
||||||
|
await fixture.whenStable()
|
||||||
|
expect(confirmButton.disabled).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
it('should support cancel & close modal', () => {
|
it('should support cancel & close modal', () => {
|
||||||
let confirmSubjectResult
|
let confirmSubjectResult
|
||||||
const closeModalSpy = jest.spyOn(modal, 'close')
|
const closeModalSpy = jest.spyOn(modal, 'close')
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { DecimalPipe } from '@angular/common'
|
import { DecimalPipe } from '@angular/common'
|
||||||
import { Component, EventEmitter, Input, Output, inject } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
|
} from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
||||||
@@ -46,8 +53,7 @@ export class ConfirmDialogComponent extends LoadingComponentWithPermissions {
|
|||||||
@Input()
|
@Input()
|
||||||
cancelBtnCaption = $localize`Cancel`
|
cancelBtnCaption = $localize`Cancel`
|
||||||
|
|
||||||
@Input()
|
readonly buttonsEnabled = signal(true)
|
||||||
buttonsEnabled = true
|
|
||||||
|
|
||||||
confirmButtonEnabled = true
|
confirmButtonEnabled = true
|
||||||
alternativeButtonEnabled = true
|
alternativeButtonEnabled = true
|
||||||
|
|||||||
+2
-2
@@ -56,10 +56,10 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
|
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled()">
|
||||||
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
|
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled()">
|
||||||
{{btnCaption}}
|
{{btnCaption}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-2
@@ -57,7 +57,7 @@
|
|||||||
class="btn"
|
class="btn"
|
||||||
[class]="cancelBtnClass"
|
[class]="cancelBtnClass"
|
||||||
(click)="cancel()"
|
(click)="cancel()"
|
||||||
[disabled]="!buttonsEnabled"
|
[disabled]="!buttonsEnabled()"
|
||||||
>
|
>
|
||||||
<span class="d-inline-block" style="padding-bottom: 1px;">
|
<span class="d-inline-block" style="padding-bottom: 1px;">
|
||||||
{{cancelBtnCaption}}
|
{{cancelBtnCaption}}
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
class="btn"
|
class="btn"
|
||||||
[class]="btnClass"
|
[class]="btnClass"
|
||||||
(click)="confirm()"
|
(click)="confirm()"
|
||||||
[disabled]="!confirmButtonEnabled || !buttonsEnabled"
|
[disabled]="!confirmButtonEnabled || !buttonsEnabled()"
|
||||||
>
|
>
|
||||||
{{btnCaption}}
|
{{btnCaption}}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+2
-2
@@ -34,10 +34,10 @@
|
|||||||
<p class="mb-0 small"><b>{{messageBold}}</b></p>
|
<p class="mb-0 small"><b>{{messageBold}}</b></p>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
|
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled()">
|
||||||
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled || degrees === 0">
|
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled() || degrees === 0">
|
||||||
{{btnCaption}}
|
{{btnCaption}}
|
||||||
@if (!confirmButtonEnabled) {
|
@if (!confirmButtonEnabled) {
|
||||||
<ngb-progressbar style="height: 1px;" type="dark" [max]="secondsTotal" [value]="seconds"></ngb-progressbar>
|
<ngb-progressbar style="height: 1px;" type="dark" [max]="secondsTotal" [value]="seconds"></ngb-progressbar>
|
||||||
|
|||||||
+4
-4
@@ -52,10 +52,10 @@ describe('CustomFieldsValuesComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should set selectedFields and map values correctly', () => {
|
it('should set selectedFields and map values correctly', () => {
|
||||||
component.value = { 1: 'value1' }
|
component.value = { 1: 'value1', 3: 0, 4: false }
|
||||||
component.selectedFields = [1, 2]
|
component.selectedFields = [1, 2, 3, 4]
|
||||||
expect(component.selectedFields).toEqual([1, 2])
|
expect(component.selectedFields).toEqual([1, 2, 3, 4])
|
||||||
expect(component.value).toEqual({ 1: 'value1', 2: null })
|
expect(component.value).toEqual({ 1: 'value1', 2: null, 3: 0, 4: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return the correct custom field by id', () => {
|
it('should return the correct custom field by id', () => {
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ export class CustomFieldsValuesComponent extends AbstractInputComponent<Object>
|
|||||||
this._selectedFields = newFields
|
this._selectedFields = newFields
|
||||||
// map the selected fields to an object with field_id as key and value as value
|
// map the selected fields to an object with field_id as key and value as value
|
||||||
this.value = newFields.reduce((acc, fieldId) => {
|
this.value = newFields.reduce((acc, fieldId) => {
|
||||||
acc[fieldId] = this.value?.[fieldId] || null
|
acc[fieldId] = this.value?.[fieldId] ?? null
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
this.onChange(this.value)
|
this.onChange(this.value)
|
||||||
|
|||||||
@@ -36,7 +36,16 @@
|
|||||||
(focus)="clearLastSearchTerm()"
|
(focus)="clearLastSearchTerm()"
|
||||||
(clear)="clearLastSearchTerm()"
|
(clear)="clearLastSearchTerm()"
|
||||||
(blur)="onBlur()">
|
(blur)="onBlur()">
|
||||||
|
<ng-template ng-label-tmp let-item="item">
|
||||||
|
@if (iconField && item[iconField]) {
|
||||||
|
<i-bs class="me-2" [name]="item[iconField]"></i-bs>
|
||||||
|
}
|
||||||
|
<span [title]="item[bindLabel]">{{item[bindLabel]}}</span>
|
||||||
|
</ng-template>
|
||||||
<ng-template ng-option-tmp let-item="item">
|
<ng-template ng-option-tmp let-item="item">
|
||||||
|
@if (iconField && item[iconField]) {
|
||||||
|
<i-bs class="me-2" [name]="item[iconField]"></i-bs>
|
||||||
|
}
|
||||||
<span [title]="item[bindLabel]">{{item[bindLabel]}}</span>
|
<span [title]="item[bindLabel]">{{item[bindLabel]}}</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-select>
|
</ng-select>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { AbstractInputComponent } from '../abstract-input'
|
|||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class SelectComponent extends AbstractInputComponent<number> {
|
export class SelectComponent extends AbstractInputComponent<number | string> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.addItemRef = this.addItem.bind(this)
|
this.addItemRef = this.addItem.bind(this)
|
||||||
@@ -100,6 +100,9 @@ export class SelectComponent extends AbstractInputComponent<number> {
|
|||||||
@Input()
|
@Input()
|
||||||
bindLabel: string = 'name'
|
bindLabel: string = 'name'
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
iconField: string
|
||||||
|
|
||||||
public searchFn = (term: string, item: any): boolean =>
|
public searchFn = (term: string, item: any): boolean =>
|
||||||
matchesSearchText(item?.[this.bindLabel], term)
|
matchesSearchText(item?.[this.bindLabel], term)
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="form-group ms-md-auto">
|
<div class="form-group ms-md-auto">
|
||||||
<button type="button" class="btn me-2" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">{{ cancelBtnCaption }}</button>
|
<button type="button" class="btn me-2" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled()">{{ cancelBtnCaption }}</button>
|
||||||
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="pages.length === 0">{{ btnCaption }}</button>
|
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="pages.length === 0">{{ btnCaption }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+9
@@ -17,6 +17,10 @@ const permissions = [
|
|||||||
'view_document',
|
'view_document',
|
||||||
'change_document',
|
'change_document',
|
||||||
'delete_document',
|
'delete_document',
|
||||||
|
'add_sharelinkbundle',
|
||||||
|
'view_sharelinkbundle',
|
||||||
|
'change_sharelinkbundle',
|
||||||
|
'delete_sharelinkbundle',
|
||||||
'change_tag',
|
'change_tag',
|
||||||
'view_documenttype',
|
'view_documenttype',
|
||||||
]
|
]
|
||||||
@@ -75,6 +79,7 @@ describe('PermissionsSelectComponent', () => {
|
|||||||
component.ngOnInit()
|
component.ngOnInit()
|
||||||
component.writeValue(permissions)
|
component.writeValue(permissions)
|
||||||
expect(component.typesWithAllActions).toContain('Document')
|
expect(component.typesWithAllActions).toContain('Document')
|
||||||
|
expect(component.typesWithAllActions).toContain('ShareLinkBundle')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should update checkboxes on permissions set', () => {
|
it('should update checkboxes on permissions set', () => {
|
||||||
@@ -85,6 +90,10 @@ describe('PermissionsSelectComponent', () => {
|
|||||||
expect(input1.nativeElement.checked).toBeTruthy()
|
expect(input1.nativeElement.checked).toBeTruthy()
|
||||||
const input2 = fixture.debugElement.query(By.css('input#Tag_Change'))
|
const input2 = fixture.debugElement.query(By.css('input#Tag_Change'))
|
||||||
expect(input2.nativeElement.checked).toBeTruthy()
|
expect(input2.nativeElement.checked).toBeTruthy()
|
||||||
|
const bundleInput = fixture.debugElement.query(
|
||||||
|
By.css('input#ShareLinkBundle_Add')
|
||||||
|
)
|
||||||
|
expect(bundleInput.nativeElement.checked).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('disable checkboxes when permissions are inherited', () => {
|
it('disable checkboxes when permissions are inherited', () => {
|
||||||
|
|||||||
+1
-1
@@ -119,7 +119,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm d-inline-flex align-items-center gap-2 text-nowrap"
|
class="btn btn-primary btn-sm d-inline-flex align-items-center gap-2 text-nowrap"
|
||||||
(click)="submit()"
|
(click)="submit()"
|
||||||
[disabled]="loading() || !buttonsEnabled">
|
[disabled]="loading() || !buttonsEnabled()">
|
||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -69,7 +69,7 @@ describe('ShareLinkBundleDialogComponent', () => {
|
|||||||
file_version: FileVersion.Original,
|
file_version: FileVersion.Original,
|
||||||
expiration_days: 3,
|
expiration_days: 3,
|
||||||
})
|
})
|
||||||
expect(component.buttonsEnabled).toBe(false)
|
expect(component.buttonsEnabled()).toBe(false)
|
||||||
expect(confirmSpy).toHaveBeenCalled()
|
expect(confirmSpy).toHaveBeenCalled()
|
||||||
|
|
||||||
component.form.setValue({
|
component.form.setValue({
|
||||||
|
|||||||
+1
-1
@@ -78,7 +78,7 @@ export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent {
|
|||||||
: FileVersion.Original,
|
: FileVersion.Original,
|
||||||
expiration_days: this.form.value.expirationDays,
|
expiration_days: this.form.value.expirationDays,
|
||||||
}
|
}
|
||||||
this.buttonsEnabled = false
|
this.buttonsEnabled.set(false)
|
||||||
super.confirm()
|
super.confirm()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -1,6 +1,7 @@
|
|||||||
<pngx-widget-frame
|
<pngx-widget-frame
|
||||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }"
|
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }"
|
||||||
[title]="savedView.name"
|
[title]="savedView.name"
|
||||||
|
[titleIcon]="savedView.icon || 'funnel'"
|
||||||
[loading]="false"
|
[loading]="false"
|
||||||
[draggable]="savedView"
|
[draggable]="savedView"
|
||||||
>
|
>
|
||||||
|
|||||||
+6
-1
@@ -8,7 +8,12 @@
|
|||||||
<i-bs name="grip-vertical"></i-bs>
|
<i-bs name="grip-vertical"></i-bs>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<h6 class="card-title mb-0">{{title()}}</h6>
|
<h6 class="card-title mb-0">
|
||||||
|
@if (titleIcon()) {
|
||||||
|
<i-bs class="me-2" [name]="titleIcon()"></i-bs>
|
||||||
|
}
|
||||||
|
{{title()}}
|
||||||
|
</h6>
|
||||||
<ng-content select="[title-badge]"></ng-content>
|
<ng-content select="[title-badge]"></ng-content>
|
||||||
@if (badge() !== null && badge() !== undefined) {
|
@if (badge() !== null && badge() !== undefined) {
|
||||||
<span class="badge bg-info text-dark ms-2">{{badge()}}</span>
|
<span class="badge bg-info text-dark ms-2">{{badge()}}</span>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export class WidgetFrameComponent implements AfterViewInit {
|
|||||||
|
|
||||||
title = input<string>()
|
title = input<string>()
|
||||||
|
|
||||||
|
titleIcon = input<string>()
|
||||||
|
|
||||||
draggable = input<any>()
|
draggable = input<any>()
|
||||||
|
|
||||||
cardless = input(false)
|
cardless = input(false)
|
||||||
|
|||||||
@@ -1564,7 +1564,7 @@ describe('DocumentDetailComponent', () => {
|
|||||||
dialog.confirmClicked.next()
|
dialog.confirmClicked.next()
|
||||||
await openModal.result
|
await openModal.result
|
||||||
|
|
||||||
expect(dialog.buttonsEnabled).toBe(false)
|
expect(dialog.buttonsEnabled()).toBe(false)
|
||||||
expect(reloadSpy).toHaveBeenCalled()
|
expect(reloadSpy).toHaveBeenCalled()
|
||||||
expect((component as any).incomingUpdateModal).toBeNull()
|
expect((component as any).incomingUpdateModal).toBeNull()
|
||||||
})
|
})
|
||||||
@@ -1789,7 +1789,7 @@ describe('DocumentDetailComponent', () => {
|
|||||||
|
|
||||||
expect(errorSpy).toHaveBeenCalled()
|
expect(errorSpy).toHaveBeenCalled()
|
||||||
expect(component.networkActive()).toBe(false)
|
expect(component.networkActive()).toBe(false)
|
||||||
expect(dialog.buttonsEnabled).toBe(true)
|
expect(dialog.buttonsEnabled()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should refresh the document when removing password in update mode', () => {
|
it('should refresh the document when removing password in update mode', () => {
|
||||||
|
|||||||
@@ -659,7 +659,7 @@ export class DocumentDetailComponent
|
|||||||
modal.componentInstance.cancelBtnCaption = $localize`Dismiss`
|
modal.componentInstance.cancelBtnCaption = $localize`Dismiss`
|
||||||
|
|
||||||
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
modal.close()
|
modal.close()
|
||||||
this.reloadRemoteVersion()
|
this.reloadRemoteVersion()
|
||||||
})
|
})
|
||||||
@@ -1374,7 +1374,7 @@ export class DocumentDetailComponent
|
|||||||
modal.componentInstance.confirmClicked
|
modal.componentInstance.confirmClicked
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
return this.documentsService.delete(this.document())
|
return this.documentsService.delete(this.document())
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1386,7 +1386,7 @@ export class DocumentDetailComponent
|
|||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
this.toastService.showError($localize`Error deleting document`, error)
|
this.toastService.showError($localize`Error deleting document`, error)
|
||||||
modal.componentInstance.buttonsEnabled = true
|
modal.componentInstance.buttonsEnabled.set(true)
|
||||||
this.subscribeModalDelete(modal)
|
this.subscribeModalDelete(modal)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1411,7 +1411,7 @@ export class DocumentDetailComponent
|
|||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.documentsService
|
this.documentsService
|
||||||
.reprocessDocuments({ documents: [this.document().id] })
|
.reprocessDocuments({ documents: [this.document().id] })
|
||||||
.subscribe({
|
.subscribe({
|
||||||
@@ -1425,7 +1425,7 @@ export class DocumentDetailComponent
|
|||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.componentInstance.buttonsEnabled = true
|
modal.componentInstance.buttonsEnabled.set(true)
|
||||||
}
|
}
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
$localize`Error executing operation`,
|
$localize`Error executing operation`,
|
||||||
@@ -1798,7 +1798,7 @@ export class DocumentDetailComponent
|
|||||||
modal.componentInstance.confirmClicked
|
modal.componentInstance.confirmClicked
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.documentsService
|
this.documentsService
|
||||||
.editPdfDocuments([sourceDocumentId], {
|
.editPdfDocuments([sourceDocumentId], {
|
||||||
operations: modal.componentInstance.getOperations(),
|
operations: modal.componentInstance.getOperations(),
|
||||||
@@ -1821,7 +1821,7 @@ export class DocumentDetailComponent
|
|||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.componentInstance.buttonsEnabled = true
|
modal.componentInstance.buttonsEnabled.set(true)
|
||||||
}
|
}
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
$localize`Error executing PDF edit operation`,
|
$localize`Error executing PDF edit operation`,
|
||||||
@@ -1855,7 +1855,7 @@ export class DocumentDetailComponent
|
|||||||
const sourceDocumentId = this.selectedVersionId() ?? this.document().id
|
const sourceDocumentId = this.selectedVersionId() ?? this.document().id
|
||||||
const dialog =
|
const dialog =
|
||||||
modal.componentInstance as PasswordRemovalConfirmDialogComponent
|
modal.componentInstance as PasswordRemovalConfirmDialogComponent
|
||||||
dialog.buttonsEnabled = false
|
dialog.buttonsEnabled.set(false)
|
||||||
this.networkActive.set(true)
|
this.networkActive.set(true)
|
||||||
this.documentsService
|
this.documentsService
|
||||||
.removePasswordDocuments([sourceDocumentId], {
|
.removePasswordDocuments([sourceDocumentId], {
|
||||||
@@ -1880,7 +1880,7 @@ export class DocumentDetailComponent
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
dialog.buttonsEnabled = true
|
dialog.buttonsEnabled.set(true)
|
||||||
this.networkActive.set(false)
|
this.networkActive.set(false)
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
$localize`Error executing password removal operation`,
|
$localize`Error executing password removal operation`,
|
||||||
|
|||||||
@@ -1683,7 +1683,7 @@ describe('BulkEditorComponent', () => {
|
|||||||
expiration_days: 7,
|
expiration_days: 7,
|
||||||
},
|
},
|
||||||
loading: signal(false),
|
loading: signal(false),
|
||||||
buttonsEnabled: true,
|
buttonsEnabled: signal(true),
|
||||||
copied: signal(false),
|
copied: signal(false),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1715,7 +1715,7 @@ describe('BulkEditorComponent', () => {
|
|||||||
expiration_days: 7,
|
expiration_days: 7,
|
||||||
})
|
})
|
||||||
expect(dialogInstance.loading()).toBe(false)
|
expect(dialogInstance.loading()).toBe(false)
|
||||||
expect(dialogInstance.buttonsEnabled).toBe(false)
|
expect(dialogInstance.buttonsEnabled()).toBe(false)
|
||||||
expect(dialogInstance.createdBundle).toEqual({ id: 42 })
|
expect(dialogInstance.createdBundle).toEqual({ id: 42 })
|
||||||
expect(typeof dialogInstance.onOpenManage).toBe('function')
|
expect(typeof dialogInstance.onOpenManage).toBe('function')
|
||||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||||
@@ -1755,7 +1755,7 @@ describe('BulkEditorComponent', () => {
|
|||||||
expiration_days: null,
|
expiration_days: null,
|
||||||
},
|
},
|
||||||
loading: signal(false),
|
loading: signal(false),
|
||||||
buttonsEnabled: true,
|
buttonsEnabled: signal(true),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1777,7 +1777,7 @@ describe('BulkEditorComponent', () => {
|
|||||||
expect.any(Error)
|
expect.any(Error)
|
||||||
)
|
)
|
||||||
expect(dialogInstance.loading()).toBe(false)
|
expect(dialogInstance.loading()).toBe(false)
|
||||||
expect(dialogInstance.buttonsEnabled).toBe(true)
|
expect(dialogInstance.buttonsEnabled()).toBe(true)
|
||||||
openSpy.mockRestore()
|
openSpy.mockRestore()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ export class BulkEditorComponent
|
|||||||
overrideSelection?: DocumentSelectionQuery
|
overrideSelection?: DocumentSelectionQuery
|
||||||
) {
|
) {
|
||||||
if (modal) {
|
if (modal) {
|
||||||
this.setModalButtonsEnabled(modal, false)
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
}
|
}
|
||||||
this.documentService
|
this.documentService
|
||||||
.bulkEdit(overrideSelection ?? this.getSelectionQuery(), method, args)
|
.bulkEdit(overrideSelection ?? this.getSelectionQuery(), method, args)
|
||||||
@@ -290,7 +290,7 @@ export class BulkEditorComponent
|
|||||||
options: { deleteOriginals?: boolean } = {}
|
options: { deleteOriginals?: boolean } = {}
|
||||||
) {
|
) {
|
||||||
if (modal) {
|
if (modal) {
|
||||||
this.setModalButtonsEnabled(modal, false)
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
}
|
}
|
||||||
request.pipe(first()).subscribe({
|
request.pipe(first()).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
@@ -320,7 +320,7 @@ export class BulkEditorComponent
|
|||||||
|
|
||||||
private handleOperationError(modal: NgbModalRef, error: any) {
|
private handleOperationError(modal: NgbModalRef, error: any) {
|
||||||
if (modal) {
|
if (modal) {
|
||||||
this.setModalButtonsEnabled(modal, true)
|
modal.componentInstance.buttonsEnabled.set(true)
|
||||||
}
|
}
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
$localize`Error executing bulk operation`,
|
$localize`Error executing bulk operation`,
|
||||||
@@ -328,15 +328,6 @@ export class BulkEditorComponent
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private setModalButtonsEnabled(modal: NgbModalRef, enabled: boolean) {
|
|
||||||
const buttonsEnabled = modal.componentInstance.buttonsEnabled
|
|
||||||
if (typeof buttonsEnabled?.set === 'function') {
|
|
||||||
buttonsEnabled.set(enabled)
|
|
||||||
} else {
|
|
||||||
modal.componentInstance.buttonsEnabled = enabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private applySelectionData(
|
private applySelectionData(
|
||||||
items: SelectionDataItem[],
|
items: SelectionDataItem[],
|
||||||
selectionModel: FilterableDropdownSelectionModel
|
selectionModel: FilterableDropdownSelectionModel
|
||||||
@@ -872,7 +863,7 @@ export class BulkEditorComponent
|
|||||||
modal.componentInstance.confirmClicked
|
modal.componentInstance.confirmClicked
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.executeDocumentAction(
|
this.executeDocumentAction(
|
||||||
modal,
|
modal,
|
||||||
this.documentService.deleteDocuments(this.getSelectionQuery())
|
this.documentService.deleteDocuments(this.getSelectionQuery())
|
||||||
@@ -920,7 +911,7 @@ export class BulkEditorComponent
|
|||||||
modal.componentInstance.confirmClicked
|
modal.componentInstance.confirmClicked
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.executeDocumentAction(
|
this.executeDocumentAction(
|
||||||
modal,
|
modal,
|
||||||
this.documentService.reprocessDocuments(this.getSelectionQuery())
|
this.documentService.reprocessDocuments(this.getSelectionQuery())
|
||||||
@@ -957,7 +948,7 @@ export class BulkEditorComponent
|
|||||||
rotateDialog.confirmClicked
|
rotateDialog.confirmClicked
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
rotateDialog.buttonsEnabled = false
|
rotateDialog.buttonsEnabled.set(false)
|
||||||
this.executeDocumentAction(
|
this.executeDocumentAction(
|
||||||
modal,
|
modal,
|
||||||
this.documentService.rotateDocuments(
|
this.documentService.rotateDocuments(
|
||||||
@@ -990,7 +981,7 @@ export class BulkEditorComponent
|
|||||||
if (mergeDialog.archiveFallback()) {
|
if (mergeDialog.archiveFallback()) {
|
||||||
args.archive_fallback = true
|
args.archive_fallback = true
|
||||||
}
|
}
|
||||||
mergeDialog.buttonsEnabled = false
|
mergeDialog.buttonsEnabled.set(false)
|
||||||
this.executeDocumentAction(
|
this.executeDocumentAction(
|
||||||
modal,
|
modal,
|
||||||
this.documentService.mergeDocuments(mergeDialog.documentIDs(), args),
|
this.documentService.mergeDocuments(mergeDialog.documentIDs(), args),
|
||||||
@@ -1063,14 +1054,14 @@ export class BulkEditorComponent
|
|||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
dialog.loading.set(true)
|
dialog.loading.set(true)
|
||||||
dialog.buttonsEnabled = false
|
dialog.buttonsEnabled.set(false)
|
||||||
this.shareLinkBundleService
|
this.shareLinkBundleService
|
||||||
.createBundle(dialog.payload)
|
.createBundle(dialog.payload)
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (result) => {
|
next: (result) => {
|
||||||
dialog.loading.set(false)
|
dialog.loading.set(false)
|
||||||
dialog.buttonsEnabled = false
|
dialog.buttonsEnabled.set(false)
|
||||||
dialog.createdBundle = result
|
dialog.createdBundle = result
|
||||||
dialog.copied.set(false)
|
dialog.copied.set(false)
|
||||||
dialog.payload = null
|
dialog.payload = null
|
||||||
@@ -1084,7 +1075,7 @@ export class BulkEditorComponent
|
|||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
dialog.loading.set(false)
|
dialog.loading.set(false)
|
||||||
dialog.buttonsEnabled = true
|
dialog.buttonsEnabled.set(true)
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
$localize`Share link bundle creation is not available yet.`,
|
$localize`Share link bundle creation is not available yet.`,
|
||||||
error
|
error
|
||||||
|
|||||||
@@ -97,7 +97,9 @@
|
|||||||
<div class="dropdown-menu shadow dropdown-menu-right" ngbDropdownMenu>
|
<div class="dropdown-menu shadow dropdown-menu-right" ngbDropdownMenu>
|
||||||
@if (!list.activeSavedViewId) {
|
@if (!list.activeSavedViewId) {
|
||||||
@for (view of savedViewService.allViews; track view) {
|
@for (view of savedViewService.allViews; track view) {
|
||||||
<button ngbDropdownItem (click)="loadViewConfig(view.id)">{{view.name}}</button>
|
<button ngbDropdownItem (click)="loadViewConfig(view.id)">
|
||||||
|
<i-bs class="me-2" [name]="view.icon || 'funnel'"></i-bs>{{view.name}}
|
||||||
|
</button>
|
||||||
}
|
}
|
||||||
@if (savedViewService.allViews.length > 0) {
|
@if (savedViewService.allViews.length > 0) {
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
|
|||||||
@@ -457,6 +457,7 @@ export class DocumentListComponent
|
|||||||
modal.componentInstance.buttonsEnabled.set(false)
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
let savedView: SavedView = {
|
let savedView: SavedView = {
|
||||||
name: formValue.name,
|
name: formValue.name,
|
||||||
|
icon: formValue.icon,
|
||||||
filter_rules: this.list.filterRules,
|
filter_rules: this.list.filterRules,
|
||||||
sort_reverse: this.list.sortReverse,
|
sort_reverse: this.list.sortReverse,
|
||||||
sort_field: this.list.sortField,
|
sort_field: this.list.sortField,
|
||||||
|
|||||||
+8
@@ -6,6 +6,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error()?.name" autocomplete="off"></pngx-input-text>
|
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error()?.name" autocomplete="off"></pngx-input-text>
|
||||||
|
<pngx-input-select
|
||||||
|
i18n-title
|
||||||
|
title="Icon"
|
||||||
|
formControlName="icon"
|
||||||
|
[items]="savedViewIcons"
|
||||||
|
iconField="icon"
|
||||||
|
[error]="error()?.icon">
|
||||||
|
</pngx-input-select>
|
||||||
<pngx-input-check i18n-title title="Show in sidebar" formControlName="showInSideBar"></pngx-input-check>
|
<pngx-input-check i18n-title title="Show in sidebar" formControlName="showInSideBar"></pngx-input-check>
|
||||||
<pngx-input-check i18n-title title="Show on dashboard" formControlName="showOnDashboard"></pngx-input-check>
|
<pngx-input-check i18n-title title="Show on dashboard" formControlName="showOnDashboard"></pngx-input-check>
|
||||||
<pngx-permissions-form accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
<pngx-permissions-form accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
||||||
|
|||||||
+5
@@ -9,6 +9,7 @@ import { CheckComponent } from '../../common/input/check/check.component'
|
|||||||
import { PermissionsFormComponent } from '../../common/input/permissions/permissions-form/permissions-form.component'
|
import { PermissionsFormComponent } from '../../common/input/permissions/permissions-form/permissions-form.component'
|
||||||
import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
|
import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
|
||||||
import { PermissionsUserComponent } from '../../common/input/permissions/permissions-user/permissions-user.component'
|
import { PermissionsUserComponent } from '../../common/input/permissions/permissions-user/permissions-user.component'
|
||||||
|
import { SelectComponent } from '../../common/input/select/select.component'
|
||||||
import { TextComponent } from '../../common/input/text/text.component'
|
import { TextComponent } from '../../common/input/text/text.component'
|
||||||
import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component'
|
import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component'
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ describe('SaveViewConfigDialogComponent', () => {
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
SaveViewConfigDialogComponent,
|
SaveViewConfigDialogComponent,
|
||||||
TextComponent,
|
TextComponent,
|
||||||
|
SelectComponent,
|
||||||
CheckComponent,
|
CheckComponent,
|
||||||
PermissionsFormComponent,
|
PermissionsFormComponent,
|
||||||
PermissionsUserComponent,
|
PermissionsUserComponent,
|
||||||
@@ -63,6 +65,7 @@ describe('SaveViewConfigDialogComponent', () => {
|
|||||||
expect(component.defaultName()).toEqual(name)
|
expect(component.defaultName()).toEqual(name)
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
name,
|
name,
|
||||||
|
icon: 'funnel',
|
||||||
showInSideBar: false,
|
showInSideBar: false,
|
||||||
showOnDashboard: false,
|
showOnDashboard: false,
|
||||||
})
|
})
|
||||||
@@ -94,6 +97,7 @@ describe('SaveViewConfigDialogComponent', () => {
|
|||||||
component.save()
|
component.save()
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
name,
|
name,
|
||||||
|
icon: 'funnel',
|
||||||
showInSideBar: true,
|
showInSideBar: true,
|
||||||
showOnDashboard: true,
|
showOnDashboard: true,
|
||||||
})
|
})
|
||||||
@@ -113,6 +117,7 @@ describe('SaveViewConfigDialogComponent', () => {
|
|||||||
component.save()
|
component.save()
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
name: '',
|
name: '',
|
||||||
|
icon: 'funnel',
|
||||||
showInSideBar: false,
|
showInSideBar: false,
|
||||||
showOnDashboard: false,
|
showOnDashboard: false,
|
||||||
permissions_form: permissions,
|
permissions_form: permissions,
|
||||||
|
|||||||
+9
@@ -13,9 +13,14 @@ import {
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
} from '@angular/forms'
|
} from '@angular/forms'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import {
|
||||||
|
DEFAULT_SAVED_VIEW_ICON,
|
||||||
|
SAVED_VIEW_ICONS,
|
||||||
|
} from 'src/app/data/saved-view-icons'
|
||||||
import { User } from 'src/app/data/user'
|
import { User } from 'src/app/data/user'
|
||||||
import { CheckComponent } from '../../common/input/check/check.component'
|
import { CheckComponent } from '../../common/input/check/check.component'
|
||||||
import { PermissionsFormComponent } from '../../common/input/permissions/permissions-form/permissions-form.component'
|
import { PermissionsFormComponent } from '../../common/input/permissions/permissions-form/permissions-form.component'
|
||||||
|
import { SelectComponent } from '../../common/input/select/select.component'
|
||||||
import { TextComponent } from '../../common/input/text/text.component'
|
import { TextComponent } from '../../common/input/text/text.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -24,6 +29,7 @@ import { TextComponent } from '../../common/input/text/text.component'
|
|||||||
styleUrls: ['./save-view-config-dialog.component.scss'],
|
styleUrls: ['./save-view-config-dialog.component.scss'],
|
||||||
imports: [
|
imports: [
|
||||||
CheckComponent,
|
CheckComponent,
|
||||||
|
SelectComponent,
|
||||||
TextComponent,
|
TextComponent,
|
||||||
PermissionsFormComponent,
|
PermissionsFormComponent,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
@@ -41,6 +47,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
|
|||||||
public saveClicked = new EventEmitter()
|
public saveClicked = new EventEmitter()
|
||||||
|
|
||||||
users: User[]
|
users: User[]
|
||||||
|
readonly savedViewIcons = SAVED_VIEW_ICONS
|
||||||
|
|
||||||
setDefaultName(value: string) {
|
setDefaultName(value: string) {
|
||||||
this.defaultName.set(value)
|
this.defaultName.set(value)
|
||||||
@@ -49,6 +56,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
|
|||||||
|
|
||||||
saveViewConfigForm = new FormGroup({
|
saveViewConfigForm = new FormGroup({
|
||||||
name: new FormControl(''),
|
name: new FormControl(''),
|
||||||
|
icon: new FormControl(DEFAULT_SAVED_VIEW_ICON),
|
||||||
showInSideBar: new FormControl(false),
|
showInSideBar: new FormControl(false),
|
||||||
showOnDashboard: new FormControl(false),
|
showOnDashboard: new FormControl(false),
|
||||||
permissions_form: new FormControl(null),
|
permissions_form: new FormControl(null),
|
||||||
@@ -65,6 +73,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
|
|||||||
const formValue = this.saveViewConfigForm.value
|
const formValue = this.saveViewConfigForm.value
|
||||||
const saveViewConfig = {
|
const saveViewConfig = {
|
||||||
name: formValue.name,
|
name: formValue.name,
|
||||||
|
icon: formValue.icon,
|
||||||
showInSideBar: formValue.showInSideBar,
|
showInSideBar: formValue.showInSideBar,
|
||||||
showOnDashboard: formValue.showOnDashboard,
|
showOnDashboard: formValue.showOnDashboard,
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -105,7 +105,7 @@ export class CustomFieldsComponent
|
|||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.customFieldsService.delete(field).subscribe({
|
this.customFieldsService.delete(field).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
modal.close()
|
modal.close()
|
||||||
|
|||||||
+4
-4
@@ -274,7 +274,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
|
|||||||
activeModal.componentInstance.btnClass = 'btn-danger'
|
activeModal.componentInstance.btnClass = 'btn-danger'
|
||||||
activeModal.componentInstance.btnCaption = $localize`Delete`
|
activeModal.componentInstance.btnCaption = $localize`Delete`
|
||||||
activeModal.componentInstance.confirmClicked.subscribe(() => {
|
activeModal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
activeModal.componentInstance.buttonsEnabled = false
|
activeModal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.service
|
this.service
|
||||||
.delete(object)
|
.delete(object)
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
@@ -284,7 +284,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
|
|||||||
this.reloadData()
|
this.reloadData()
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
activeModal.componentInstance.buttonsEnabled = true
|
activeModal.componentInstance.buttonsEnabled.set(true)
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
$localize`Error while deleting element`,
|
$localize`Error while deleting element`,
|
||||||
error
|
error
|
||||||
@@ -455,7 +455,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
|
|||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.service
|
this.service
|
||||||
.bulk_edit_objects(
|
.bulk_edit_objects(
|
||||||
this.allSelectionActive ? [] : Array.from(this.selectedObjects),
|
this.allSelectionActive ? [] : Array.from(this.selectedObjects),
|
||||||
@@ -472,7 +472,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
|
|||||||
this.reloadData()
|
this.reloadData()
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
modal.componentInstance.buttonsEnabled = true
|
modal.componentInstance.buttonsEnabled.set(true)
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
$localize`Error deleting objects`,
|
$localize`Error deleting objects`,
|
||||||
error
|
error
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export class MailComponent
|
|||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.mailAccountService.delete(account).subscribe({
|
this.mailAccountService.delete(account).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
modal.close()
|
modal.close()
|
||||||
@@ -298,7 +298,7 @@ export class MailComponent
|
|||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.mailRuleService.delete(rule).subscribe({
|
this.mailRuleService.delete(rule).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
modal.close()
|
modal.close()
|
||||||
|
|||||||
@@ -7,15 +7,24 @@
|
|||||||
</pngx-page-header>
|
</pngx-page-header>
|
||||||
<form [formGroup]="savedViewsForm" (ngSubmit)="save()">
|
<form [formGroup]="savedViewsForm" (ngSubmit)="save()">
|
||||||
<ul class="list-group mb-3" formGroupName="savedViews">
|
<ul class="list-group mb-3" formGroupName="savedViews">
|
||||||
@for (view of savedViews(); track view) {
|
@for (view of pagedSavedViews(); track view) {
|
||||||
<li class="list-group-item py-3">
|
<li class="list-group-item py-3">
|
||||||
<div [formGroupName]="view.id">
|
<div [formGroupName]="view.id">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col-md">
|
||||||
<pngx-input-text title="Name" formControlName="name"></pngx-input-text>
|
<pngx-input-text title="Name" formControlName="name"></pngx-input-text>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md">
|
||||||
|
<pngx-input-select
|
||||||
|
i18n-title
|
||||||
|
title="Icon"
|
||||||
|
formControlName="icon"
|
||||||
|
[items]="savedViewIcons"
|
||||||
|
iconField="icon">
|
||||||
|
</pngx-input-select>
|
||||||
|
</div>
|
||||||
@if (canSaveSettings) {
|
@if (canSaveSettings) {
|
||||||
<div class="col">
|
<div class="col-md">
|
||||||
<div class="form-check form-switch mt-3">
|
<div class="form-check form-switch mt-3">
|
||||||
<input type="checkbox" class="form-check-input" id="show_on_dashboard_{{view.id}}" formControlName="show_on_dashboard">
|
<input type="checkbox" class="form-check-input" id="show_on_dashboard_{{view.id}}" formControlName="show_on_dashboard">
|
||||||
<label class="form-check-label" for="show_on_dashboard_{{view.id}}" i18n>Show on dashboard</label>
|
<label class="form-check-label" for="show_on_dashboard_{{view.id}}" i18n>Show on dashboard</label>
|
||||||
@@ -81,6 +90,11 @@
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<button type="button" (click)="reset()" class="btn btn-outline-secondary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Cancel</button>
|
<div class="d-flex align-items-center mb-3">
|
||||||
<button type="submit" class="btn btn-primary ms-2 mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
|
<button type="button" (click)="reset()" class="btn btn-outline-secondary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary ms-2 mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
|
||||||
|
@if (savedViews()?.length > pageSize) {
|
||||||
|
<ngb-pagination class="ms-auto" [pageSize]="pageSize" [collectionSize]="savedViews().length" [page]="page()" [maxSize]="5" (pageChange)="page.set($event)" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'
|
|||||||
import { signal } from '@angular/core'
|
import { signal } from '@angular/core'
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
|
import { By } from '@angular/platform-browser'
|
||||||
import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
import { Subject, of, throwError } from 'rxjs'
|
import { Subject, of, throwError } from 'rxjs'
|
||||||
@@ -25,8 +26,20 @@ import { PageHeaderComponent } from '../../common/page-header/page-header.compon
|
|||||||
import { SavedViewsComponent } from './saved-views.component'
|
import { SavedViewsComponent } from './saved-views.component'
|
||||||
|
|
||||||
const savedViews = [
|
const savedViews = [
|
||||||
{ id: 1, name: 'view1', show_in_sidebar: true, show_on_dashboard: true },
|
{
|
||||||
{ id: 2, name: 'view2', show_in_sidebar: false, show_on_dashboard: false },
|
id: 1,
|
||||||
|
name: 'view1',
|
||||||
|
icon: 'archive',
|
||||||
|
show_in_sidebar: true,
|
||||||
|
show_on_dashboard: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'view2',
|
||||||
|
icon: 'funnel',
|
||||||
|
show_in_sidebar: false,
|
||||||
|
show_on_dashboard: false,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
describe('SavedViewsComponent', () => {
|
describe('SavedViewsComponent', () => {
|
||||||
@@ -157,6 +170,24 @@ describe('SavedViewsComponent', () => {
|
|||||||
expect(patchBody.show_in_sidebar).toBeUndefined()
|
expect(patchBody.show_in_sidebar).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should persist a changed icon', () => {
|
||||||
|
const patchSpy = jest.spyOn(savedViewService, 'patchMany')
|
||||||
|
const view = savedViews[0]
|
||||||
|
const iconControl = component.savedViewsForm
|
||||||
|
.get('savedViews')
|
||||||
|
.get(view.id.toString())
|
||||||
|
.get('icon')
|
||||||
|
|
||||||
|
iconControl.setValue('bell')
|
||||||
|
iconControl.markAsDirty()
|
||||||
|
component.save()
|
||||||
|
|
||||||
|
expect(patchSpy.mock.calls[0][0][0]).toMatchObject({
|
||||||
|
id: view.id,
|
||||||
|
icon: 'bell',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should persist visibility changes to user settings', () => {
|
it('should persist visibility changes to user settings', () => {
|
||||||
const patchSpy = jest.spyOn(savedViewService, 'patchMany')
|
const patchSpy = jest.spyOn(savedViewService, 'patchMany')
|
||||||
const updateVisibilitySpy = jest
|
const updateVisibilitySpy = jest
|
||||||
@@ -222,6 +253,44 @@ describe('SavedViewsComponent', () => {
|
|||||||
).toEqual(view.show_on_dashboard)
|
).toEqual(view.show_on_dashboard)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should page saved views, clamp the page if views are removed', () => {
|
||||||
|
const manyViews = Array.from({ length: 30 }, (_, i) => ({
|
||||||
|
id: i + 1,
|
||||||
|
name: `view${i + 1}`,
|
||||||
|
})) as SavedView[]
|
||||||
|
const listSpy = jest.spyOn(savedViewService, 'list').mockReturnValue(
|
||||||
|
of({
|
||||||
|
all: manyViews.map((v) => v.id),
|
||||||
|
count: manyViews.length,
|
||||||
|
results: manyViews.concat([]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
component.ngOnInit()
|
||||||
|
fixture.detectChanges()
|
||||||
|
expect(listSpy).toHaveBeenCalledWith(1, 100000, null, false, {
|
||||||
|
full_perms: true,
|
||||||
|
})
|
||||||
|
expect(component.pagedSavedViews()).toHaveLength(25)
|
||||||
|
expect(fixture.debugElement.query(By.css('ngb-pagination'))).not.toBeNull()
|
||||||
|
// all views have controls, not just the current page
|
||||||
|
expect(
|
||||||
|
Object.keys(component.savedViewsForm.get('savedViews').value)
|
||||||
|
).toHaveLength(30)
|
||||||
|
|
||||||
|
component.page.set(2)
|
||||||
|
expect(component.pagedSavedViews()).toHaveLength(5)
|
||||||
|
|
||||||
|
listSpy.mockReturnValue(
|
||||||
|
of({
|
||||||
|
all: manyViews.slice(0, 25).map((v) => v.id),
|
||||||
|
count: 25,
|
||||||
|
results: manyViews.slice(0, 25),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
component.ngOnInit()
|
||||||
|
expect(component.page()).toEqual(1)
|
||||||
|
})
|
||||||
|
|
||||||
it('should support editing permissions', () => {
|
it('should support editing permissions', () => {
|
||||||
const confirmClicked = new Subject<any>()
|
const confirmClicked = new Subject<any>()
|
||||||
const modalRef = {
|
const modalRef = {
|
||||||
|
|||||||
@@ -1,18 +1,29 @@
|
|||||||
import { AsyncPipe } from '@angular/common'
|
import { AsyncPipe } from '@angular/common'
|
||||||
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
computed,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
|
} from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
} from '@angular/forms'
|
} from '@angular/forms'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { dirtyCheck } from '@ngneat/dirty-check-forms'
|
import { dirtyCheck } from '@ngneat/dirty-check-forms'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { BehaviorSubject, Observable, of, switchMap, takeUntil } from 'rxjs'
|
import { BehaviorSubject, Observable, of, switchMap, takeUntil } from 'rxjs'
|
||||||
import { PermissionsDialogComponent } from 'src/app/components/common/permissions-dialog/permissions-dialog.component'
|
import { PermissionsDialogComponent } from 'src/app/components/common/permissions-dialog/permissions-dialog.component'
|
||||||
import { DisplayMode } from 'src/app/data/document'
|
import { DisplayMode } from 'src/app/data/document'
|
||||||
import { SavedView } from 'src/app/data/saved-view'
|
import { SavedView } from 'src/app/data/saved-view'
|
||||||
|
import {
|
||||||
|
DEFAULT_SAVED_VIEW_ICON,
|
||||||
|
SAVED_VIEW_ICONS,
|
||||||
|
} from 'src/app/data/saved-view-icons'
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||||
import {
|
import {
|
||||||
PermissionAction,
|
PermissionAction,
|
||||||
@@ -25,6 +36,7 @@ import { ToastService } from 'src/app/services/toast.service'
|
|||||||
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
|
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
|
||||||
import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component'
|
import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component'
|
||||||
import { NumberComponent } from '../../common/input/number/number.component'
|
import { NumberComponent } from '../../common/input/number/number.component'
|
||||||
|
import { SelectComponent } from '../../common/input/select/select.component'
|
||||||
import { TextComponent } from '../../common/input/text/text.component'
|
import { TextComponent } from '../../common/input/text/text.component'
|
||||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||||
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
||||||
@@ -36,12 +48,14 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
|
|||||||
PageHeaderComponent,
|
PageHeaderComponent,
|
||||||
ConfirmButtonComponent,
|
ConfirmButtonComponent,
|
||||||
NumberComponent,
|
NumberComponent,
|
||||||
|
SelectComponent,
|
||||||
TextComponent,
|
TextComponent,
|
||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
DragDropSelectComponent,
|
DragDropSelectComponent,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
NgbPaginationModule,
|
||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -56,8 +70,17 @@ export class SavedViewsComponent
|
|||||||
private readonly modalService = inject(NgbModal)
|
private readonly modalService = inject(NgbModal)
|
||||||
|
|
||||||
DisplayMode = DisplayMode
|
DisplayMode = DisplayMode
|
||||||
|
readonly savedViewIcons = SAVED_VIEW_ICONS
|
||||||
|
|
||||||
readonly savedViews = signal<SavedView[]>(undefined)
|
readonly savedViews = signal<SavedView[]>(undefined)
|
||||||
|
readonly page = signal(1)
|
||||||
|
public readonly pageSize = 25
|
||||||
|
// All views are loaded at init, so paging is only for display
|
||||||
|
readonly pagedSavedViews = computed(() => {
|
||||||
|
const start = (this.page() - 1) * this.pageSize
|
||||||
|
return this.savedViews()?.slice(start, start + this.pageSize)
|
||||||
|
})
|
||||||
|
|
||||||
private savedViewsGroup = new FormGroup({})
|
private savedViewsGroup = new FormGroup({})
|
||||||
public savedViewsForm: FormGroup = new FormGroup({
|
public savedViewsForm: FormGroup = new FormGroup({
|
||||||
savedViews: this.savedViewsGroup,
|
savedViews: this.savedViewsGroup,
|
||||||
@@ -84,9 +107,11 @@ export class SavedViewsComponent
|
|||||||
private reloadViews(): void {
|
private reloadViews(): void {
|
||||||
this.loading.set(true)
|
this.loading.set(true)
|
||||||
this.savedViewService
|
this.savedViewService
|
||||||
.list(null, null, null, false, { full_perms: true })
|
.list(1, 100000, null, false, { full_perms: true })
|
||||||
.subscribe((r) => {
|
.subscribe((r) => {
|
||||||
this.savedViews.set(r.results)
|
this.savedViews.set(r.results)
|
||||||
|
const pageCount = Math.ceil(r.results.length / this.pageSize)
|
||||||
|
this.page.update((page) => Math.min(page, Math.max(1, pageCount)))
|
||||||
this.initialize()
|
this.initialize()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -110,6 +135,7 @@ export class SavedViewsComponent
|
|||||||
storeData.savedViews[view.id.toString()] = {
|
storeData.savedViews[view.id.toString()] = {
|
||||||
id: view.id,
|
id: view.id,
|
||||||
name: view.name,
|
name: view.name,
|
||||||
|
icon: view.icon ?? DEFAULT_SAVED_VIEW_ICON,
|
||||||
show_on_dashboard: view.show_on_dashboard,
|
show_on_dashboard: view.show_on_dashboard,
|
||||||
show_in_sidebar: view.show_in_sidebar,
|
show_in_sidebar: view.show_in_sidebar,
|
||||||
page_size: view.page_size,
|
page_size: view.page_size,
|
||||||
@@ -122,6 +148,7 @@ export class SavedViewsComponent
|
|||||||
new FormGroup({
|
new FormGroup({
|
||||||
id: new FormControl({ value: null, disabled: !canEdit }),
|
id: new FormControl({ value: null, disabled: !canEdit }),
|
||||||
name: new FormControl({ value: null, disabled: !canEdit }),
|
name: new FormControl({ value: null, disabled: !canEdit }),
|
||||||
|
icon: new FormControl({ value: null, disabled: !canEdit }),
|
||||||
show_on_dashboard: new FormControl({
|
show_on_dashboard: new FormControl({
|
||||||
value: null,
|
value: null,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
@@ -200,6 +227,7 @@ export class SavedViewsComponent
|
|||||||
|
|
||||||
const modelFieldsChanged =
|
const modelFieldsChanged =
|
||||||
group.get('name')?.dirty ||
|
group.get('name')?.dirty ||
|
||||||
|
group.get('icon')?.dirty ||
|
||||||
group.get('page_size')?.dirty ||
|
group.get('page_size')?.dirty ||
|
||||||
group.get('display_mode')?.dirty ||
|
group.get('display_mode')?.dirty ||
|
||||||
group.get('display_fields')?.dirty
|
group.get('display_fields')?.dirty
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export class WorkflowsComponent
|
|||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
this.workflowService.delete(workflow).subscribe({
|
this.workflowService.delete(workflow).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
modal.close()
|
modal.close()
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
export const DEFAULT_SAVED_VIEW_ICON = 'funnel'
|
||||||
|
|
||||||
|
export const SAVED_VIEW_ICONS = [
|
||||||
|
{ id: 'archive', name: $localize`Archive`, icon: 'archive' },
|
||||||
|
{ id: 'bank', name: $localize`Bank`, icon: 'bank' },
|
||||||
|
{ id: 'basket', name: $localize`Basket`, icon: 'basket' },
|
||||||
|
{ id: 'bell', name: $localize`Bell`, icon: 'bell' },
|
||||||
|
{ id: 'bookmark', name: $localize`Bookmark`, icon: 'bookmark' },
|
||||||
|
{ id: 'boxes', name: $localize`Boxes`, icon: 'boxes' },
|
||||||
|
{ id: 'briefcase', name: $localize`Briefcase`, icon: 'briefcase' },
|
||||||
|
{ id: 'building', name: $localize`Building`, icon: 'building' },
|
||||||
|
{ id: 'calculator', name: $localize`Calculator`, icon: 'calculator' },
|
||||||
|
{ id: 'calendar', name: $localize`Calendar`, icon: 'calendar' },
|
||||||
|
{ id: 'camera', name: $localize`Camera`, icon: 'camera' },
|
||||||
|
{
|
||||||
|
id: 'card-checklist',
|
||||||
|
name: $localize`Checklist`,
|
||||||
|
icon: 'card-checklist',
|
||||||
|
},
|
||||||
|
{ id: 'cash', name: $localize`Cash`, icon: 'cash' },
|
||||||
|
{ id: 'chat-left-text', name: $localize`Chat`, icon: 'chat-left-text' },
|
||||||
|
{ id: 'check-circle', name: $localize`Check`, icon: 'check-circle' },
|
||||||
|
{ id: 'clipboard', name: $localize`Clipboard`, icon: 'clipboard' },
|
||||||
|
{ id: 'clock-history', name: $localize`Clock`, icon: 'clock-history' },
|
||||||
|
{ id: 'credit-card', name: $localize`Credit card`, icon: 'credit-card' },
|
||||||
|
{ id: 'download', name: $localize`Download`, icon: 'download' },
|
||||||
|
{ id: 'envelope', name: $localize`Envelope`, icon: 'envelope' },
|
||||||
|
{
|
||||||
|
id: 'exclamation-triangle',
|
||||||
|
name: $localize`Warning`,
|
||||||
|
icon: 'exclamation-triangle',
|
||||||
|
},
|
||||||
|
{ id: 'file-earmark', name: $localize`File`, icon: 'file-earmark' },
|
||||||
|
{
|
||||||
|
id: 'file-earmark-check',
|
||||||
|
name: $localize`Checked file`,
|
||||||
|
icon: 'file-earmark-check',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'file-earmark-lock',
|
||||||
|
name: $localize`Locked file`,
|
||||||
|
icon: 'file-earmark-lock',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'file-earmark-medical',
|
||||||
|
name: $localize`Medical file`,
|
||||||
|
icon: 'file-earmark-medical',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'file-earmark-person',
|
||||||
|
name: $localize`Person file`,
|
||||||
|
icon: 'file-earmark-person',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'file-earmark-spreadsheet',
|
||||||
|
name: $localize`Spreadsheet`,
|
||||||
|
icon: 'file-earmark-spreadsheet',
|
||||||
|
},
|
||||||
|
{ id: 'file-text', name: $localize`Text file`, icon: 'file-text' },
|
||||||
|
{ id: 'files', name: $localize`Files`, icon: 'files' },
|
||||||
|
{ id: 'folder', name: $localize`Folder`, icon: 'folder' },
|
||||||
|
{ id: 'funnel', name: $localize`Filter`, icon: 'funnel' },
|
||||||
|
{ id: 'gear', name: $localize`Gear`, icon: 'gear' },
|
||||||
|
{ id: 'globe2', name: $localize`Globe`, icon: 'globe2' },
|
||||||
|
{ id: 'hash', name: $localize`Hash`, icon: 'hash' },
|
||||||
|
{ id: 'heart', name: $localize`Heart`, icon: 'heart' },
|
||||||
|
{ id: 'house', name: $localize`House`, icon: 'house' },
|
||||||
|
{ id: 'inbox', name: $localize`Inbox`, icon: 'inbox' },
|
||||||
|
{ id: 'journals', name: $localize`Journals`, icon: 'journals' },
|
||||||
|
{ id: 'list-task', name: $localize`Task list`, icon: 'list-task' },
|
||||||
|
{ id: 'newspaper', name: $localize`Newspaper`, icon: 'newspaper' },
|
||||||
|
{ id: 'paperclip', name: $localize`Attachment`, icon: 'paperclip' },
|
||||||
|
{ id: 'people', name: $localize`People`, icon: 'people' },
|
||||||
|
{ id: 'person', name: $localize`Person`, icon: 'person' },
|
||||||
|
{ id: 'printer', name: $localize`Printer`, icon: 'printer' },
|
||||||
|
{ id: 'receipt', name: $localize`Receipt`, icon: 'receipt' },
|
||||||
|
{ id: 'safe', name: $localize`Safe`, icon: 'safe' },
|
||||||
|
{ id: 'search', name: $localize`Search`, icon: 'search' },
|
||||||
|
{ id: 'send', name: $localize`Send`, icon: 'send' },
|
||||||
|
{ id: 'shop', name: $localize`Shop`, icon: 'shop' },
|
||||||
|
{ id: 'stack', name: $localize`Stack`, icon: 'stack' },
|
||||||
|
{ id: 'stars', name: $localize`Stars`, icon: 'stars' },
|
||||||
|
{ id: 'tag', name: $localize`Tag`, icon: 'tag' },
|
||||||
|
{ id: 'tags', name: $localize`Tags`, icon: 'tags' },
|
||||||
|
{ id: 'telephone', name: $localize`Telephone`, icon: 'telephone' },
|
||||||
|
{ id: 'truck', name: $localize`Truck`, icon: 'truck' },
|
||||||
|
{ id: 'upc-scan', name: $localize`Barcode`, icon: 'upc-scan' },
|
||||||
|
{ id: 'wallet2', name: $localize`Wallet`, icon: 'wallet2' },
|
||||||
|
]
|
||||||
@@ -5,6 +5,8 @@ import { ObjectWithPermissions } from './object-with-permissions'
|
|||||||
export interface SavedView extends ObjectWithPermissions {
|
export interface SavedView extends ObjectWithPermissions {
|
||||||
name?: string
|
name?: string
|
||||||
|
|
||||||
|
icon?: string
|
||||||
|
|
||||||
show_on_dashboard?: boolean
|
show_on_dashboard?: boolean
|
||||||
|
|
||||||
show_in_sidebar?: boolean
|
show_in_sidebar?: boolean
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class DirtyFormGuard extends DirtyCheckGuard {
|
|||||||
modal.componentInstance.btnClass = 'btn-warning'
|
modal.componentInstance.btnClass = 'btn-warning'
|
||||||
modal.componentInstance.btnCaption = $localize`Leave page`
|
modal.componentInstance.btnCaption = $localize`Leave page`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
modal.close()
|
modal.close()
|
||||||
})
|
})
|
||||||
const subject = new Subject<boolean>()
|
const subject = new Subject<boolean>()
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ export class DirtySavedViewGuard {
|
|||||||
modal.componentInstance.alternativeBtnClass = 'btn-primary'
|
modal.componentInstance.alternativeBtnClass = 'btn-primary'
|
||||||
modal.componentInstance.alternativeBtnCaption = $localize`Save and close`
|
modal.componentInstance.alternativeBtnCaption = $localize`Save and close`
|
||||||
modal.componentInstance.alternativeClicked.pipe(first()).subscribe(() => {
|
modal.componentInstance.alternativeClicked.pipe(first()).subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
component.saveViewConfig()
|
component.saveViewConfig()
|
||||||
modal.close()
|
modal.close()
|
||||||
})
|
})
|
||||||
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
modal.close()
|
modal.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export class OpenDocumentsService {
|
|||||||
modal.componentInstance.btnClass = 'btn-warning'
|
modal.componentInstance.btnClass = 'btn-warning'
|
||||||
modal.componentInstance.btnCaption = $localize`Close document`
|
modal.componentInstance.btnCaption = $localize`Close document`
|
||||||
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
modal.close()
|
modal.close()
|
||||||
this.openDocuments.splice(index, 1)
|
this.openDocuments.splice(index, 1)
|
||||||
this.dirtyDocuments.delete(doc.id)
|
this.dirtyDocuments.delete(doc.id)
|
||||||
@@ -165,7 +165,7 @@ export class OpenDocumentsService {
|
|||||||
modal.componentInstance.btnClass = 'btn-warning'
|
modal.componentInstance.btnClass = 'btn-warning'
|
||||||
modal.componentInstance.btnCaption = $localize`Close documents`
|
modal.componentInstance.btnCaption = $localize`Close documents`
|
||||||
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled.set(false)
|
||||||
modal.close()
|
modal.close()
|
||||||
this.openDocuments.splice(0, this.openDocuments.length)
|
this.openDocuments.splice(0, this.openDocuments.length)
|
||||||
this.dirtyDocuments.clear()
|
this.dirtyDocuments.clear()
|
||||||
|
|||||||
@@ -120,6 +120,12 @@ describe('PermissionsService', () => {
|
|||||||
actionKey: 'View', // PermissionAction.View
|
actionKey: 'View', // PermissionAction.View
|
||||||
typeKey: 'SystemMonitoring', // PermissionType.SystemMonitoring
|
typeKey: 'SystemMonitoring', // PermissionType.SystemMonitoring
|
||||||
})
|
})
|
||||||
|
expect(permissionsService.getPermissionKeys('add_sharelinkbundle')).toEqual(
|
||||||
|
{
|
||||||
|
actionKey: 'Add', // PermissionAction.Add
|
||||||
|
typeKey: 'ShareLinkBundle', // PermissionType.ShareLinkBundle
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('correctly checks explicit global permissions', () => {
|
it('correctly checks explicit global permissions', () => {
|
||||||
@@ -269,6 +275,10 @@ describe('PermissionsService', () => {
|
|||||||
'view_sharelink',
|
'view_sharelink',
|
||||||
'change_sharelink',
|
'change_sharelink',
|
||||||
'delete_sharelink',
|
'delete_sharelink',
|
||||||
|
'add_sharelinkbundle',
|
||||||
|
'view_sharelinkbundle',
|
||||||
|
'change_sharelinkbundle',
|
||||||
|
'delete_sharelinkbundle',
|
||||||
'add_workflow',
|
'add_workflow',
|
||||||
'view_workflow',
|
'view_workflow',
|
||||||
'change_workflow',
|
'change_workflow',
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export enum PermissionType {
|
|||||||
User = '%s_user',
|
User = '%s_user',
|
||||||
Group = '%s_group',
|
Group = '%s_group',
|
||||||
ShareLink = '%s_sharelink',
|
ShareLink = '%s_sharelink',
|
||||||
|
ShareLinkBundle = '%s_sharelinkbundle',
|
||||||
CustomField = '%s_customfield',
|
CustomField = '%s_customfield',
|
||||||
Workflow = '%s_workflow',
|
Workflow = '%s_workflow',
|
||||||
ProcessedMail = '%s_processedmail',
|
ProcessedMail = '%s_processedmail',
|
||||||
|
|||||||
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1336
-885
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1335
-884
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1359
-908
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+2583
-2133
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1370
-919
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1335
-884
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
+1333
-882
File diff suppressed because it is too large
Load Diff
@@ -35,19 +35,27 @@ import {
|
|||||||
arrowRightShort,
|
arrowRightShort,
|
||||||
arrowUpRight,
|
arrowUpRight,
|
||||||
asterisk,
|
asterisk,
|
||||||
|
bank,
|
||||||
|
basket,
|
||||||
bell,
|
bell,
|
||||||
bodyText,
|
bodyText,
|
||||||
|
bookmark,
|
||||||
boxArrowUp,
|
boxArrowUp,
|
||||||
boxArrowUpRight,
|
boxArrowUpRight,
|
||||||
boxes,
|
boxes,
|
||||||
braces,
|
braces,
|
||||||
|
briefcase,
|
||||||
|
building,
|
||||||
|
calculator,
|
||||||
calendar,
|
calendar,
|
||||||
calendarEvent,
|
calendarEvent,
|
||||||
calendarEventFill,
|
calendarEventFill,
|
||||||
|
camera,
|
||||||
cardChecklist,
|
cardChecklist,
|
||||||
cardHeading,
|
cardHeading,
|
||||||
caretDown,
|
caretDown,
|
||||||
caretUp,
|
caretUp,
|
||||||
|
cash,
|
||||||
chatLeftText,
|
chatLeftText,
|
||||||
chatSquareDots,
|
chatSquareDots,
|
||||||
check,
|
check,
|
||||||
@@ -65,6 +73,7 @@ import {
|
|||||||
clipboardCheckFill,
|
clipboardCheckFill,
|
||||||
clipboardFill,
|
clipboardFill,
|
||||||
clockHistory,
|
clockHistory,
|
||||||
|
creditCard,
|
||||||
dash,
|
dash,
|
||||||
dashCircle,
|
dashCircle,
|
||||||
diagram3,
|
diagram3,
|
||||||
@@ -83,9 +92,12 @@ import {
|
|||||||
fileEarmarkDiff,
|
fileEarmarkDiff,
|
||||||
fileEarmarkFill,
|
fileEarmarkFill,
|
||||||
fileEarmarkLock,
|
fileEarmarkLock,
|
||||||
|
fileEarmarkMedical,
|
||||||
fileEarmarkMinus,
|
fileEarmarkMinus,
|
||||||
|
fileEarmarkPerson,
|
||||||
fileEarmarkPlus,
|
fileEarmarkPlus,
|
||||||
fileEarmarkRichtext,
|
fileEarmarkRichtext,
|
||||||
|
fileEarmarkSpreadsheet,
|
||||||
fileText,
|
fileText,
|
||||||
files,
|
files,
|
||||||
filter,
|
filter,
|
||||||
@@ -93,12 +105,15 @@ import {
|
|||||||
folderFill,
|
folderFill,
|
||||||
funnel,
|
funnel,
|
||||||
gear,
|
gear,
|
||||||
|
globe2,
|
||||||
google,
|
google,
|
||||||
grid,
|
grid,
|
||||||
gripVertical,
|
gripVertical,
|
||||||
hash,
|
hash,
|
||||||
hddStack,
|
hddStack,
|
||||||
|
heart,
|
||||||
house,
|
house,
|
||||||
|
inbox,
|
||||||
infoCircle,
|
infoCircle,
|
||||||
journals,
|
journals,
|
||||||
link,
|
link,
|
||||||
@@ -106,7 +121,9 @@ import {
|
|||||||
listTask,
|
listTask,
|
||||||
listUl,
|
listUl,
|
||||||
microsoft,
|
microsoft,
|
||||||
|
newspaper,
|
||||||
nodePlus,
|
nodePlus,
|
||||||
|
paperclip,
|
||||||
pencil,
|
pencil,
|
||||||
people,
|
people,
|
||||||
peopleFill,
|
peopleFill,
|
||||||
@@ -121,9 +138,12 @@ import {
|
|||||||
plusCircle,
|
plusCircle,
|
||||||
printer,
|
printer,
|
||||||
questionCircle,
|
questionCircle,
|
||||||
|
receipt,
|
||||||
|
safe,
|
||||||
scissors,
|
scissors,
|
||||||
search,
|
search,
|
||||||
send,
|
send,
|
||||||
|
shop,
|
||||||
slashCircle,
|
slashCircle,
|
||||||
sliders2Vertical,
|
sliders2Vertical,
|
||||||
sortAlphaDown,
|
sortAlphaDown,
|
||||||
@@ -133,14 +153,17 @@ import {
|
|||||||
tag,
|
tag,
|
||||||
tagFill,
|
tagFill,
|
||||||
tags,
|
tags,
|
||||||
|
telephone,
|
||||||
textIndentLeft,
|
textIndentLeft,
|
||||||
textLeft,
|
textLeft,
|
||||||
threeDots,
|
threeDots,
|
||||||
threeDotsVertical,
|
threeDotsVertical,
|
||||||
trash,
|
trash,
|
||||||
|
truck,
|
||||||
uiRadios,
|
uiRadios,
|
||||||
unlock,
|
unlock,
|
||||||
upcScan,
|
upcScan,
|
||||||
|
wallet2,
|
||||||
windowStack,
|
windowStack,
|
||||||
x,
|
x,
|
||||||
xCircle,
|
xCircle,
|
||||||
@@ -258,15 +281,22 @@ const icons = {
|
|||||||
arrowRightShort,
|
arrowRightShort,
|
||||||
arrowUpRight,
|
arrowUpRight,
|
||||||
asterisk,
|
asterisk,
|
||||||
|
bank,
|
||||||
|
basket,
|
||||||
bell,
|
bell,
|
||||||
braces,
|
braces,
|
||||||
bodyText,
|
bodyText,
|
||||||
|
bookmark,
|
||||||
boxArrowUp,
|
boxArrowUp,
|
||||||
boxArrowUpRight,
|
boxArrowUpRight,
|
||||||
boxes,
|
boxes,
|
||||||
|
briefcase,
|
||||||
|
building,
|
||||||
|
calculator,
|
||||||
calendar,
|
calendar,
|
||||||
calendarEvent,
|
calendarEvent,
|
||||||
calendarEventFill,
|
calendarEventFill,
|
||||||
|
camera,
|
||||||
cardChecklist,
|
cardChecklist,
|
||||||
cardHeading,
|
cardHeading,
|
||||||
caretDown,
|
caretDown,
|
||||||
@@ -288,6 +318,8 @@ const icons = {
|
|||||||
clipboardCheckFill,
|
clipboardCheckFill,
|
||||||
clipboardFill,
|
clipboardFill,
|
||||||
clockHistory,
|
clockHistory,
|
||||||
|
cash,
|
||||||
|
creditCard,
|
||||||
dash,
|
dash,
|
||||||
dashCircle,
|
dashCircle,
|
||||||
diagram3,
|
diagram3,
|
||||||
@@ -306,9 +338,12 @@ const icons = {
|
|||||||
fileEarmarkDiff,
|
fileEarmarkDiff,
|
||||||
fileEarmarkFill,
|
fileEarmarkFill,
|
||||||
fileEarmarkLock,
|
fileEarmarkLock,
|
||||||
|
fileEarmarkMedical,
|
||||||
fileEarmarkMinus,
|
fileEarmarkMinus,
|
||||||
|
fileEarmarkPerson,
|
||||||
fileEarmarkPlus,
|
fileEarmarkPlus,
|
||||||
fileEarmarkRichtext,
|
fileEarmarkRichtext,
|
||||||
|
fileEarmarkSpreadsheet,
|
||||||
files,
|
files,
|
||||||
fileText,
|
fileText,
|
||||||
filter,
|
filter,
|
||||||
@@ -316,12 +351,15 @@ const icons = {
|
|||||||
folderFill,
|
folderFill,
|
||||||
funnel,
|
funnel,
|
||||||
gear,
|
gear,
|
||||||
|
globe2,
|
||||||
google,
|
google,
|
||||||
grid,
|
grid,
|
||||||
gripVertical,
|
gripVertical,
|
||||||
hash,
|
hash,
|
||||||
hddStack,
|
hddStack,
|
||||||
|
heart,
|
||||||
house,
|
house,
|
||||||
|
inbox,
|
||||||
infoCircle,
|
infoCircle,
|
||||||
journals,
|
journals,
|
||||||
link,
|
link,
|
||||||
@@ -329,8 +367,10 @@ const icons = {
|
|||||||
listTask,
|
listTask,
|
||||||
listUl,
|
listUl,
|
||||||
microsoft,
|
microsoft,
|
||||||
|
newspaper,
|
||||||
nodePlus,
|
nodePlus,
|
||||||
pencil,
|
pencil,
|
||||||
|
paperclip,
|
||||||
people,
|
people,
|
||||||
peopleFill,
|
peopleFill,
|
||||||
person,
|
person,
|
||||||
@@ -344,10 +384,13 @@ const icons = {
|
|||||||
plusCircle,
|
plusCircle,
|
||||||
printer,
|
printer,
|
||||||
questionCircle,
|
questionCircle,
|
||||||
|
receipt,
|
||||||
|
safe,
|
||||||
scissors,
|
scissors,
|
||||||
search,
|
search,
|
||||||
send,
|
send,
|
||||||
slashCircle,
|
slashCircle,
|
||||||
|
shop,
|
||||||
sliders2Vertical,
|
sliders2Vertical,
|
||||||
sortAlphaDown,
|
sortAlphaDown,
|
||||||
sortAlphaUpAlt,
|
sortAlphaUpAlt,
|
||||||
@@ -358,12 +401,15 @@ const icons = {
|
|||||||
tags,
|
tags,
|
||||||
textIndentLeft,
|
textIndentLeft,
|
||||||
textLeft,
|
textLeft,
|
||||||
|
telephone,
|
||||||
threeDots,
|
threeDots,
|
||||||
threeDotsVertical,
|
threeDotsVertical,
|
||||||
trash,
|
trash,
|
||||||
|
truck,
|
||||||
uiRadios,
|
uiRadios,
|
||||||
unlock,
|
unlock,
|
||||||
upcScan,
|
upcScan,
|
||||||
|
wallet2,
|
||||||
windowStack,
|
windowStack,
|
||||||
x,
|
x,
|
||||||
xCircle,
|
xCircle,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user