Get listing storage paths working
This commit is contained in:
parent
62d6c30ee1
commit
c1fc77f55f
@ -1,95 +1,98 @@
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { DatePipe, registerLocaleData } from '@angular/common'
|
||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core'
|
||||
import { AppRoutingModule } from './app-routing.module'
|
||||
import { AppComponent } from './app.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import {
|
||||
NgbDateAdapter,
|
||||
NgbDateParserFormatter,
|
||||
NgbModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'
|
||||
import { DocumentListComponent } from './components/document-list/document-list.component'
|
||||
import { ExplorerComponent } from './components/explorer/explorer.component'
|
||||
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
|
||||
import { DashboardComponent } from './components/dashboard/dashboard.component'
|
||||
import { TagListComponent } from './components/manage/tag-list/tag-list.component'
|
||||
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'
|
||||
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
|
||||
import { LogsComponent } from './components/manage/logs/logs.component'
|
||||
import { SettingsComponent } from './components/manage/settings/settings.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { DatePipe, registerLocaleData } from '@angular/common'
|
||||
import { NotFoundComponent } from './components/not-found/not-found.component'
|
||||
import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component'
|
||||
import { CorrespondentEditDialogComponent } from './components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||
import { TagEditDialogComponent } from './components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
||||
import { DocumentTypeEditDialogComponent } from './components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
||||
import { TagComponent } from './components/common/tag/tag.component'
|
||||
import { ClearableBadgeComponent } from './components/common/clearable-badge/clearable-badge.component'
|
||||
import { PageHeaderComponent } from './components/common/page-header/page-header.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PdfViewerModule } from 'ng2-pdf-viewer'
|
||||
import { ColorSliderModule } from 'ngx-color/slider'
|
||||
import { CookieService } from 'ngx-cookie-service'
|
||||
import { NgxFileDropModule } from 'ngx-file-drop'
|
||||
import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
|
||||
import { AppRoutingModule } from './app-routing.module'
|
||||
import { AppComponent } from './app.component'
|
||||
import { AppFrameComponent } from './components/app-frame/app-frame.component'
|
||||
import { ToastsComponent } from './components/common/toasts/toasts.component'
|
||||
import { FilterEditorComponent } from './components/document-list/filter-editor/filter-editor.component'
|
||||
import { FilterEditorComponent as ExplorerFilterEditorComponent } from './components/explorer/filter-editor/filter-editor.component'
|
||||
import { ClearableBadgeComponent } from './components/common/clearable-badge/clearable-badge.component'
|
||||
import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component'
|
||||
import { DateDropdownComponent } from './components/common/date-dropdown/date-dropdown.component'
|
||||
import { CorrespondentEditDialogComponent } from './components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||
import { DocumentTypeEditDialogComponent } from './components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
||||
import { GroupEditDialogComponent } from './components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
||||
import { MailAccountEditDialogComponent } from './components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
||||
import { MailRuleEditDialogComponent } from './components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
||||
import { StoragePathEditDialogComponent } from './components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||
import { TagEditDialogComponent } from './components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
||||
import { UserEditDialogComponent } from './components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
|
||||
import { FilterableDropdownComponent } from './components/common/filterable-dropdown/filterable-dropdown.component'
|
||||
import { ToggleableDropdownButtonComponent } from './components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
|
||||
import { DateDropdownComponent } from './components/common/date-dropdown/date-dropdown.component'
|
||||
import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component'
|
||||
import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component'
|
||||
import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component'
|
||||
import { NgxFileDropModule } from 'ngx-file-drop'
|
||||
import { TextComponent } from './components/common/input/text/text.component'
|
||||
import { SelectComponent } from './components/common/input/select/select.component'
|
||||
import { CheckComponent } from './components/common/input/check/check.component'
|
||||
import { ColorComponent } from './components/common/input/color/color.component'
|
||||
import { DateComponent } from './components/common/input/date/date.component'
|
||||
import { NumberComponent } from './components/common/input/number/number.component'
|
||||
import { PasswordComponent } from './components/common/input/password/password.component'
|
||||
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'
|
||||
import { PermissionsFormComponent } from './components/common/input/permissions/permissions-form/permissions-form.component'
|
||||
import { PermissionsGroupComponent } from './components/common/input/permissions/permissions-group/permissions-group.component'
|
||||
import { PermissionsUserComponent } from './components/common/input/permissions/permissions-user/permissions-user.component'
|
||||
import { SelectComponent } from './components/common/input/select/select.component'
|
||||
import { TagsComponent } from './components/common/input/tags/tags.component'
|
||||
import { IfPermissionsDirective } from './directives/if-permissions.directive'
|
||||
import { SortableDirective } from './directives/sortable.directive'
|
||||
import { CookieService } from 'ngx-cookie-service'
|
||||
import { CsrfInterceptor } from './interceptors/csrf.interceptor'
|
||||
import { TextComponent } from './components/common/input/text/text.component'
|
||||
import { PageHeaderComponent } from './components/common/page-header/page-header.component'
|
||||
import { PermissionsDialogComponent } from './components/common/permissions-dialog/permissions-dialog.component'
|
||||
import { PermissionsSelectComponent } from './components/common/permissions-select/permissions-select.component'
|
||||
import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'
|
||||
import { TagComponent } from './components/common/tag/tag.component'
|
||||
import { ToastsComponent } from './components/common/toasts/toasts.component'
|
||||
import { DashboardComponent } from './components/dashboard/dashboard.component'
|
||||
import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component'
|
||||
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component'
|
||||
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component'
|
||||
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component'
|
||||
import { PdfViewerModule } from 'ng2-pdf-viewer'
|
||||
import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component'
|
||||
import { YesNoPipe } from './pipes/yes-no.pipe'
|
||||
import { FileSizePipe } from './pipes/file-size.pipe'
|
||||
import { FilterPipe } from './pipes/filter.pipe'
|
||||
import { DocumentTitlePipe } from './pipes/document-title.pipe'
|
||||
import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component'
|
||||
import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { NumberComponent } from './components/common/input/number/number.component'
|
||||
import { SafeUrlPipe } from './pipes/safeurl.pipe'
|
||||
import { SafeHtmlPipe } from './pipes/safehtml.pipe'
|
||||
import { CustomDatePipe } from './pipes/custom-date.pipe'
|
||||
import { DateComponent } from './components/common/input/date/date.component'
|
||||
import { ISODateAdapter } from './utils/ngb-iso-date-adapter'
|
||||
import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter'
|
||||
import { ApiVersionInterceptor } from './interceptors/api-version.interceptor'
|
||||
import { ColorSliderModule } from 'ngx-color/slider'
|
||||
import { ColorComponent } from './components/common/input/color/color.component'
|
||||
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component'
|
||||
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
|
||||
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
|
||||
import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component'
|
||||
import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component'
|
||||
import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component'
|
||||
import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component'
|
||||
import { DocumentListComponent } from './components/document-list/document-list.component'
|
||||
import { FilterEditorComponent } from './components/document-list/filter-editor/filter-editor.component'
|
||||
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'
|
||||
import { DocumentNotesComponent } from './components/document-notes/document-notes.component'
|
||||
import { PermissionsGuard } from './guards/permissions.guard'
|
||||
import { ExplorerComponent } from './components/explorer/explorer.component'
|
||||
import { FilterEditorComponent as ExplorerFilterEditorComponent } from './components/explorer/filter-editor/filter-editor.component'
|
||||
import { FolderCardSmallComponent } from './components/explorer/folder-card-small/folder-card-small.component'
|
||||
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
|
||||
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'
|
||||
import { LogsComponent } from './components/manage/logs/logs.component'
|
||||
import { SettingsComponent } from './components/manage/settings/settings.component'
|
||||
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
|
||||
import { TagListComponent } from './components/manage/tag-list/tag-list.component'
|
||||
import { TasksComponent } from './components/manage/tasks/tasks.component'
|
||||
import { NotFoundComponent } from './components/not-found/not-found.component'
|
||||
import { IfObjectPermissionsDirective } from './directives/if-object-permissions.directive'
|
||||
import { IfOwnerDirective } from './directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from './directives/if-permissions.directive'
|
||||
import { SortableDirective } from './directives/sortable.directive'
|
||||
import { DirtyDocGuard } from './guards/dirty-doc.guard'
|
||||
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
|
||||
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
|
||||
import { StoragePathEditDialogComponent } from './components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||
import { PermissionsGuard } from './guards/permissions.guard'
|
||||
import { ApiVersionInterceptor } from './interceptors/api-version.interceptor'
|
||||
import { CsrfInterceptor } from './interceptors/csrf.interceptor'
|
||||
import { CustomDatePipe } from './pipes/custom-date.pipe'
|
||||
import { DocumentTitlePipe } from './pipes/document-title.pipe'
|
||||
import { FileSizePipe } from './pipes/file-size.pipe'
|
||||
import { FilterPipe } from './pipes/filter.pipe'
|
||||
import { SafeHtmlPipe } from './pipes/safehtml.pipe'
|
||||
import { SafeUrlPipe } from './pipes/safeurl.pipe'
|
||||
import { YesNoPipe } from './pipes/yes-no.pipe'
|
||||
import { SettingsService } from './services/settings.service'
|
||||
import { TasksComponent } from './components/manage/tasks/tasks.component'
|
||||
import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
|
||||
import { UserEditDialogComponent } from './components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
|
||||
import { GroupEditDialogComponent } from './components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
||||
import { PermissionsSelectComponent } from './components/common/permissions-select/permissions-select.component'
|
||||
import { MailAccountEditDialogComponent } from './components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
||||
import { MailRuleEditDialogComponent } from './components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
||||
import { PermissionsUserComponent } from './components/common/input/permissions/permissions-user/permissions-user.component'
|
||||
import { PermissionsGroupComponent } from './components/common/input/permissions/permissions-group/permissions-group.component'
|
||||
import { IfOwnerDirective } from './directives/if-owner.directive'
|
||||
import { IfObjectPermissionsDirective } from './directives/if-object-permissions.directive'
|
||||
import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter'
|
||||
import { ISODateAdapter } from './utils/ngb-iso-date-adapter'
|
||||
|
||||
import localeAr from '@angular/common/locales/ar'
|
||||
import localeBe from '@angular/common/locales/be'
|
||||
@ -111,8 +114,6 @@ import localeSr from '@angular/common/locales/sr'
|
||||
import localeSv from '@angular/common/locales/sv'
|
||||
import localeTr from '@angular/common/locales/tr'
|
||||
import localeZh from '@angular/common/locales/zh'
|
||||
import { PermissionsDialogComponent } from './components/common/permissions-dialog/permissions-dialog.component'
|
||||
import { PermissionsFormComponent } from './components/common/input/permissions/permissions-form/permissions-form.component'
|
||||
|
||||
registerLocaleData(localeAr)
|
||||
registerLocaleData(localeBe)
|
||||
@ -173,6 +174,7 @@ function initializeApp(settings: SettingsService) {
|
||||
DateDropdownComponent,
|
||||
DocumentCardLargeComponent,
|
||||
DocumentCardSmallComponent,
|
||||
FolderCardSmallComponent,
|
||||
BulkEditorComponent,
|
||||
TextComponent,
|
||||
SelectComponent,
|
||||
|
@ -1,32 +1,67 @@
|
||||
<app-page-header [title]="getTitle()">
|
||||
|
||||
<div ngbDropdown class="me-2 d-flex">
|
||||
<button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-primary"
|
||||
id="dropdownSelect"
|
||||
ngbDropdownToggle
|
||||
>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#text-indent-left" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Select</ng-container></div>
|
||||
<div class="d-none d-sm-inline">
|
||||
<ng-container i18n>Select</ng-container>
|
||||
</div>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
|
||||
<button ngbDropdownItem (click)="list.selectNone()" i18n>Select none</button>
|
||||
<button ngbDropdownItem (click)="list.selectPage()" i18n>Select page</button>
|
||||
<button ngbDropdownItem (click)="list.selectAll()" i18n>Select all</button>
|
||||
<button ngbDropdownItem (click)="list.selectNone()" i18n>
|
||||
Select none
|
||||
</button>
|
||||
<button ngbDropdownItem (click)="list.selectPage()" i18n>
|
||||
Select page
|
||||
</button>
|
||||
<button ngbDropdownItem (click)="list.selectAll()" i18n>
|
||||
Select all
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group flex-fill" role="group">
|
||||
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="details" (ngModelChange)="saveDisplayMode()" id="displayModeDetails" name="displayModeDetails">
|
||||
<input
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
[(ngModel)]="displayMode"
|
||||
value="details"
|
||||
(ngModelChange)="saveDisplayMode()"
|
||||
id="displayModeDetails"
|
||||
name="displayModeDetails"
|
||||
/>
|
||||
<label for="displayModeDetails" class="btn btn-outline-primary btn-sm">
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#list-ul" />
|
||||
</svg>
|
||||
</label>
|
||||
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="smallCards" (ngModelChange)="saveDisplayMode()" id="displayModeSmall" name="displayModeSmall">
|
||||
<input
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
[(ngModel)]="displayMode"
|
||||
value="smallCards"
|
||||
(ngModelChange)="saveDisplayMode()"
|
||||
id="displayModeSmall"
|
||||
name="displayModeSmall"
|
||||
/>
|
||||
<label for="displayModeSmall" class="btn btn-outline-primary btn-sm">
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#grid" />
|
||||
</svg>
|
||||
</label>
|
||||
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="largeCards" (ngModelChange)="saveDisplayMode()" id="displayModeLarge" name="displayModeLarge">
|
||||
<input
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
[(ngModel)]="displayMode"
|
||||
value="largeCards"
|
||||
(ngModelChange)="saveDisplayMode()"
|
||||
id="displayModeLarge"
|
||||
name="displayModeLarge"
|
||||
/>
|
||||
<label for="displayModeLarge" class="btn btn-outline-primary btn-sm">
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#hdd-stack" />
|
||||
@ -35,53 +70,115 @@
|
||||
</div>
|
||||
|
||||
<div ngbDropdown class="btn-group ms-2 flex-fill">
|
||||
<button class="btn btn-outline-primary btn-sm" id="dropdownBasic1" ngbDropdownToggle i18n>Sort</button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownBasic1" class="shadow dropdown-menu-right">
|
||||
<button
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
id="dropdownBasic1"
|
||||
ngbDropdownToggle
|
||||
i18n
|
||||
>
|
||||
Sort
|
||||
</button>
|
||||
<div
|
||||
ngbDropdownMenu
|
||||
aria-labelledby="dropdownBasic1"
|
||||
class="shadow dropdown-menu-right"
|
||||
>
|
||||
<div class="w-100 d-flex pb-2 mb-1 border-bottom">
|
||||
<input type="radio" class="btn-check" [value]="false" [(ngModel)]="listSortReverse" id="listSortReverseFalse">
|
||||
<label class="btn btn-outline-primary btn-sm mx-2 flex-fill" for="listSortReverseFalse">
|
||||
<input
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
[value]="false"
|
||||
[(ngModel)]="listSortReverse"
|
||||
id="listSortReverseFalse"
|
||||
/>
|
||||
<label
|
||||
class="btn btn-outline-primary btn-sm mx-2 flex-fill"
|
||||
for="listSortReverseFalse"
|
||||
>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#sort-alpha-down" />
|
||||
</svg>
|
||||
</label>
|
||||
<input type="radio" class="btn-check" [value]="true" [(ngModel)]="listSortReverse" id="listSortReverseTrue">
|
||||
<label class="btn btn-outline-primary btn-sm me-2 flex-fill" for="listSortReverseTrue">
|
||||
<input
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
[value]="true"
|
||||
[(ngModel)]="listSortReverse"
|
||||
id="listSortReverseTrue"
|
||||
/>
|
||||
<label
|
||||
class="btn btn-outline-primary btn-sm me-2 flex-fill"
|
||||
for="listSortReverseTrue"
|
||||
>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#sort-alpha-up-alt" />
|
||||
</svg>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button *ngFor="let f of getSortFields()" ngbDropdownItem (click)="setSortField(f.field)"
|
||||
[class.active]="list.sortField === f.field">{{f.name}}
|
||||
<button
|
||||
*ngFor="let f of getSortFields()"
|
||||
ngbDropdownItem
|
||||
(click)="setSortField(f.field)"
|
||||
[class.active]="list.sortField === f.field"
|
||||
>
|
||||
{{ f.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group ms-2 flex-fill" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }" ngbDropdown role="group">
|
||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle flex-fill" tourAnchor="tour.documents-views" ngbDropdownToggle>
|
||||
<div
|
||||
class="btn-group ms-2 flex-fill"
|
||||
*appIfPermissions="{
|
||||
action: PermissionAction.View,
|
||||
type: PermissionType.SavedView
|
||||
}"
|
||||
ngbDropdown
|
||||
role="group"
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-primary dropdown-toggle flex-fill"
|
||||
tourAnchor="tour.documents-views"
|
||||
ngbDropdownToggle
|
||||
>
|
||||
<ng-container i18n>Views</ng-container>
|
||||
<div *ngIf="savedViewIsModified" class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle">
|
||||
<div
|
||||
*ngIf="savedViewIsModified"
|
||||
class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle"
|
||||
>
|
||||
<span class="visually-hidden">selected</span>
|
||||
</div>
|
||||
</button>
|
||||
<div class="dropdown-menu shadow dropdown-menu-right" ngbDropdownMenu>
|
||||
<ng-container *ngIf="!list.activeSavedViewId">
|
||||
<button ngbDropdownItem *ngFor="let view of savedViewService.allViews" (click)="loadViewConfig(view.id)">{{view.name}}</button>
|
||||
<div class="dropdown-divider" *ngIf="savedViewService.allViews.length > 0"></div>
|
||||
<button
|
||||
ngbDropdownItem
|
||||
*ngFor="let view of savedViewService.allViews"
|
||||
(click)="loadViewConfig(view.id)"
|
||||
>
|
||||
{{ view.name }}
|
||||
</button>
|
||||
<div
|
||||
class="dropdown-divider"
|
||||
*ngIf="savedViewService.allViews.length > 0"
|
||||
></div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</app-page-header>
|
||||
|
||||
<div class="row sticky-top pt-3 pt-sm-4 pb-2 pb-lg-4 bg-body">
|
||||
<app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" [selectionData]="list.selectionData" #filterEditor></app-filter-editor>
|
||||
<app-filter-editor
|
||||
[hidden]="isBulkEditing"
|
||||
[(filterRules)]="list.filterRules"
|
||||
[unmodifiedFilterRules]="unmodifiedFilterRules"
|
||||
[selectionData]="list.selectionData"
|
||||
#filterEditor
|
||||
></app-filter-editor>
|
||||
<app-bulk-editor [hidden]="!isBulkEditing"></app-bulk-editor>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-template #pagination>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<p>
|
||||
@ -89,13 +186,21 @@
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</ng-container>
|
||||
<span i18n *ngIf="list.selected.size > 0">{list.collectionSize, plural, =1 {Selected {{list.selected.size}} of one document} other {Selected {{list.selected.size}} of {{list.collectionSize || 0}} documents}}</span>
|
||||
<ng-container *ngIf="!list.isReloading">
|
||||
<span i18n *ngIf="list.selected.size === 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span> <span i18n *ngIf="isFiltered">(filtered)</span>
|
||||
</ng-container>
|
||||
<span i18n *ngIf="list.selected.size > 0"
|
||||
>{list.collectionSize, plural, =1 {Selected {{ list.selected.size }} of
|
||||
one document} other {Selected {{ list.selected.size }} of
|
||||
{{ list.collectionSize || 0 }} documents}}</span
|
||||
>
|
||||
</p>
|
||||
<ngb-pagination *ngIf="list.collectionSize" [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
|
||||
[rotate]="true" aria-label="Default pagination"></ngb-pagination>
|
||||
<ngb-pagination
|
||||
*ngIf="list.collectionSize"
|
||||
[pageSize]="list.currentPageSize"
|
||||
[collectionSize]="list.collectionSize"
|
||||
[(page)]="list.currentPage"
|
||||
[maxSize]="5"
|
||||
[rotate]="true"
|
||||
aria-label="Default pagination"
|
||||
></ngb-pagination>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -103,131 +208,246 @@
|
||||
<ng-container *ngTemplateOutlet="pagination"></ng-container>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="list.error ; else documentListNoError">
|
||||
<div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
|
||||
<ng-container *ngIf="list.error; else documentListNoError">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<ng-container i18n>Error while loading documents</ng-container>:
|
||||
{{ list.error }}
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #documentListNoError>
|
||||
<div *ngIf="displayMode === 'largeCards'">
|
||||
<app-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" (dblClickDocument)="openDocumentDetail(d)" *ngFor="let d of list.documents; trackBy: trackByDocumentId" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)" (clickStoragePath)="clickStoragePath($event)" (clickMoreLike)="clickMoreLike(d.id)">
|
||||
<app-document-card-large
|
||||
[selected]="list.isSelected(d)"
|
||||
(toggleSelected)="toggleSelected(d, $event)"
|
||||
(dblClickDocument)="openDocumentDetail(d)"
|
||||
*ngFor="let d of list.documents; trackBy: trackByDocumentId"
|
||||
[document]="d"
|
||||
(clickTag)="clickTag($event)"
|
||||
(clickCorrespondent)="clickCorrespondent($event)"
|
||||
(clickDocumentType)="clickDocumentType($event)"
|
||||
(clickStoragePath)="clickStoragePath($event)"
|
||||
(clickMoreLike)="clickMoreLike(d.id)"
|
||||
>
|
||||
</app-document-card-large>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm align-middle border shadow-sm" *ngIf="displayMode === 'details'">
|
||||
<table
|
||||
class="table table-sm align-middle border shadow-sm"
|
||||
*ngIf="displayMode === 'details'"
|
||||
>
|
||||
<thead>
|
||||
<th></th>
|
||||
<th class="d-none d-lg-table-cell"
|
||||
<th
|
||||
class="d-none d-lg-table-cell"
|
||||
appSortable="archive_serial_number"
|
||||
title="Sort by ASN" i18n-title
|
||||
title="Sort by ASN"
|
||||
i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>ASN</th>
|
||||
<th class="d-none d-md-table-cell"
|
||||
i18n
|
||||
>
|
||||
ASN
|
||||
</th>
|
||||
<th
|
||||
class="d-none d-md-table-cell"
|
||||
appSortable="correspondent__name"
|
||||
title="Sort by correspondent" i18n-title
|
||||
title="Sort by correspondent"
|
||||
i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Correspondent</th>
|
||||
i18n
|
||||
>
|
||||
Correspondent
|
||||
</th>
|
||||
<th
|
||||
appSortable="title"
|
||||
title="Sort by title" i18n-title
|
||||
title="Sort by title"
|
||||
i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Title</th>
|
||||
<th *ngIf="notesEnabled" class="d-none d-xl-table-cell"
|
||||
i18n
|
||||
>
|
||||
Title
|
||||
</th>
|
||||
<th
|
||||
*ngIf="notesEnabled"
|
||||
class="d-none d-xl-table-cell"
|
||||
appSortable="num_notes"
|
||||
title="Sort by notes" i18n-title
|
||||
title="Sort by notes"
|
||||
i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Notes</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
i18n
|
||||
>
|
||||
Notes
|
||||
</th>
|
||||
<th
|
||||
class="d-none d-xl-table-cell"
|
||||
appSortable="document_type__name"
|
||||
title="Sort by document type" i18n-title
|
||||
title="Sort by document type"
|
||||
i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Document type</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
i18n
|
||||
>
|
||||
Document type
|
||||
</th>
|
||||
<th
|
||||
class="d-none d-xl-table-cell"
|
||||
appSortable="storage_path__name"
|
||||
title="Sort by storage path" i18n-title
|
||||
title="Sort by storage path"
|
||||
i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Storage path</th>
|
||||
i18n
|
||||
>
|
||||
Storage path
|
||||
</th>
|
||||
<th
|
||||
appSortable="created"
|
||||
title="Sort by created date" i18n-title
|
||||
title="Sort by created date"
|
||||
i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Created</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
i18n
|
||||
>
|
||||
Created
|
||||
</th>
|
||||
<th
|
||||
class="d-none d-xl-table-cell"
|
||||
appSortable="added"
|
||||
title="Sort by added date" i18n-title
|
||||
title="Sort by added date"
|
||||
i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Added</th>
|
||||
i18n
|
||||
>
|
||||
Added
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let d of list.documents; trackBy: trackByDocumentId" (click)="toggleSelected(d, $event); $event.stopPropagation();" (dblclick)="openDocumentDetail(d)" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
|
||||
<tr
|
||||
*ngFor="let d of list.documents; trackBy: trackByDocumentId"
|
||||
(click)="toggleSelected(d, $event); $event.stopPropagation()"
|
||||
(dblclick)="openDocumentDetail(d)"
|
||||
[ngClass]="list.isSelected(d) ? 'table-row-selected' : ''"
|
||||
>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="docCheck{{d.id}}" [checked]="list.isSelected(d)" (click)="toggleSelected(d, $event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="docCheck{{d.id}}"></label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="docCheck{{ d.id }}"
|
||||
[checked]="list.isSelected(d)"
|
||||
(click)="toggleSelected(d, $event); $event.stopPropagation()"
|
||||
/>
|
||||
<label class="form-check-label" for="docCheck{{ d.id }}"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="d-none d-lg-table-cell">
|
||||
{{d.archive_serial_number}}
|
||||
{{ d.archive_serial_number }}
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<ng-container *ngIf="d.correspondent">
|
||||
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
|
||||
<a
|
||||
(click)="
|
||||
clickCorrespondent(d.correspondent); $event.stopPropagation()
|
||||
"
|
||||
title="Filter by correspondent"
|
||||
i18n-title
|
||||
>{{ (d.correspondent$ | async)?.name }}</a
|
||||
>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
|
||||
<app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></app-tag>
|
||||
<a
|
||||
routerLink="/explorer/{{ d.id }}"
|
||||
title="Edit document"
|
||||
i18n-title
|
||||
style="overflow-wrap: anywhere"
|
||||
>{{ d.title | documentTitle }}</a
|
||||
>
|
||||
<app-tag
|
||||
[tag]="t"
|
||||
*ngFor="let t of d.tags$ | async"
|
||||
class="ms-1"
|
||||
clickable="true"
|
||||
linkTitle="Filter by tag"
|
||||
i18n-linkTitle
|
||||
(click)="clickTag(t.id); $event.stopPropagation()"
|
||||
></app-tag>
|
||||
</td>
|
||||
<td *ngIf="notesEnabled" class="d-none d-xl-table-cell">
|
||||
<a *ngIf="d.notes.length" routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
|
||||
<a
|
||||
*ngIf="d.notes.length"
|
||||
routerLink="/documents/{{ d.id }}/notes"
|
||||
class="btn btn-sm p-0"
|
||||
>
|
||||
<span class="badge rounded-pill bg-light border text-primary">
|
||||
<svg class="metadata-icon ms-1 me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
|
||||
</svg>
|
||||
{{d.notes.length}}</span>
|
||||
<svg class="metadata-icon ms-1 me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text" />
|
||||
</svg>
|
||||
{{ d.notes.length }}</span
|
||||
>
|
||||
</a>
|
||||
</td>
|
||||
<td class="d-none d-xl-table-cell">
|
||||
<ng-container *ngIf="d.document_type">
|
||||
<a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>
|
||||
<a
|
||||
(click)="
|
||||
clickDocumentType(d.document_type); $event.stopPropagation()
|
||||
"
|
||||
title="Filter by document type"
|
||||
i18n-title
|
||||
>{{ (d.document_type$ | async)?.name }}</a
|
||||
>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td class="d-none d-xl-table-cell">
|
||||
<ng-container *ngIf="d.storage_path">
|
||||
<a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{(d.storage_path$ | async)?.name}}</a>
|
||||
<a
|
||||
(click)="
|
||||
clickStoragePath(d.storage_path); $event.stopPropagation()
|
||||
"
|
||||
title="Filter by storage path"
|
||||
i18n-title
|
||||
>{{ (d.storage_path$ | async)?.name }}</a
|
||||
>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
{{d.created_date | customDate}}
|
||||
{{ d.created_date | customDate }}
|
||||
</td>
|
||||
<td class="d-none d-xl-table-cell">
|
||||
{{d.added | customDate}}
|
||||
{{ d.added | customDate }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="row row-cols-paperless-cards" *ngIf="displayMode === 'smallCards'">
|
||||
<app-document-card-small class="p-0" [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" (dblClickDocument)="openDocumentDetail(d)" [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickStoragePath)="clickStoragePath($event)" (clickDocumentType)="clickDocumentType($event)"></app-document-card-small>
|
||||
<div
|
||||
class="row row-cols-paperless-cards"
|
||||
*ngIf="displayMode === 'smallCards'"
|
||||
>
|
||||
<app-folder-card-small
|
||||
class="p-0"
|
||||
[selected]="list.isSelected(d)"
|
||||
(toggleSelected)="toggleSelected(d, $event)"
|
||||
(dblClickDocument)="openDocumentDetail(d)"
|
||||
[storagePath]="d"
|
||||
*ngFor="let d of list.documents; trackBy: trackByDocumentId"
|
||||
></app-folder-card-small>
|
||||
</div>
|
||||
<div *ngIf="list.documents?.length > 15" class="mt-3">
|
||||
<ng-container *ngTemplateOutlet="pagination"></ng-container>
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
|
@ -6,9 +6,9 @@ import {
|
||||
ViewChild,
|
||||
ViewChildren,
|
||||
} from '@angular/core'
|
||||
import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'
|
||||
import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { filter, first, map, Subject, switchMap, takeUntil } from 'rxjs'
|
||||
import { Subject, filter, first, map, switchMap, takeUntil } from 'rxjs'
|
||||
import {
|
||||
FilterRule,
|
||||
filterRulesDiffer,
|
||||
@ -19,11 +19,10 @@ import { PaperlessDocument } from 'src/app/data/paperless-document'
|
||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||
import {
|
||||
SortableDirective,
|
||||
SortEvent,
|
||||
SortableDirective,
|
||||
} from 'src/app/directives/sortable.directive'
|
||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
||||
import {
|
||||
DOCUMENT_SORT_FIELDS,
|
||||
@ -31,6 +30,7 @@ import {
|
||||
} from 'src/app/services/rest/document.service'
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { StoragePathListViewService } from 'src/app/services/storage-path-list-view.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||
import { FilterEditorComponent } from './filter-editor/filter-editor.component'
|
||||
@ -45,7 +45,7 @@ export class ExplorerComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
constructor(
|
||||
public list: DocumentListViewService,
|
||||
public list: StoragePathListViewService,
|
||||
public savedViewService: SavedViewService,
|
||||
public route: ActivatedRoute,
|
||||
private router: Router,
|
||||
@ -170,13 +170,14 @@ export class ExplorerComponent
|
||||
takeUntil(this.unsubscribeNotifier)
|
||||
)
|
||||
.subscribe((queryParams) => {
|
||||
console.log('test')
|
||||
if (queryParams.has('view')) {
|
||||
// loading a saved view on /documents
|
||||
this.loadViewConfig(parseInt(queryParams.get('view')))
|
||||
} else {
|
||||
// this.list.activateSavedView(null)
|
||||
// this.list.loadFromQueryParams(queryParams)
|
||||
// this.unmodifiedFilterRules = []
|
||||
this.list.activateSavedView(null)
|
||||
this.list.loadFromQueryParams(queryParams)
|
||||
this.unmodifiedFilterRules = []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
<div class="col p-2 h-100">
|
||||
<div
|
||||
class="card h-100 shadow-sm document-card"
|
||||
[class.card-selected]="selected"
|
||||
[class.popover-hidden]="popoverHidden"
|
||||
(mouseleave)="mouseLeaveCard()"
|
||||
>
|
||||
<div
|
||||
class="border-bottom doc-img-container"
|
||||
[class.doc-img-background-selected]="selected"
|
||||
(click)="this.toggleSelected.emit($event)"
|
||||
(dblclick)="dblClickDocument.emit(this)"
|
||||
>
|
||||
<div class="card-img doc-img rounded-top">
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
fill="currentColor"
|
||||
>
|
||||
<use xlink:href="assets/bootstrap-icons.svg#folder" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="border-end border-bottom bg-light py-1 px-2 document-card-check"
|
||||
>
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="smallCardCheck{{ storagePath.id }}"
|
||||
[checked]="selected"
|
||||
(click)="this.toggleSelected.emit($event)"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="smallCardCheck{{ storagePath.id }}"
|
||||
></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body bg-light p-2">
|
||||
<p class="card-text">
|
||||
{{ storagePath.name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,92 @@
|
||||
.card-text {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.doc-img {
|
||||
object-fit: cover;
|
||||
object-position: top left;
|
||||
height: 90px;
|
||||
mix-blend-mode: multiply;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.document-card-check {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0.5rem;
|
||||
border-top-left-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
pointer-events: none;
|
||||
|
||||
.form-check {
|
||||
padding: 0;
|
||||
min-height: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
.form-check-input {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.document-card:hover .document-card-check {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.document-card-notes {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 142px;
|
||||
}
|
||||
|
||||
.card-selected {
|
||||
border-color:var(--bs-primary);
|
||||
|
||||
.document-card-check {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.doc-img-background-selected {
|
||||
background-color: var(--pngx-primary-faded);
|
||||
}
|
||||
|
||||
.card-info {
|
||||
line-height: 1;
|
||||
|
||||
button {
|
||||
line-height: 1;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: transparent !important;
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer .btn {
|
||||
padding-top: .10rem;
|
||||
}
|
||||
|
||||
::ng-deep .tooltip-inner {
|
||||
text-align: left !important;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tags {
|
||||
top: .2rem;
|
||||
right: 0;
|
||||
max-width: 80%;
|
||||
row-gap: .2rem;
|
||||
line-height: 1;
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core'
|
||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||
|
||||
@Component({
|
||||
selector: 'app-folder-card-small',
|
||||
templateUrl: './folder-card-small.component.html',
|
||||
styleUrls: [
|
||||
'./folder-card-small.component.scss',
|
||||
'../popover-preview/popover-preview.scss',
|
||||
],
|
||||
})
|
||||
export class FolderCardSmallComponent extends ComponentWithPermissions {
|
||||
constructor(
|
||||
private storagePathService: StoragePathService,
|
||||
private settingsService: SettingsService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@Input()
|
||||
selected = false
|
||||
|
||||
@Output()
|
||||
toggleSelected = new EventEmitter()
|
||||
|
||||
@Input()
|
||||
storagePath: PaperlessStoragePath
|
||||
|
||||
@Output()
|
||||
dblClickDocument = new EventEmitter()
|
||||
|
||||
@Output()
|
||||
clickTag = new EventEmitter<number>()
|
||||
|
||||
@Output()
|
||||
clickCorrespondent = new EventEmitter<number>()
|
||||
|
||||
@Output()
|
||||
clickDocumentType = new EventEmitter<number>()
|
||||
|
||||
@Output()
|
||||
clickStoragePath = new EventEmitter<number>()
|
||||
|
||||
moreTags: number = null
|
||||
|
||||
@ViewChild('popover') popover: NgbPopover
|
||||
|
||||
mouseOnPreview = false
|
||||
popoverHidden = true
|
||||
|
||||
getIsThumbInverted() {
|
||||
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
|
||||
}
|
||||
|
||||
getThumbUrl() {
|
||||
return ''
|
||||
}
|
||||
|
||||
getDownloadUrl() {
|
||||
return ''
|
||||
}
|
||||
|
||||
get previewUrl() {
|
||||
return ''
|
||||
}
|
||||
|
||||
mouseEnterPreview() {
|
||||
this.mouseOnPreview = true
|
||||
if (!this.popover.isOpen()) {
|
||||
// we're going to open but hide to pre-load content during hover delay
|
||||
this.popover.open()
|
||||
this.popoverHidden = true
|
||||
setTimeout(() => {
|
||||
if (this.mouseOnPreview) {
|
||||
// show popover
|
||||
this.popoverHidden = false
|
||||
} else {
|
||||
this.popover.close()
|
||||
}
|
||||
}, 600)
|
||||
}
|
||||
}
|
||||
|
||||
mouseLeavePreview() {
|
||||
this.mouseOnPreview = false
|
||||
}
|
||||
|
||||
mouseLeaveCard() {
|
||||
this.popover.close()
|
||||
}
|
||||
|
||||
get notesEnabled(): boolean {
|
||||
return this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
::ng-deep app-document-list .popover {
|
||||
max-width: 40rem;
|
||||
|
||||
.preview {
|
||||
min-width: 30rem;
|
||||
min-height: 18rem;
|
||||
max-height: 35rem;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
position: absolute;
|
||||
top: 4rem;
|
||||
left: calc(50% - 0.5rem);
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .popover-hidden .popover {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
143
src-ui/src/app/services/rest/custom-storage-path.service.ts
Normal file
143
src-ui/src/app/services/rest/custom-storage-path.service.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Observable, map } from 'rxjs'
|
||||
import { FilterRule } from 'src/app/data/filter-rule'
|
||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
|
||||
import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata'
|
||||
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions'
|
||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||
import { Results } from 'src/app/data/results'
|
||||
import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
|
||||
import { AbstractPaperlessService } from './abstract-paperless-service'
|
||||
|
||||
interface SelectionDataItem {
|
||||
id: number
|
||||
document_count: number
|
||||
}
|
||||
|
||||
interface SelectionData {
|
||||
selected_storage_paths: SelectionDataItem[]
|
||||
selected_correspondents: SelectionDataItem[]
|
||||
selected_tags: SelectionDataItem[]
|
||||
selected_document_types: SelectionDataItem[]
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CustomStoragePathService extends AbstractPaperlessService<PaperlessStoragePath> {
|
||||
private _searchQuery: string
|
||||
|
||||
constructor(http: HttpClient) {
|
||||
super(http, 'storage_paths')
|
||||
}
|
||||
|
||||
listFiltered(
|
||||
page?: number,
|
||||
pageSize?: number,
|
||||
sortField?: string,
|
||||
sortReverse?: boolean,
|
||||
filterRules?: FilterRule[],
|
||||
extraParams = {}
|
||||
): Observable<Results<PaperlessStoragePath>> {
|
||||
return this.list(
|
||||
page,
|
||||
pageSize,
|
||||
sortField,
|
||||
sortReverse,
|
||||
Object.assign(extraParams, queryParamsFromFilterRules(filterRules))
|
||||
).pipe(
|
||||
map((results) => {
|
||||
return results
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
listAllFilteredIds(filterRules?: FilterRule[]): Observable<number[]> {
|
||||
return this.listFiltered(1, 100000, null, null, filterRules, {
|
||||
fields: 'id',
|
||||
}).pipe(map((response) => response.results.map((doc) => doc.id)))
|
||||
}
|
||||
|
||||
getPreviewUrl(id: number, original: boolean = false): string {
|
||||
let url = this.getResourceUrl(id, 'preview')
|
||||
if (this._searchQuery) url += `#search="${this._searchQuery}"`
|
||||
if (original) {
|
||||
url += '?original=true'
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
getThumbUrl(id: number): string {
|
||||
return this.getResourceUrl(id, 'thumb')
|
||||
}
|
||||
|
||||
getDownloadUrl(id: number, original: boolean = false): string {
|
||||
let url = this.getResourceUrl(id, 'download')
|
||||
if (original) {
|
||||
url += '?original=true'
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
update(o: PaperlessDocument): Observable<PaperlessDocument> {
|
||||
// we want to only set created_date
|
||||
o.created = undefined
|
||||
return super.update(o)
|
||||
}
|
||||
|
||||
uploadDocument(formData) {
|
||||
return this.http.post(
|
||||
this.getResourceUrl(null, 'post_document'),
|
||||
formData,
|
||||
{ reportProgress: true, observe: 'events' }
|
||||
)
|
||||
}
|
||||
|
||||
getMetadata(id: number): Observable<PaperlessDocumentMetadata> {
|
||||
return this.http.get<PaperlessDocumentMetadata>(
|
||||
this.getResourceUrl(id, 'metadata')
|
||||
)
|
||||
}
|
||||
|
||||
bulkEdit(ids: number[], method: string, args: any) {
|
||||
return this.http.post(this.getResourceUrl(null, 'bulk_edit'), {
|
||||
documents: ids,
|
||||
method: method,
|
||||
parameters: args,
|
||||
})
|
||||
}
|
||||
|
||||
getSelectionData(ids: number[]): Observable<SelectionData> {
|
||||
return this.http.post<SelectionData>(
|
||||
this.getResourceUrl(null, 'selection_data'),
|
||||
{ documents: ids }
|
||||
)
|
||||
}
|
||||
|
||||
getSuggestions(id: number): Observable<PaperlessDocumentSuggestions> {
|
||||
return this.http.get<PaperlessDocumentSuggestions>(
|
||||
this.getResourceUrl(id, 'suggestions')
|
||||
)
|
||||
}
|
||||
|
||||
bulkDownload(
|
||||
ids: number[],
|
||||
content = 'both',
|
||||
useFilenameFormatting: boolean = false
|
||||
) {
|
||||
return this.http.post(
|
||||
this.getResourceUrl(null, 'bulk_download'),
|
||||
{
|
||||
documents: ids,
|
||||
content: content,
|
||||
follow_formatting: useFilenameFormatting,
|
||||
},
|
||||
{ responseType: 'blob' }
|
||||
)
|
||||
}
|
||||
|
||||
public set searchQuery(query: string) {
|
||||
this._searchQuery = query
|
||||
}
|
||||
}
|
517
src-ui/src/app/services/storage-path-list-view.service.ts
Normal file
517
src-ui/src/app/services/storage-path-list-view.service.ts
Normal file
@ -0,0 +1,517 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ParamMap, Router } from '@angular/router'
|
||||
import { Observable } from 'rxjs'
|
||||
import {
|
||||
FilterRule,
|
||||
cloneFilterRules,
|
||||
filterRulesDiffer,
|
||||
isFullTextFilterRule,
|
||||
} from '../data/filter-rule'
|
||||
import { PaperlessDocument } from '../data/paperless-document'
|
||||
import { PaperlessSavedView } from '../data/paperless-saved-view'
|
||||
import { PaperlessStoragePath } from '../data/paperless-storage-path'
|
||||
import { SETTINGS_KEYS } from '../data/paperless-uisettings'
|
||||
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
|
||||
import { paramsFromViewState, paramsToViewState } from '../utils/query-params'
|
||||
import { CustomStoragePathService } from './rest/custom-storage-path.service'
|
||||
import { DOCUMENT_SORT_FIELDS, SelectionData } from './rest/document.service'
|
||||
import { SettingsService } from './settings.service'
|
||||
|
||||
/**
|
||||
* Captures the current state of the list view.
|
||||
*/
|
||||
export interface ListViewState {
|
||||
/**
|
||||
* Title of the document list view. Either "Documents" (localized) or the name of a saved view.
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* Current paginated list of storage paths displayed.
|
||||
*/
|
||||
storagePaths?: PaperlessStoragePath[]
|
||||
|
||||
currentPage: number
|
||||
|
||||
/**
|
||||
* Total amount of documents with the current filter rules. Used to calculate the number of pages.
|
||||
*/
|
||||
collectionSize?: number
|
||||
|
||||
/**
|
||||
* Currently selected sort field.
|
||||
*/
|
||||
sortField: string
|
||||
|
||||
/**
|
||||
* True if the list is sorted in reverse.
|
||||
*/
|
||||
sortReverse: boolean
|
||||
|
||||
/**
|
||||
* Filter rules for the current list view.
|
||||
*/
|
||||
filterRules: FilterRule[]
|
||||
|
||||
/**
|
||||
* Contains the IDs of all selected documents.
|
||||
*/
|
||||
selected?: Set<number>
|
||||
}
|
||||
|
||||
/**
|
||||
* This service manages the document list which is displayed using the document list view.
|
||||
*
|
||||
* This service also serves saved views by transparently switching between the document list
|
||||
* and saved views on request. See below.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StoragePathListViewService {
|
||||
isReloading: boolean = false
|
||||
initialized: boolean = false
|
||||
error: string = null
|
||||
|
||||
rangeSelectionAnchorIndex: number
|
||||
lastRangeSelectionToIndex: number
|
||||
|
||||
selectionData?: SelectionData
|
||||
|
||||
currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
|
||||
|
||||
private listViewStates: Map<number, ListViewState> = new Map()
|
||||
|
||||
private _activeSavedViewId: number = null
|
||||
|
||||
get activeSavedViewId() {
|
||||
return this._activeSavedViewId
|
||||
}
|
||||
|
||||
get activeSavedViewTitle() {
|
||||
return this.activeListViewState.title
|
||||
}
|
||||
|
||||
constructor(
|
||||
private storagePathService: CustomStoragePathService,
|
||||
private settings: SettingsService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
private defaultListViewState(): ListViewState {
|
||||
return {
|
||||
title: null,
|
||||
storagePaths: [],
|
||||
currentPage: 1,
|
||||
collectionSize: null,
|
||||
sortField: 'created',
|
||||
sortReverse: true,
|
||||
filterRules: [],
|
||||
selected: new Set<number>(),
|
||||
}
|
||||
}
|
||||
|
||||
private get activeListViewState() {
|
||||
if (!this.listViewStates.has(this._activeSavedViewId)) {
|
||||
this.listViewStates.set(
|
||||
this._activeSavedViewId,
|
||||
this.defaultListViewState()
|
||||
)
|
||||
}
|
||||
return this.listViewStates.get(this._activeSavedViewId)
|
||||
}
|
||||
|
||||
activateSavedView(view: PaperlessSavedView) {
|
||||
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
|
||||
if (view) {
|
||||
this._activeSavedViewId = view.id
|
||||
this.loadSavedView(view)
|
||||
} else {
|
||||
this._activeSavedViewId = null
|
||||
}
|
||||
}
|
||||
|
||||
activateSavedViewWithQueryParams(
|
||||
view: PaperlessSavedView,
|
||||
queryParams: ParamMap
|
||||
) {
|
||||
const viewState = paramsToViewState(queryParams)
|
||||
this.activateSavedView(view)
|
||||
this.currentPage = viewState.currentPage
|
||||
}
|
||||
|
||||
loadSavedView(view: PaperlessSavedView, closeCurrentView: boolean = false) {
|
||||
if (closeCurrentView) {
|
||||
this._activeSavedViewId = null
|
||||
}
|
||||
|
||||
this.activeListViewState.filterRules = cloneFilterRules(view.filter_rules)
|
||||
this.activeListViewState.sortField = view.sort_field
|
||||
this.activeListViewState.sortReverse = view.sort_reverse
|
||||
if (this._activeSavedViewId) {
|
||||
this.activeListViewState.title = view.name
|
||||
}
|
||||
|
||||
this.reduceSelectionToFilter()
|
||||
|
||||
if (!this.router.routerState.snapshot.url.includes('/view/')) {
|
||||
this.router.navigate(['view', view.id])
|
||||
}
|
||||
}
|
||||
|
||||
loadFromQueryParams(queryParams: ParamMap) {
|
||||
const paramsEmpty: boolean = queryParams.keys.length == 0
|
||||
let newState: ListViewState = this.listViewStates.get(
|
||||
this._activeSavedViewId
|
||||
)
|
||||
if (!paramsEmpty) newState = paramsToViewState(queryParams)
|
||||
if (newState == undefined) newState = this.defaultListViewState() // if nothing in local storage
|
||||
|
||||
// only reload if things have changed
|
||||
if (
|
||||
!this.initialized ||
|
||||
paramsEmpty ||
|
||||
this.activeListViewState.sortField !== newState.sortField ||
|
||||
this.activeListViewState.sortReverse !== newState.sortReverse ||
|
||||
this.activeListViewState.currentPage !== newState.currentPage ||
|
||||
filterRulesDiffer(
|
||||
this.activeListViewState.filterRules,
|
||||
newState.filterRules
|
||||
)
|
||||
) {
|
||||
this.activeListViewState.filterRules = newState.filterRules
|
||||
this.activeListViewState.sortField = newState.sortField
|
||||
this.activeListViewState.sortReverse = newState.sortReverse
|
||||
this.activeListViewState.currentPage = newState.currentPage
|
||||
this.reload(null, paramsEmpty) // update the params if there arent any
|
||||
}
|
||||
}
|
||||
|
||||
reload(onFinish?, updateQueryParams: boolean = true) {
|
||||
this.isReloading = true
|
||||
this.error = null
|
||||
let activeListViewState = this.activeListViewState
|
||||
this.storagePathService
|
||||
.listFiltered(
|
||||
activeListViewState.currentPage,
|
||||
this.currentPageSize,
|
||||
activeListViewState.sortField,
|
||||
activeListViewState.sortReverse,
|
||||
activeListViewState.filterRules,
|
||||
{ truncate_content: true }
|
||||
)
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
console.log('list filtered result:', result)
|
||||
this.initialized = true
|
||||
this.isReloading = false
|
||||
activeListViewState.collectionSize = result.count
|
||||
activeListViewState.storagePaths = result.results
|
||||
|
||||
this.storagePathService
|
||||
.getSelectionData(result.results.map((d) => d.id))
|
||||
.subscribe({
|
||||
next: (selectionData) => {
|
||||
this.selectionData = selectionData
|
||||
},
|
||||
error: () => {
|
||||
this.selectionData = null
|
||||
},
|
||||
})
|
||||
|
||||
// if (updateQueryParams && !this._activeSavedViewId) {
|
||||
// let base = ['/documents']
|
||||
// this.router.navigate(base, {
|
||||
// queryParams: paramsFromViewState(activeListViewState),
|
||||
// replaceUrl: !this.router.routerState.snapshot.url.includes('?'), // in case navigating from params-less /documents
|
||||
// })
|
||||
// } else if (this._activeSavedViewId) {
|
||||
// this.router.navigate([], {
|
||||
// queryParams: paramsFromViewState(activeListViewState, true),
|
||||
// queryParamsHandling: 'merge',
|
||||
// })
|
||||
// }
|
||||
|
||||
if (onFinish) {
|
||||
onFinish()
|
||||
}
|
||||
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
|
||||
},
|
||||
error: (error) => {
|
||||
this.isReloading = false
|
||||
if (activeListViewState.currentPage != 1 && error.status == 404) {
|
||||
// this happens when applying a filter: the current page might not be available anymore due to the reduced result set.
|
||||
activeListViewState.currentPage = 1
|
||||
this.reload()
|
||||
} else {
|
||||
this.selectionData = null
|
||||
let errorMessage
|
||||
if (
|
||||
typeof error.error !== 'string' &&
|
||||
Object.keys(error.error).length > 0
|
||||
) {
|
||||
// e.g. { archive_serial_number: Array<string> }
|
||||
errorMessage = Object.keys(error.error)
|
||||
.map((fieldName) => {
|
||||
const fieldError: Array<string> = error.error[fieldName]
|
||||
return `${
|
||||
DOCUMENT_SORT_FIELDS.find((f) => f.field == fieldName)?.name
|
||||
}: ${fieldError[0]}`
|
||||
})
|
||||
.join(', ')
|
||||
} else {
|
||||
errorMessage = error.error
|
||||
}
|
||||
this.error = errorMessage
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
set filterRules(filterRules: FilterRule[]) {
|
||||
if (
|
||||
!isFullTextFilterRule(filterRules) &&
|
||||
this.activeListViewState.sortField == 'score'
|
||||
) {
|
||||
this.activeListViewState.sortField = 'created'
|
||||
}
|
||||
this.activeListViewState.filterRules = filterRules
|
||||
this.reload()
|
||||
this.reduceSelectionToFilter()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
get filterRules(): FilterRule[] {
|
||||
return this.activeListViewState.filterRules
|
||||
}
|
||||
|
||||
set sortField(field: string) {
|
||||
this.activeListViewState.sortField = field
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
get sortField(): string {
|
||||
return this.activeListViewState.sortField
|
||||
}
|
||||
|
||||
set sortReverse(reverse: boolean) {
|
||||
this.activeListViewState.sortReverse = reverse
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
get sortReverse(): boolean {
|
||||
return this.activeListViewState.sortReverse
|
||||
}
|
||||
|
||||
get collectionSize(): number {
|
||||
return this.activeListViewState.collectionSize
|
||||
}
|
||||
|
||||
get currentPage(): number {
|
||||
return this.activeListViewState.currentPage
|
||||
}
|
||||
|
||||
set currentPage(page: number) {
|
||||
if (this.activeListViewState.currentPage == page) return
|
||||
this.activeListViewState.currentPage = page
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
get documents(): PaperlessDocument[] {
|
||||
return this.activeListViewState.storagePaths
|
||||
}
|
||||
|
||||
get selected(): Set<number> {
|
||||
return this.activeListViewState.selected
|
||||
}
|
||||
|
||||
setSort(field: string, reverse: boolean) {
|
||||
this.activeListViewState.sortField = field
|
||||
this.activeListViewState.sortReverse = reverse
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
private saveDocumentListView() {
|
||||
if (this._activeSavedViewId == null) {
|
||||
let savedState: ListViewState = {
|
||||
collectionSize: this.activeListViewState.collectionSize,
|
||||
currentPage: this.activeListViewState.currentPage,
|
||||
filterRules: this.activeListViewState.filterRules,
|
||||
sortField: this.activeListViewState.sortField,
|
||||
sortReverse: this.activeListViewState.sortReverse,
|
||||
}
|
||||
localStorage.setItem(
|
||||
DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG,
|
||||
JSON.stringify(savedState)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
quickFilter(filterRules: FilterRule[]) {
|
||||
this._activeSavedViewId = null
|
||||
this.filterRules = filterRules
|
||||
}
|
||||
|
||||
getLastPage(): number {
|
||||
return Math.ceil(this.collectionSize / this.currentPageSize)
|
||||
}
|
||||
|
||||
hasNext(doc: number) {
|
||||
if (this.documents) {
|
||||
let index = this.documents.findIndex((d) => d.id == doc)
|
||||
return (
|
||||
index != -1 &&
|
||||
(this.currentPage < this.getLastPage() ||
|
||||
index + 1 < this.documents.length)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
hasPrevious(doc: number) {
|
||||
if (this.documents) {
|
||||
let index = this.documents.findIndex((d) => d.id == doc)
|
||||
return index != -1 && !(index == 0 && this.currentPage == 1)
|
||||
}
|
||||
}
|
||||
|
||||
getNext(currentDocId: number): Observable<number> {
|
||||
return new Observable((nextDocId) => {
|
||||
if (this.documents != null) {
|
||||
let index = this.documents.findIndex((d) => d.id == currentDocId)
|
||||
|
||||
if (index != -1 && index + 1 < this.documents.length) {
|
||||
nextDocId.next(this.documents[index + 1].id)
|
||||
nextDocId.complete()
|
||||
} else if (index != -1 && this.currentPage < this.getLastPage()) {
|
||||
this.currentPage += 1
|
||||
this.reload(() => {
|
||||
nextDocId.next(this.documents[0].id)
|
||||
nextDocId.complete()
|
||||
})
|
||||
} else {
|
||||
nextDocId.complete()
|
||||
}
|
||||
} else {
|
||||
nextDocId.complete()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getPrevious(currentDocId: number): Observable<number> {
|
||||
return new Observable((prevDocId) => {
|
||||
if (this.documents != null) {
|
||||
let index = this.documents.findIndex((d) => d.id == currentDocId)
|
||||
|
||||
if (index != 0) {
|
||||
prevDocId.next(this.documents[index - 1].id)
|
||||
prevDocId.complete()
|
||||
} else if (this.currentPage > 1) {
|
||||
this.currentPage -= 1
|
||||
this.reload(() => {
|
||||
prevDocId.next(this.documents[this.documents.length - 1].id)
|
||||
prevDocId.complete()
|
||||
})
|
||||
} else {
|
||||
prevDocId.complete()
|
||||
}
|
||||
} else {
|
||||
prevDocId.complete()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updatePageSize() {
|
||||
let newPageSize = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
|
||||
if (newPageSize != this.currentPageSize) {
|
||||
this.currentPageSize = newPageSize
|
||||
}
|
||||
}
|
||||
|
||||
selectNone() {
|
||||
this.selected.clear()
|
||||
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
|
||||
}
|
||||
|
||||
reduceSelectionToFilter() {
|
||||
if (this.selected.size > 0) {
|
||||
this.storagePathService
|
||||
.listAllFilteredIds(this.filterRules)
|
||||
.subscribe((ids) => {
|
||||
for (let id of this.selected) {
|
||||
if (!ids.includes(id)) {
|
||||
this.selected.delete(id)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
selectAll() {
|
||||
this.storagePathService
|
||||
.listAllFilteredIds(this.filterRules)
|
||||
.subscribe((ids) => ids.forEach((id) => this.selected.add(id)))
|
||||
}
|
||||
|
||||
selectPage() {
|
||||
this.selected.clear()
|
||||
this.documents.forEach((doc) => {
|
||||
this.selected.add(doc.id)
|
||||
})
|
||||
}
|
||||
|
||||
isSelected(d: PaperlessDocument) {
|
||||
return this.selected.has(d.id)
|
||||
}
|
||||
|
||||
toggleSelected(d: PaperlessDocument): void {
|
||||
if (this.selected.has(d.id)) this.selected.delete(d.id)
|
||||
else this.selected.add(d.id)
|
||||
this.rangeSelectionAnchorIndex = this.documentIndexInCurrentView(d.id)
|
||||
this.lastRangeSelectionToIndex = null
|
||||
}
|
||||
|
||||
selectRangeTo(d: PaperlessDocument) {
|
||||
if (this.rangeSelectionAnchorIndex !== null) {
|
||||
const documentToIndex = this.documentIndexInCurrentView(d.id)
|
||||
const fromIndex = Math.min(
|
||||
this.rangeSelectionAnchorIndex,
|
||||
documentToIndex
|
||||
)
|
||||
const toIndex = Math.max(this.rangeSelectionAnchorIndex, documentToIndex)
|
||||
|
||||
if (this.lastRangeSelectionToIndex !== null) {
|
||||
// revert the old selection
|
||||
this.documents
|
||||
.slice(
|
||||
Math.min(
|
||||
this.rangeSelectionAnchorIndex,
|
||||
this.lastRangeSelectionToIndex
|
||||
),
|
||||
Math.max(
|
||||
this.rangeSelectionAnchorIndex,
|
||||
this.lastRangeSelectionToIndex
|
||||
) + 1
|
||||
)
|
||||
.forEach((d) => {
|
||||
this.selected.delete(d.id)
|
||||
})
|
||||
}
|
||||
|
||||
this.documents.slice(fromIndex, toIndex + 1).forEach((d) => {
|
||||
this.selected.add(d.id)
|
||||
})
|
||||
this.lastRangeSelectionToIndex = documentToIndex
|
||||
} else {
|
||||
// e.g. shift key but was first click
|
||||
this.toggleSelected(d)
|
||||
}
|
||||
}
|
||||
|
||||
documentIndexInCurrentView(documentID: number): number {
|
||||
return this.documents.map((d) => d.id).indexOf(documentID)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user