import { animate , state , style , transition , trigger } from '@angular/animations' ; import { Component , NgZone , OnDestroy , OnInit , ViewChild , ViewEncapsulation } from '@angular/core' ; import { MatDialog , MatDialogRef } from '@angular/material/dialog' ; import { MatTabChangeEvent } from '@angular/material/tabs' ; import { SplitAreaDirective , SplitComponent } from 'angular-split' ; import { OpenDialogReturnValue , remote } from 'electron' ; import { Subject , Subscription } from 'rxjs' ; import { debounceTime } from 'rxjs/internal/operators' ; import { Constants } from '../../core/constants' ; import { Operation } from '../../core/enums' ; import { Logger } from '../../core/logger' ; import { ProductInformation } from '../../core/product-information' ; import { SelectionWatcher } from '../../core/selection-watcher' ; import { Settings } from '../../core/settings' ; import { Note } from '../../data/entities/note' ; import { Notebook } from '../../data/entities/notebook' ; import { AppearanceService } from '../../services/appearance/appearance.service' ; import { CollectionService } from '../../services/collection/collection.service' ; import { FileService } from '../../services/file/file.service' ; import { NoteMarkResult } from '../../services/results/note-mark-result' ; import { NoteOperationResult } from '../../services/results/note-operation-result' ; import { NotesCountResult } from '../../services/results/notes-count-result' ; import { SnackBarService } from '../../services/snack-bar/snack-bar.service' ; import { TranslatorService } from '../../services/translator/translator.service' ; import { UpdateService } from '../../services/update/update.service' ; import { ConfirmationDialogComponent } from '../dialogs/confirmation-dialog/confirmation-dialog.component' ; import { ErrorDialogComponent } from '../dialogs/error-dialog/error-dialog.component' ; import { InputDialogComponent } from '../dialogs/input-dialog/input-dialog.component' ; import { RenameNotebookDialogComponent } from '../dialogs/rename-notebook-dialog/rename-notebook-dialog.component' ; @ Component ( { selector : 'app-collection' , templateUrl : './collection.component.html' , styleUrls : [ './collection.component.scss' ] , encapsulation : ViewEncapsulation . None , animations : [ trigger ( 'noteButtonsVisibility' , [ state ( 'visible' , style ( { opacity : 1 , } ) ) , state ( 'hidden' , style ( { opacity : 0 , } ) ) , transition ( 'hidden => visible' , animate ( '.25s' ) ) , transition ( 'visible => hidden' , animate ( '.05s' ) ) , ] ) , ] , } ) export class CollectionComponent implements OnInit , OnDestroy { constructor ( private dialog , private collection , private file , private translator , private snackBar , public appearance , private update , private zone , private logger , private settings ) { } public get allCategory ( ) : string { return Constants . allCategory ; } public get todayCategory ( ) : string { return Constants . todayCategory ; } public get yesterdayCategory ( ) : string { return Constants . yesterdayCategory ; } public get thisWeekCategory ( ) : string { return Constants . thisWeekCategory ; } public get markedCategory ( ) : string { return Constants . markedCategory ; } private globalEmitter : any = remote . getGlobal ( 'globalEmitter' ) ; private subscription : Subscription ; @ ViewChild ( 'split' , { static : false } ) public split : SplitComponent ; @ ViewChild ( 'area1' , { static : false } ) public area1 : SplitAreaDirective ; public area1Size : number = this . settings . notebooksPaneWidth ; public selectedIndex : number ; public applicationName : string = Constants . applicationName ; public notebooksCount : number = 0 ; public allNotesCount : number = 0 ; public todayNotesCount : number = 0 ; public yesterdayNotesCount : number = 0 ; public thisWeekNotesCount : number = 0 ; public markedNotesCount : number = 0 ; public notebooks : Notebook [ ] ; public activeNotebook : Notebook ; public hoveredNotebook : Notebook ; public noteButtonsVisibility : string = 'visible' ; public selectedNoteIds : string [ ] ; public notesCount : number = 0 ; public canDeleteNotes : boolean = false ; public tabChangedSubject : Subject < any > = new Subject ( ) ; public showNoteButtonSubject : Subject < any > = new Subject ( ) ; public isBusy : boolean = false ; public canRenameNotebook : boolean = false ; public canDeleteNotebooks : boolean = false ; public selectionWatcher : SelectionWatcher = new SelectionWatcher ( ) ; public ngOnDestroy ( ) { this . subscription . unsubscribe ( ) ; } public async ngOnInit ( ) { await this . collection . initializeAsync ( ) ; await this . getNotebooksAsync ( ) ; this . update . checkForUpdatesAsync ( ) ; this . subscription = this . collection . notebookEdited$ . subscribe ( async ( ) => await this . getNotebooksAsync ( ) ) ; this . subscription . add ( this . collection . notebookDeleted$ . subscribe ( async ( ) => await this . getNotebooksAndResetSelectionAsync ( ) ) ) ; this . subscription . add ( this . collection . notesCountChanged$ . subscribe ( ( result ) => { this . allNotesCount = result . allNotesCount ; this . todayNotesCount = result . todayNotesCount ; this . yesterdayNotesCount = result . yesterdayNotesCount ; this . thisWeekNotesCount = result . thisWeekNotesCount ; this . markedNotesCount = result . markedNotesCount ; } ) ) ; this . subscription . add ( this . collection . noteMarkChanged$ . subscribe ( ( result ) => { this . zone . run ( ( ) => { this . markedNotesCount = result . markedNotesCount ; } ) ; } ) ) ; this . showNoteButtonSubject . pipe ( debounceTime ( 700 ) ) . subscribe ( ( _ ) => { this . showNoteButtons ( ) ; } ) ; } public setSelectedNotebooks ( notebook , event = null ) { if ( event && event . ctrlKey ) { this . selectionWatcher . toggleItemSelection ( notebook ) ; } else if ( event && event . shiftKey ) { this . selectionWatcher . selectItemsRange ( notebook ) ; } else { this . selectionWatcher . selectSingleItem ( notebook ) ; } if ( this . selectionWatcher . selectedItemsCount > 0 ) { this . activeNotebook = this . selectionWatcher . selectedItems [ 0 ] ; } this . zone . run ( ( ) => { this . canRenameNotebook = this . selectionWatcher . selectedItemsCount === 1 && ! this . selectionWatcher . selectedItems [ 0 ] . isDefault ; this . canDeleteNotebooks = this . selectionWatcher . selectedItemsCount > 1 || ( this . selectionWatcher . selectedItemsCount === 1 && ! this . selectionWatcher . selectedItems [ 0 ] . isDefault ) ; } ) ; } public async addNotebookAsync ( ) { const titleText = await this . translator . getAsync ( 'DialogTitles.AddNotebook' ) ; const placeholderText = await this . translator . getAsync ( 'Input.NotebookName' ) ; const dialogRef = this . dialog . open ( InputDialogComponent , { width : '450px' , data : { titleText : titleText , placeholderText : placeholderText } , } ) ; dialogRef . afterClosed ( ) . subscribe ( async ( result ) => { if ( result ) { const notebookName = dialogRef . componentInstance . inputText ; const operation = this . collection . addNotebook ( notebookName ) ; switch ( operation ) { case Operation . Duplicate : { this . snackBar . duplicateNotebookAsync ( notebookName ) ; break ; } case Operation . Error : { const errorText = await this . translator . getAsync ( 'ErrorTexts.AddNotebookError' , { notebookName : notebookName , } ) ; this . dialog . open ( ErrorDialogComponent , { width : '450px' , data : { errorText : errorText } , } ) ; break ; } default : { break ; } } } } ) ; } public renameNotebook ( ) { const dialogRef = this . dialog . open ( RenameNotebookDialogComponent , { width : '450px' , data : { notebookId : this . activeNotebook . id } , } ) ; } public async deleteNotebooksAsync ( ) { let title = await this . translator . getAsync ( 'DialogTitles.ConfirmDeleteNotebooks' ) ; let text = await this . translator . getAsync ( 'DialogTexts.ConfirmDeleteNotebooks' ) ; if ( this . selectionWatcher . selectedItemsCount === 1 ) { title = await this . translator . getAsync ( 'DialogTitles.ConfirmDeleteNotebook' ) ; text = await this . translator . getAsync ( 'DialogTexts.ConfirmDeleteNotebook' , { notebookName : this . selectionWatcher . selectedItems [ 0 ] . name , } ) ; } const dialogRef = this . dialog . open ( ConfirmationDialogComponent , { width : '450px' , data : { dialogTitle : title , dialogText : text } , } ) ; dialogRef . afterClosed ( ) . subscribe ( async ( result ) => { if ( result ) { const operation = await this . collection . deleteNotebooksAsync ( this . selectionWatcher . selectedItems . map ( ( x ) => x . id ) ) ; if ( operation === Operation . Error ) { const errorText = await this . translator . getAsync ( 'ErrorTexts.DeleteNotebooksError' ) ; this . dialog . open ( ErrorDialogComponent , { width : '450px' , data : { errorText : errorText } , } ) ; } } } ) ; } public onNotesCountChanged ( notesCount ) { this . notesCount = notesCount ; } public async addNoteAsync ( ) { const baseTitle = await this . translator . getAsync ( 'Notes.NewNote' ) ; const result = this . collection . addNote ( baseTitle , this . activeNotebook . id ) ; if ( result . operation === Operation . Success ) { this . globalEmitter . emit ( Constants . setNoteOpenEvent , result . noteId , true ) ; } } public async deleteNotesAsync ( ) { let title = await this . translator . getAsync ( 'DialogTitles.ConfirmDeleteNotes' ) ; let text = await this . translator . getAsync ( 'DialogTexts.ConfirmDeleteNotes' ) ; if ( ! this . settings . moveDeletedNotesToTrash ) { text = await this . translator . getAsync ( 'DialogTexts.ConfirmPermanentlyDeleteNotes' ) ; } if ( ! this . selectedNoteIds || this . selectedNoteIds . length === 0 ) { this . logger . warn ( 'User requested to delete notes, but there are not selected notes.' , 'CollectionComponent' , 'deleteNotesAsync' ) ; return ; } if ( this . selectedNoteIds . length === 1 ) { const note = await this . collection . getNote ( this . selectedNoteIds [ 0 ] ) ; title = await this . translator . getAsync ( 'DialogTitles.ConfirmDeleteNote' ) ; text = await this . translator . getAsync ( 'DialogTexts.ConfirmDeleteNote' , { noteTitle : note . title } ) ; if ( ! this . settings . moveDeletedNotesToTrash ) { text = await this . translator . getAsync ( 'DialogTexts.ConfirmPermanentlyDeleteNote' , { noteTitle : note . title } ) ; } } const dialogRef = this . dialog . open ( ConfirmationDialogComponent , { width : '450px' , data : { dialogTitle : title , dialogText : text } , } ) ; dialogRef . afterClosed ( ) . subscribe ( async ( result ) => { if ( result ) { for ( const selectedNoteId of this . selectedNoteIds ) { if ( this . collection . noteIsOpen ( selectedNoteId ) ) { this . globalEmitter . emit ( Constants . closeNoteEvent , selectedNoteId ) ; } } const operation = this . collection . deleteNotes ( this . selectedNoteIds ) ; if ( operation === Operation . Error ) { const errorText = await this . translator . getAsync ( 'ErrorTexts.DeleteNotesError' ) ; this . dialog . open ( ErrorDialogComponent , { width : '450px' , data : { errorText : errorText } , } ) ; } } } ) ; } public onSelectedNotesChanged ( selectedNoteIds ) { this . selectedNoteIds = selectedNoteIds ; this . canDeleteNotes = this . selectedNoteIds != undefined && this . selectedNoteIds . length > 0 ; } public async importNotesAsync ( ) { const openDialogReturnValue = await remote . dialog . showOpenDialog ( { filters : [ { name : ProductInformation . applicationName , extensions : [ Constants . noteExportExtension . replace ( '.' , '' ) ] } , { name : await this . translator . getAsync ( 'DialogTexts.AllFiles' ) , extensions : [ '*' ] } , ] , properties : [ 'openFile' , 'multiSelections' ] , } ) ; if ( openDialogReturnValue != undefined && openDialogReturnValue . filePaths != undefined && openDialogReturnValue . filePaths . length > 0 ) { await this . importNoteFilesAsync ( openDialogReturnValue . filePaths , this . activeNotebook ) ; } } public onSelectedTabChange ( event ) { window . dispatchEvent ( new Event ( 'resize' ) ) ; this . hideNoteButtons ( ) ; const tabIndex = event . index ; let category = '' ; if ( tabIndex === 0 ) { category = Constants . allCategory ; } else if ( tabIndex === 1 ) { category = Constants . todayCategory ; } else if ( tabIndex === 2 ) { category = Constants . yesterdayCategory ; } else if ( tabIndex === 3 ) { category = Constants . thisWeekCategory ; } else if ( tabIndex === 4 ) { category = Constants . markedCategory ; } this . tabChangedSubject . next ( category ) ; this . showNoteButtonSubject . next ( '' ) ; } private hideNoteButtons ( ) { this . noteButtonsVisibility = 'hidden' ; } private showNoteButtons ( ) { this . noteButtonsVisibility = 'visible' ; } private async getNotebooksAsync ( ) { this . notebooks = await this . collection . getNotebooksAsync ( true ) ; this . selectionWatcher . reset ( this . notebooks , true ) ; this . activeNotebook = this . selectionWatcher . selectedItems [ 0 ] ; this . notebooksCount = this . notebooks . length - 2 ; } private async getNotebooksAndResetSelectionAsync ( ) { await this . getNotebooksAsync ( ) ; } public notebookDragOver ( event , notebook ) { event . preventDefault ( ) ; this . hoveredNotebook = notebook ; } public notebookDragLeave ( event ) { event . preventDefault ( ) ; this . hoveredNotebook = null ; } public async notebookDrop ( event , notebook ) { event . preventDefault ( ) ; this . hoveredNotebook = null ; if ( this . file . isDroppingFiles ( event ) ) { const pathsOfDroppedFiles = this . file . getDroppedFilesPaths ( event ) ; await this . importNoteFilesAsync ( pathsOfDroppedFiles , notebook ) ; } else { const noteIds = JSON . parse ( event . dataTransfer . getData ( 'text' ) ) ; const operation = this . collection . setNotebook ( notebook . id , noteIds ) ; if ( operation === Operation . Success ) { if ( noteIds . length > 1 ) { this . snackBar . notesMovedToNotebookAsync ( notebook . name ) ; } else { this . snackBar . noteMovedToNotebookAsync ( notebook . name ) ; } } else if ( operation === Operation . Error ) { const errorText = await this . translator . getAsync ( 'ErrorTexts.ChangeNotebookError' ) ; this . dialog . open ( ErrorDialogComponent , { width : '450px' , data : { errorText : errorText } , } ) ; } } } public async notesDrop ( event ) { event . preventDefault ( ) ; const droppedFilesPaths = this . file . getDroppedFilesPaths ( event ) ; if ( this . file . isDroppingFiles ( event ) ) { const pathsOfDroppedFiles = this . file . getDroppedFilesPaths ( event ) ; await this . importNoteFilesAsync ( pathsOfDroppedFiles , this . activeNotebook ) ; } } public notesDragOver ( event ) { event . preventDefault ( ) ; } private async importNoteFilesAsync ( filePaths , notebook ) { const noteFilePaths = this . file . getNoteFilePaths ( filePaths ) ; if ( noteFilePaths . length === 0 ) { await this . snackBar . noNoteFilesToImportAsync ( ) ; return ; } const importOperation = await this . collection . importNoteFilesAsync ( noteFilePaths , notebook . id ) ; if ( importOperation === Operation . Success ) { await this . snackBar . notesImportedIntoNotebookAsync ( notebook . name ) ; } else if ( importOperation === Operation . Error ) { const errorText = await this . translator . getAsync ( 'ErrorTexts.ImportNotesError' ) ; this . dialog . open ( ErrorDialogComponent , { width : '450px' , data : { errorText : errorText } , } ) ; } } public dragEnd ( event ) { this . settings . notebooksPaneWidth = event . sizes [ 0 ] ; } }