Merge branch 'dev' into feature-websockets-status

This commit is contained in:
jonaswinkler
2021-01-04 22:45:56 +01:00
257 changed files with 18414 additions and 3310 deletions

View File

@@ -1,15 +1,17 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { cloneFilterRules, FilterRule } from '../data/filter-rule';
import { PaperlessDocument } from '../data/paperless-document';
import { SavedViewConfig } from '../data/saved-view-config';
import { DOCUMENT_LIST_SERVICE, GENERAL_SETTINGS } from '../data/storage-keys';
import { PaperlessSavedView } from '../data/paperless-saved-view';
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys';
import { DocumentService } from './rest/document.service';
import { SettingsService, SETTINGS_KEYS } from './settings.service';
/**
* 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.
*/
@@ -23,27 +25,31 @@ export class DocumentListViewService {
isReloading: boolean = false
documents: PaperlessDocument[] = []
currentPage = 1
currentPageSize: number = +localStorage.getItem(GENERAL_SETTINGS.DOCUMENT_LIST_SIZE) || GENERAL_SETTINGS.DOCUMENT_LIST_SIZE_DEFAULT
currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
collectionSize: number
/**
* This is the current config for the document list. The service will always remember the last settings used for the document list.
*/
private _documentListViewConfig: SavedViewConfig
private _documentListViewConfig: PaperlessSavedView
/**
* Optionally, this is the currently selected saved view, which might be null.
*/
private _savedViewConfig: SavedViewConfig
private _savedViewConfig: PaperlessSavedView
get savedView() {
get savedView(): PaperlessSavedView {
return this._savedViewConfig
}
set savedView(value) {
if (value) {
set savedView(value: PaperlessSavedView) {
if (value && !this._savedViewConfig || value && value.id != this._savedViewConfig.id) {
//saved view inactive and should be active now, or saved view active, but a different view is requested
//this is here so that we don't modify value, which might be the actual instance of the saved view.
this.selectNone()
this._savedViewConfig = Object.assign({}, value)
} else {
} else if (this._savedViewConfig && !value) {
//saved view active, but document list requested
this.selectNone()
this._savedViewConfig = null
}
}
@@ -53,7 +59,7 @@ export class DocumentListViewService {
}
get savedViewTitle() {
return this.savedView?.title
return this.savedView?.name
}
get documentListView() {
@@ -75,11 +81,11 @@ export class DocumentListViewService {
return this.savedView || this.documentListView
}
load(config: SavedViewConfig) {
this.view.filterRules = cloneFilterRules(config.filterRules)
this.view.sortDirection = config.sortDirection
this.view.sortField = config.sortField
this.reload()
load(view: PaperlessSavedView) {
this.documentListView.filter_rules = cloneFilterRules(view.filter_rules)
this.documentListView.sort_reverse = view.sort_reverse
this.documentListView.sort_field = view.sort_field
this.saveDocumentListView()
}
clear() {
@@ -90,12 +96,12 @@ export class DocumentListViewService {
reload(onFinish?) {
this.isReloading = true
this.documentService.list(
this.documentService.listFiltered(
this.currentPage,
this.currentPageSize,
this.view.sortField,
this.view.sortDirection,
this.view.filterRules).subscribe(
this.view.sort_field,
this.view.sort_reverse,
this.view.filter_rules).subscribe(
result => {
this.collectionSize = result.count
this.documents = result.results
@@ -105,7 +111,8 @@ export class DocumentListViewService {
this.isReloading = false
},
error => {
if (error.error['detail'] == 'Invalid page.') {
if (this.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.
this.currentPage = 1
this.reload()
}
@@ -116,39 +123,55 @@ export class DocumentListViewService {
set filterRules(filterRules: FilterRule[]) {
//we're going to clone the filterRules object, since we don't
//want changes in the filter editor to propagate into here right away.
this.view.filterRules = cloneFilterRules(filterRules)
this.view.filter_rules = filterRules
this.reload()
this.reduceSelectionToFilter()
this.saveDocumentListView()
}
get filterRules(): FilterRule[] {
return cloneFilterRules(this.view.filterRules)
return this.view.filter_rules
}
set sortField(field: string) {
this.view.sortField = field
this.view.sort_field = field
this.saveDocumentListView()
this.reload()
}
get sortField(): string {
return this.view.sortField
return this.view.sort_field
}
set sortDirection(direction: string) {
this.view.sortDirection = direction
set sortReverse(reverse: boolean) {
this.view.sort_reverse = reverse
this.saveDocumentListView()
this.reload()
}
get sortDirection(): string {
return this.view.sortDirection
get sortReverse(): boolean {
return this.view.sort_reverse
}
setSort(field: string, reverse: boolean) {
this.view.sort_field = field
this.view.sort_reverse = reverse
this.saveDocumentListView()
this.reload()
}
private saveDocumentListView() {
sessionStorage.setItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG, JSON.stringify(this.documentListView))
}
quickFilter(filterRules: FilterRule[]) {
this.savedView = null
this.view.filter_rules = filterRules
this.reduceSelectionToFilter()
this.saveDocumentListView()
this.router.navigate(["documents"])
}
getLastPage(): number {
return Math.ceil(this.collectionSize / this.currentPageSize)
}
@@ -185,14 +208,56 @@ export class DocumentListViewService {
}
updatePageSize() {
let newPageSize = +localStorage.getItem(GENERAL_SETTINGS.DOCUMENT_LIST_SIZE) || GENERAL_SETTINGS.DOCUMENT_LIST_SIZE_DEFAULT
let newPageSize = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
if (newPageSize != this.currentPageSize) {
this.currentPageSize = newPageSize
//this.reload()
}
}
constructor(private documentService: DocumentService) {
selected = new Set<number>()
selectNone() {
this.selected.clear()
}
reduceSelectionToFilter() {
if (this.selected.size > 0) {
this.documentService.listAllFilteredIds(this.filterRules).subscribe(ids => {
let subset = new Set<number>()
for (let id of ids) {
if (this.selected.has(id)) {
subset.add(id)
}
}
this.selected = subset
})
}
}
selectAll() {
this.documentService.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)
}
setSelected(d: PaperlessDocument, value: boolean) {
if (value) {
this.selected.add(d.id)
} else if (!value) {
this.selected.delete(d.id)
}
}
constructor(private documentService: DocumentService, private settings: SettingsService, private router: Router) {
let documentListViewConfigJson = sessionStorage.getItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
if (documentListViewConfigJson) {
try {
@@ -202,11 +267,11 @@ export class DocumentListViewService {
this.documentListView = null
}
}
if (!this.documentListView) {
if (!this.documentListView || this.documentListView.filter_rules == null || this.documentListView.sort_reverse == null || this.documentListView.sort_field == null) {
this.documentListView = {
filterRules: [],
sortDirection: 'des',
sortField: 'created'
filter_rules: [],
sort_reverse: true,
sort_field: 'created'
}
}
}

View File

@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { PaperlessDocument } from '../data/paperless-document';
import { OPEN_DOCUMENT_SERVICE } from '../data/storage-keys';
import { DocumentService } from './rest/document.service';
@Injectable({
providedIn: 'root'
@@ -9,7 +10,7 @@ export class OpenDocumentsService {
private MAX_OPEN_DOCUMENTS = 5
constructor() {
constructor(private documentService: DocumentService) {
if (sessionStorage.getItem(OPEN_DOCUMENT_SERVICE.DOCUMENTS)) {
try {
this.openDocuments = JSON.parse(sessionStorage.getItem(OPEN_DOCUMENT_SERVICE.DOCUMENTS))
@@ -22,6 +23,18 @@ export class OpenDocumentsService {
private openDocuments: PaperlessDocument[] = []
refreshDocument(id: number) {
let index = this.openDocuments.findIndex(doc => doc.id == id)
if (index > -1) {
this.documentService.get(id).subscribe(doc => {
this.openDocuments[index] = doc
}, error => {
this.openDocuments.splice(index, 1)
this.save()
})
}
}
getOpenDocuments(): PaperlessDocument[] {
return this.openDocuments
}

View File

@@ -1,5 +1,5 @@
import { HttpClient, HttpParams } from '@angular/common/http'
import { Observable, of, Subject } from 'rxjs'
import { Observable } from 'rxjs'
import { map, publishReplay, refCount } from 'rxjs/operators'
import { ObjectWithId } from 'src/app/data/object-with-id'
import { Results } from 'src/app/data/results'
@@ -22,17 +22,15 @@ export abstract class AbstractPaperlessService<T extends ObjectWithId> {
return url
}
private getOrderingQueryParam(sortField: string, sortDirection: string) {
if (sortField && sortDirection) {
return (sortDirection == 'des' ? '-' : '') + sortField
} else if (sortField) {
return sortField
private getOrderingQueryParam(sortField: string, sortReverse: boolean) {
if (sortField) {
return (sortReverse ? '-' : '') + sortField
} else {
return null
}
}
list(page?: number, pageSize?: number, sortField?: string, sortDirection?: string, extraParams?): Observable<Results<T>> {
list(page?: number, pageSize?: number, sortField?: string, sortReverse?: boolean, extraParams?): Observable<Results<T>> {
let httpParams = new HttpParams()
if (page) {
httpParams = httpParams.set('page', page.toString())
@@ -40,7 +38,7 @@ export abstract class AbstractPaperlessService<T extends ObjectWithId> {
if (pageSize) {
httpParams = httpParams.set('page_size', pageSize.toString())
}
let ordering = this.getOrderingQueryParam(sortField, sortDirection)
let ordering = this.getOrderingQueryParam(sortField, sortReverse)
if (ordering) {
httpParams = httpParams.set('ordering', ordering)
}
@@ -54,9 +52,9 @@ export abstract class AbstractPaperlessService<T extends ObjectWithId> {
private _listAll: Observable<Results<T>>
listAll(ordering?: string, extraParams?): Observable<Results<T>> {
listAll(sortField?: string, sortReverse?: boolean, extraParams?): Observable<Results<T>> {
if (!this._listAll) {
this._listAll = this.list(1, 100000, ordering, extraParams).pipe(
this._listAll = this.list(1, 100000, sortField, sortReverse, extraParams).pipe(
publishReplay(1),
refCount()
)
@@ -76,22 +74,32 @@ export abstract class AbstractPaperlessService<T extends ObjectWithId> {
)
}
clearCache() {
this._listAll = null
}
get(id: number): Observable<T> {
return this.http.get<T>(this.getResourceUrl(id))
}
create(o: T): Observable<T> {
this._listAll = null
this.clearCache()
return this.http.post<T>(this.getResourceUrl(), o)
}
delete(o: T): Observable<any> {
this._listAll = null
this.clearCache()
return this.http.delete(this.getResourceUrl(o.id))
}
update(o: T): Observable<T> {
this._listAll = null
this.clearCache()
return this.http.put<T>(this.getResourceUrl(o.id), o)
}
}
patch(o: T): Observable<T> {
this.clearCache()
return this.http.patch<T>(this.getResourceUrl(o.id), o)
}
}

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata';
import { AbstractPaperlessService } from './abstract-paperless-service';
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Results } from 'src/app/data/results';
import { FilterRule } from 'src/app/data/filter-rule';
@@ -10,21 +10,28 @@ import { map } from 'rxjs/operators';
import { CorrespondentService } from './correspondent.service';
import { DocumentTypeService } from './document-type.service';
import { TagService } from './tag.service';
import { FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type';
export const DOCUMENT_SORT_FIELDS = [
{ field: "correspondent__name", name: "Correspondent" },
{ field: "document_type__name", name: "Document type" },
{ field: 'title', name: 'Title' },
{ field: 'archive_serial_number', name: 'ASN' },
{ field: 'created', name: 'Created' },
{ field: 'added', name: 'Added' },
{ field: 'modified', name: 'Modified' }
{ field: 'archive_serial_number', name: $localize`ASN` },
{ field: "correspondent__name", name: $localize`Correspondent` },
{ field: 'title', name: $localize`Title` },
{ field: "document_type__name", name: $localize`Document type` },
{ field: 'created', name: $localize`Created` },
{ field: 'added', name: $localize`Added` },
{ field: 'modified', name: $localize`Modified` }
]
export const SORT_DIRECTION_ASCENDING = "asc"
export const SORT_DIRECTION_DESCENDING = "des"
export interface SelectionDataItem {
id: number
document_count: number
}
export interface SelectionData {
selected_correspondents: SelectionDataItem[]
selected_tags: SelectionDataItem[]
selected_document_types: SelectionDataItem[]
}
@Injectable({
providedIn: 'root'
@@ -39,10 +46,13 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
if (filterRules) {
let params = {}
for (let rule of filterRules) {
if (rule.type.multi) {
params[rule.type.filtervar] = params[rule.type.filtervar] ? params[rule.type.filtervar] + "," + rule.value : rule.value
let ruleType = FILTER_RULE_TYPES.find(t => t.id == rule.rule_type)
if (ruleType.multi) {
params[ruleType.filtervar] = params[ruleType.filtervar] ? params[ruleType.filtervar] + "," + rule.value : rule.value
} else if (ruleType.isnull_filtervar && rule.value == null) {
params[ruleType.isnull_filtervar] = true
} else {
params[rule.type.filtervar] = rule.value
params[ruleType.filtervar] = rule.value
}
}
return params
@@ -64,8 +74,8 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
return doc
}
list(page?: number, pageSize?: number, sortField?: string, sortDirection?: string, filterRules?: FilterRule[]): Observable<Results<PaperlessDocument>> {
return super.list(page, pageSize, sortField, sortDirection, this.filterRulesToQueryParams(filterRules)).pipe(
listFiltered(page?: number, pageSize?: number, sortField?: string, sortReverse?: boolean, filterRules?: FilterRule[], extraParams = {}): Observable<Results<PaperlessDocument>> {
return this.list(page, pageSize, sortField, sortReverse, Object.assign(extraParams, this.filterRulesToQueryParams(filterRules))).pipe(
map(results => {
results.results.forEach(doc => this.addObservablesToDocument(doc))
return results
@@ -73,6 +83,12 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
)
}
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 (original) {
@@ -94,11 +110,23 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
}
uploadDocument(formData) {
return this.http.post(this.getResourceUrl(null, 'post_document'), 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})
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { SavedViewService } from './saved-view.service';
describe('SavedViewService', () => {
let service: SavedViewService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SavedViewService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,59 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { combineLatest, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
import { AbstractPaperlessService } from './abstract-paperless-service';
@Injectable({
providedIn: 'root'
})
export class SavedViewService extends AbstractPaperlessService<PaperlessSavedView> {
constructor(http: HttpClient) {
super(http, 'saved_views')
this.reload()
}
private reload() {
this.listAll().subscribe(r => this.savedViews = r.results)
}
private savedViews: PaperlessSavedView[] = []
get allViews() {
return this.savedViews
}
get sidebarViews() {
return this.savedViews.filter(v => v.show_in_sidebar)
}
get dashboardViews() {
return this.savedViews.filter(v => v.show_on_dashboard)
}
create(o: PaperlessSavedView) {
return super.create(o).pipe(
tap(() => this.reload())
)
}
update(o: PaperlessSavedView) {
return super.update(o).pipe(
tap(() => this.reload())
)
}
patchMany(objects: PaperlessSavedView[]): Observable<PaperlessSavedView[]> {
return combineLatest(objects.map(o => super.patch(o))).pipe(
tap(() => this.reload())
)
}
delete(o: PaperlessSavedView) {
return super.delete(o).pipe(
tap(() => this.reload())
)
}
}

View File

@@ -15,11 +15,17 @@ export class SearchService {
constructor(private http: HttpClient, private documentService: DocumentService) { }
search(query: string, page?: number): Observable<SearchResult> {
let httpParams = new HttpParams().set('query', query)
search(query: string, page?: number, more_like?: number): Observable<SearchResult> {
let httpParams = new HttpParams()
if (query) {
httpParams = httpParams.set('query', query)
}
if (page) {
httpParams = httpParams.set('page', page.toString())
}
if (more_like) {
httpParams = httpParams.set('more_like', more_like.toString())
}
return this.http.get<SearchResult>(`${environment.apiBaseUrl}search/`, {params: httpParams}).pipe(
map(result => {
result.results.forEach(hit => this.documentService.addObservablesToDocument(hit.document))

View File

@@ -1,16 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { SavedViewConfigService } from './saved-view-config.service';
describe('SavedViewConfigService', () => {
let service: SavedViewConfigService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SavedViewConfigService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -1,66 +0,0 @@
import { Injectable } from '@angular/core';
import { v4 as uuidv4 } from 'uuid';
import { SavedViewConfig } from '../data/saved-view-config';
@Injectable({
providedIn: 'root'
})
export class SavedViewConfigService {
constructor() {
let savedConfigs = localStorage.getItem('saved-view-config-service:savedConfigs')
if (savedConfigs) {
try {
this.configs = JSON.parse(savedConfigs)
} catch (e) {
this.configs = []
}
}
}
private configs: SavedViewConfig[] = []
getConfigs(): SavedViewConfig[] {
return this.configs
}
getDashboardConfigs(): SavedViewConfig[] {
return this.configs.filter(sf => sf.showInDashboard)
}
getSideBarConfigs(): SavedViewConfig[] {
return this.configs.filter(sf => sf.showInSideBar)
}
getConfig(id: string): SavedViewConfig {
return this.configs.find(sf => sf.id == id)
}
newConfig(config: SavedViewConfig) {
config.id = uuidv4()
this.configs.push(config)
this.save()
}
updateConfig(config: SavedViewConfig) {
let savedConfig = this.configs.find(c => c.id == config.id)
if (savedConfig) {
Object.assign(savedConfig, config)
this.save()
}
}
private save() {
localStorage.setItem('saved-view-config-service:savedConfigs', JSON.stringify(this.configs))
}
deleteConfig(config: SavedViewConfig) {
let index = this.configs.findIndex(vc => vc.id == config.id)
if (index != -1) {
this.configs.splice(index, 1)
this.save()
}
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { SettingsService } from './settings.service';
describe('SettingsService', () => {
let service: SettingsService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SettingsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,88 @@
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core';
export interface PaperlessSettings {
key: string
type: string
default: any
}
export const SETTINGS_KEYS = {
BULK_EDIT_CONFIRMATION_DIALOGS: 'general-settings:bulk-edit:confirmation-dialogs',
BULK_EDIT_APPLY_ON_CLOSE: 'general-settings:bulk-edit:apply-on-close',
DOCUMENT_LIST_SIZE: 'general-settings:documentListSize',
DARK_MODE_USE_SYSTEM: 'general-settings:dark-mode:use-system',
DARK_MODE_ENABLED: 'general-settings:dark-mode:enabled'
}
const SETTINGS: PaperlessSettings[] = [
{key: SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS, type: "boolean", default: true},
{key: SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, type: "boolean", default: false},
{key: SETTINGS_KEYS.DOCUMENT_LIST_SIZE, type: "number", default: 50},
{key: SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, type: "boolean", default: true},
{key: SETTINGS_KEYS.DARK_MODE_ENABLED, type: "boolean", default: false}
]
@Injectable({
providedIn: 'root'
})
export class SettingsService {
private renderer: Renderer2;
constructor(
private rendererFactory: RendererFactory2,
@Inject(DOCUMENT) private document
) {
this.renderer = rendererFactory.createRenderer(null, null);
this.updateDarkModeSettings()
}
updateDarkModeSettings(): void {
let darkModeUseSystem = this.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM)
let darkModeEnabled = this.get(SETTINGS_KEYS.DARK_MODE_ENABLED)
if (darkModeUseSystem) {
this.renderer.addClass(this.document.body, 'color-scheme-system')
this.renderer.removeClass(this.document.body, 'color-scheme-dark')
} else {
this.renderer.removeClass(this.document.body, 'color-scheme-system')
darkModeEnabled ? this.renderer.addClass(this.document.body, 'color-scheme-dark') : this.renderer.removeClass(this.document.body, 'color-scheme-dark')
}
}
get(key: string): any {
let setting = SETTINGS.find(s => s.key == key)
if (!setting) {
return null
}
let value = localStorage.getItem(key)
if (value != null) {
switch (setting.type) {
case "boolean":
return JSON.parse(value)
case "number":
return +value
case "string":
return value
default:
return value
}
} else {
return setting.default
}
}
set(key: string, value: any) {
localStorage.setItem(key, value.toString())
}
unset(key: string) {
localStorage.removeItem(key)
}
}

View File

@@ -7,7 +7,7 @@ export interface Toast {
content: string
delay?: number
delay: number
action?: any
@@ -26,17 +26,17 @@ export class ToastService {
private toastsSubject: Subject<Toast[]> = new Subject()
showToast(toast: Toast) {
show(toast: Toast) {
this.toasts.push(toast)
this.toastsSubject.next(this.toasts)
}
showInfo(message: string) {
this.showToast({title: "Information", content: message, delay: 5000})
showError(content: string, delay: number = 10000) {
this.show({title: $localize`Error`, content: content, delay: delay})
}
showError(message: string) {
this.showToast({title: "Error", content: message, delay: 10000})
showInfo(content: string, delay: number = 5000) {
this.show({title: $localize`Information`, content: content, delay: delay})
}
closeToast(toast: Toast) {