import { Annotation } from './../../modules/project/shared/workspace/annotation.model' ; import { AnnotationStorage , ProjectStorage , SourceStorage , AreaStorage , FacsimileStorage , DrawingStorage , SentenceStorage , WordStorage , GlyphStorage } from './../storage/storage.model' ; import { Area } from './../../modules/project/shared/workspace/area.model' ; import { AreaSeed , ProjectExport } from './data.model' ; import { BehaviorSubject , Observable } from 'rxjs' ; import { Drawing } from './../../modules/project/shared/workspace/drawing.model' ; import { environment } from '../../../environments/environment' ; import { every , includes , find , uniq , without } from 'lodash' ; import { Glyph } from '../../modules/project/shared/workspace/glyph.model' ; import { Injectable } from '@angular/core' ; import { NotificationsService } from './../notifications/notifications.service' ; import { Project , ProjectListItem } from '../../modules/project/project.model' ; import { Sentence } from '../../modules/project/shared/workspace/sentence.model' ; import { Settings } from '../../modules/settings/settings.model' ; import { Source } from './../../modules/project/shared/workspace/source.model' ; import { StorageService } from './../storage/storage.service' ; import { TranslateService } from '@ngx-translate/core' ; import { UtilsService } from '../utils/utils.service' ; import { Word } from '../../modules/project/shared/workspace/word.model' ; import { WorkspaceData , WorkspaceSeed , WorkspacePosition , WorkspaceConfig } from './../../modules/project/shared/workspace/workspace.model' ; import * as uuid from 'uuid' ; @ Injectable ( ) export class DataService { private settingsSub : BehaviorSubject < Settings > = new BehaviorSubject ( null ) ; settingsObs : Observable < Settings > = this . settingsSub . asObservable ( ) ; private projectList : Array < ProjectListItem > = [ ] ; private projectListSub : BehaviorSubject < Array < ProjectListItem > > = new BehaviorSubject ( this . projectList ) ; projectListObs : Observable < Array < ProjectListItem > > = this . projectListSub . asObservable ( ) ; private project : Project = null ; private projectSub : BehaviorSubject < Project > = new BehaviorSubject ( this . project ) ; projectObs : Observable < Project > = this . projectSub . asObservable ( ) ; private workspaceSeed : WorkspaceSeed = null ; private workspaceSeedSub : BehaviorSubject < WorkspaceSeed > = new BehaviorSubject ( this . workspaceSeed ) ; workspaceSeedObs : Observable < WorkspaceSeed > = this . workspaceSeedSub . asObservable ( ) ; constructor ( private storage , private utils , private notificationsService , private translation ) { } init ( ) { this . loadProjectList ( ) ; this . loadSettings ( ) ; } saveSettings ( settings ) { return this . storage . saveSettings ( settings ) . then ( ( success ) => { if ( success ) { this . settingsSub . next ( settings ) ; } return success ; } ) ; } loadProject ( id ) { this . nullifyProject ( ) ; return new Promise ( ( resolve , reject ) => { this . storage . loadProject ( id ) . then ( ( projectStorage ) => { const project = { id : projectStorage . id , created : projectStorage . created , updated : projectStorage . updated , properties : projectStorage . properties , } ; this . buildWorkspaceSeed ( projectStorage ) . then ( workspaceSeed => { resolve ( ) ; this . project = project ; this . workspaceSeed = workspaceSeed ; this . projectSub . next ( project ) ; this . workspaceSeedSub . next ( workspaceSeed ) ; } ) ; } ) ; } ) ; } exportProject ( id ) { const projectExport = { project : null , files : { sources : [ ] , facsimiles : [ ] , } , } ; return new Promise ( ( resolve , reject ) => { this . storage . loadProject ( id ) . then ( ( projectStorage ) => { projectExport . project = projectStorage ; const loadPromises = [ ] ; projectStorage . sources . forEach ( ( sourceId ) => { loadPromises . push ( this . storage . loadSource ( sourceId ) . then ( ( sourceStorage ) => { projectExport . files . sources . push ( sourceStorage ) ; return true ; } , err => false ) ) ; } ) ; if ( projectStorage . facsimile . processed ) { loadPromises . push ( this . storage . loadFacsimile ( projectStorage . facsimile . processed ) . then ( ( facsimileStorage ) => { projectExport . files . facsimiles . push ( facsimileStorage ) ; return true ; } , err => false ) ) ; } if ( projectStorage . facsimile . generated ) { loadPromises . push ( this . storage . loadFacsimile ( projectStorage . facsimile . generated ) . then ( ( facsimileStorage ) => { projectExport . files . facsimiles . push ( facsimileStorage ) ; return true ; } , err => false ) ) ; } Promise . all ( loadPromises ) . then ( resolutions => { if ( every ( resolutions ) ) { resolve ( projectExport ) ; } else { console . warn ( this . translation . instant ( 'CORE.DATA.WARN.EXPORT' ) , resolutions ) ; reject ( ) ; } } ) ; } ) ; } ) ; } saveProject ( project , workspaceData , workspaceType ? , isNewProject = false , newSources ? ) { return new Promise ( ( resolve , reject ) => { const projectStorage = { id : project . id , created : project . created , updated : new Date ( ) . getTime ( ) , appVersion : environment . appVersion || 'unknown' , properties : { title : project . properties . title , author : project . properties . author , } , sources : [ ] , facsimile : { processed : null , generated : null , } , drawings : [ ] , effects : workspaceData && workspaceData . facsimile && workspaceData . generate . effects ? workspaceData . generate . effects : { trace : { active : false , level : 128 , } , outlines : { active : false , level : 16 , } , drawing : { active : false , width : 3 , } , } , annotations : [ ] , areas : [ ] , sentences : [ ] , } ; const sources = [ ] ; let processedFacsimile = null ; let generatedFacsimile = null ; const saveSetupPromises = [ ] ; if ( isNewProject ) { saveSetupPromises . push ( new Promise ( pass => { newSources . forEach ( ( source ) => { sources . push ( source ) ; projectStorage . sources . push ( source . id ) ; } ) ; pass ( ) ; } ) ) ; } else { saveSetupPromises . push ( new Promise ( pass => { this . storage . loadProject ( projectStorage . id ) . then ( ( oldProjectStorage ) => { const sourceIds = [ ] ; oldProjectStorage . sources . forEach ( oldSourceId => { if ( ! find ( workspaceData . sources , { id : oldSourceId } ) ) { this . storage . deleteSource ( oldSourceId ) ; } else { sourceIds . push ( oldSourceId ) ; } } ) ; workspaceData . sources . forEach ( ( source ) => { if ( ! find ( oldProjectStorage . sources , source . id ) ) { const oldOpacity = source . opacity ; source . opacity = 1 ; sources . push ( { id : source . id , title : source . title , dataURL : source . toDataURL ( { } ) , } ) ; source . opacity = oldOpacity ; sourceIds . push ( source . id ) ; } } ) ; projectStorage . sources = uniq ( sourceIds ) ; if ( workspaceData . facsimile . processed ) { if ( workspaceData . facsimile . processed . id !== oldProjectStorage . facsimile . processed ) { this . storage . deleteFacsimile ( oldProjectStorage . facsimile . processed ) ; } const oldOpacity = workspaceData . facsimile . processed . opacity ; workspaceData . facsimile . processed . opacity = 1 ; processedFacsimile = { id : workspaceData . facsimile . processed . id , dataURL : workspaceData . facsimile . processed . toDataURL ( { } ) , } ; workspaceData . facsimile . processed . opacity = oldOpacity ; projectStorage . facsimile . processed = processedFacsimile . id ; } if ( workspaceData . facsimile . generated ) { if ( workspaceData . facsimile . generated . id !== oldProjectStorage . facsimile . generated ) { this . storage . deleteFacsimile ( oldProjectStorage . facsimile . generated ) ; } const oldOpacity = workspaceData . facsimile . generated . opacity ; workspaceData . facsimile . generated . opacity = 1 ; generatedFacsimile = { id : workspaceData . facsimile . generated . id , dataURL : workspaceData . facsimile . generated . toDataURL ( { } ) , } ; workspaceData . facsimile . generated . opacity = oldOpacity ; projectStorage . facsimile . generated = generatedFacsimile . id ; } if ( workspaceType === 'process' ) { workspaceData . process . areas . forEach ( ( area ) => { const areaStorage = { author : area . author , created : area . created , source : area . source , effects : [ ] , position : { top : area . top , left : area . left , width : area . width , height : area . height , } , } ; for ( const val in area . effects ) { if ( area . effects [ val ] ) { const effect = area . effects [ val ] ; areaStorage . effects . push ( effect ) ; } } projectStorage . areas . push ( areaStorage ) ; } ) ; } else { projectStorage . areas = oldProjectStorage . areas ; } if ( workspaceType === 'generate' ) { workspaceData . generate . drawings . forEach ( ( drawing ) => { const oldOpacity = drawing . opacity ; drawing . opacity = 1 ; const drawingStorage = { author : drawing . author , created : drawing . created , position : { top : drawing . top , left : drawing . left , width : drawing . width , height : drawing . height , } , dataURL : drawing . getElement ( ) . src || drawing . toDataURL ( { } ) , } ; drawing . opacity = oldOpacity ; projectStorage . drawings . push ( drawingStorage ) ; } ) ; } else { projectStorage . drawings = oldProjectStorage . drawings ; } if ( workspaceType === 'analyse' ) { workspaceData . analyse . sentences . forEach ( ( sentence ) => { const sentenceStorage = { author : sentence . author , created : sentence . created , words : [ ] , position : { top : sentence . top , left : sentence . left , width : sentence . width , height : sentence . height , } , order : sentence . order , interpretation : sentence . interpretation , transliteration : sentence . transliteration , } ; sentence . getWords ( ) . forEach ( ( word ) => { const wordStorage = { author : word . author , created : word . created , glyphs : [ ] , position : { top : word . top , left : word . left , width : word . width , height : word . height , } , isCartouche : word . isCartouche , order : word . order , translation : word . translation , transliteration : word . transliteration , } ; word . getGlyphs ( ) . forEach ( ( glyph ) => { const glyphStorage = { author : glyph . author , created : glyph . created , points : glyph . points , gardinerCode : glyph . gardinerCode , gardinerCodePredictions : glyph . gardinerCodePredictions , order : glyph . order , locked : glyph . locked , } ; wordStorage . glyphs . push ( glyphStorage ) ; } ) ; sentenceStorage . words . push ( wordStorage ) ; } ) ; projectStorage . sentences . push ( sentenceStorage ) ; } ) ; } else { projectStorage . sentences = oldProjectStorage . sentences ; } if ( workspaceType === 'annotate' ) { workspaceData . annotate . annotations . forEach ( ( annotation ) => { projectStorage . annotations . push ( { author : annotation . author , created : annotation . created , comments : annotation . comments , points : annotation . points , } as AnnotationStorage ) ; } ) ; } else { projectStorage . annotations = oldProjectStorage . annotations ; } pass ( ) ; } ) ; } ) ) ; } Promise . all ( saveSetupPromises ) . then ( ( ) => { const savePromises = [ ] ; sources . forEach ( ( source ) => { savePromises . push ( this . storage . saveSource ( source ) ) ; } ) ; if ( processedFacsimile ) { savePromises . push ( this . storage . saveFacsimile ( processedFacsimile ) ) ; } if ( generatedFacsimile ) { savePromises . push ( this . storage . saveFacsimile ( generatedFacsimile ) ) ; } savePromises . push ( this . storage . saveProject ( projectStorage ) ) ; savePromises . push ( this . buildWorkspaceSeed ( projectStorage ) . then ( workspaceSeed => { this . workspaceSeed = workspaceSeed ; this . workspaceSeedSub . next ( workspaceSeed ) ; return true ; } , ( ) => false ) ) ; savePromises . push ( this . addToProjectList ( projectStorage . id ) ) ; Promise . all ( savePromises ) . then ( resolutions => { if ( every ( resolutions ) ) { this . loadProjectList ( ) ; resolve ( ) ; } else { console . warn ( this . translation . instant ( 'CORE.DATA.WARN.SAVE' ) , resolutions ) ; reject ( ) ; } } ) ; } ) ; } ) ; } saveProjectProperties ( project , sources ) { return new Promise ( ( resolve , reject ) => { this . storage . loadProject ( project . id ) . then ( ( projectStorage ) => { projectStorage . properties = project . properties ; const sourcePromises = [ ] ; projectStorage . sources = [ ] ; sources . forEach ( ( source ) => { projectStorage . sources . push ( source . id ) ; sourcePromises . push ( this . storage . saveSource ( source ) ) ; } ) ; Promise . all ( sourcePromises ) . then ( ( ) => { this . storage . saveProject ( projectStorage ) . then ( ( ) => { this . loadProject ( project . id ) . then ( resolve , reject ) ; } , reject ) ; } , reject ) ; } ) ; resolve ( ) ; } ) ; } createProject ( projectProperties , fileData ) { this . nullifyProject ( ) ; return new Promise ( ( resolve , reject ) => { this . utils . dataURLFromFile ( fileData . file ) . then ( ( dataURL ) => { const time = new Date ( ) . getTime ( ) ; const project = { id : uuid ( ) , properties : { title : projectProperties . title , author : projectProperties . author , } , created : time , updated : time , } ; const source = { id : uuid ( ) , title : fileData . title , dataURL : dataURL , } ; this . saveProject ( project , null , null , true , [ source ] ) . then ( ( ) => { this . project = project ; this . projectSub . next ( project ) ; resolve ( project ) ; } , err => { this . notificationsService . error ( this . translation . instant ( 'CORE.DATA.ERROR.CREATE' ) ) ; reject ( err ) ; } ) ; } , err => { this . notificationsService . error ( this . translation . instant ( 'CORE.DATA.ERROR.CREATE' ) ) ; reject ( err ) ; } ) ; } ) ; } importProject ( projectImport ) { return new Promise ( ( resolve , reject ) => { const importPromises = [ ] ; projectImport . project . id = uuid ( ) ; projectImport . project . created = new Date ( ) . getTime ( ) ; projectImport . project . updated = new Date ( ) . getTime ( ) ; importPromises . push ( this . saveProjectData ( projectImport . project ) ) ; projectImport . files . sources . forEach ( ( sourceStorage ) => { importPromises . push ( this . saveSourceFile ( sourceStorage ) ) ; } ) ; projectImport . files . facsimiles . forEach ( ( facsimileStorage ) => { importPromises . push ( this . saveFacsimileFile ( facsimileStorage ) ) ; } ) ; Promise . all ( importPromises ) . then ( ( ) => { this . addToProjectList ( projectImport . project . id ) ; resolve ( projectImport . project . id ) ; } , err => { reject ( this . translation . instant ( 'CORE.DATA.ERROR.SAVE' ) ) ; console . warn ( this . translation . instant ( 'CORE.DATA.WARN.SAVE' ) , err ) ; } ) ; } ) ; } deleteProject ( id ) { return new Promise ( ( resolve , reject ) => { this . storage . loadProject ( id ) . then ( ( projectStorage ) => { const deletePromises = [ ] ; projectStorage . sources . forEach ( ( sourceId ) => { deletePromises . push ( this . deleteSourceFile ( sourceId ) ) ; } ) ; deletePromises . push ( this . deleteFacsimileFile ( projectStorage . facsimile . processed ) ) ; deletePromises . push ( this . deleteFacsimileFile ( projectStorage . facsimile . generated ) ) ; deletePromises . push ( this . deleteProjectData ( projectStorage . id ) ) ; deletePromises . push ( this . removeFromProjectList ( projectStorage . id ) ) ; Promise . all ( deletePromises ) . then ( ( ) => { this . nullifyProject ( ) ; this . loadProjectList ( ) ; resolve ( ) ; } , err => { console . warn ( this . translation . instant ( 'CORE.DATA.WARN.DELETE' ) , err ) ; reject ( this . translation . instant ( 'CORE.DATA.ERROR.DELETE' ) ) ; } ) ; } ) ; } ) ; } updateWorkspacePosition ( position ) { this . workspaceSeed . position = position ; this . workspaceSeedSub . next ( this . workspaceSeed ) ; } private saveProjectData ( project ) { return new Promise ( ( resolve , reject ) => { this . storage . saveProject ( project ) . then ( ( success ) => { if ( success ) { resolve ( ) ; } else { reject ( ) ; } } ) ; } ) ; } private saveSourceFile ( sourceStorage ) { return new Promise ( ( resolve , reject ) => { this . storage . saveSource ( sourceStorage ) . then ( ( success ) => { if ( success ) { resolve ( ) ; } else { reject ( ) ; } } ) ; } ) ; } private saveFacsimileFile ( facsimileStorage ) { return new Promise ( ( resolve , reject ) => { this . storage . saveFacsimile ( facsimileStorage ) . then ( ( success ) => { if ( success ) { resolve ( ) ; } else { reject ( ) ; } } ) ; } ) ; } private deleteProjectData ( id ) { return new Promise ( ( resolve , reject ) => { this . storage . deleteProject ( id ) . then ( ( success ) => { if ( success ) { resolve ( ) ; } else { reject ( ) ; } } ) ; } ) ; } private deleteSourceFile ( id ) { return new Promise ( ( resolve , reject ) => { this . storage . deleteSource ( id ) . then ( ( success ) => { if ( success ) { resolve ( ) ; } else { reject ( ) ; } } ) ; } ) ; } private deleteFacsimileFile ( id ) { return new Promise ( ( resolve , reject ) => { this . storage . deleteFacsimile ( id ) . then ( ( success ) => { if ( success ) { resolve ( ) ; } else { reject ( ) ; } } ) ; } ) ; } private buildWorkspaceSeed ( projectStorage ) { return new Promise ( ( resolve , reject ) => { const workspaceSeed = { sources : [ ] , facsimile : { processed : null , generated : null , } , drawings : projectStorage . drawings , effects : projectStorage . effects , annotations : projectStorage . annotations || [ ] , sentences : projectStorage . sentences || [ ] , areas : [ ] , position : this . workspaceSeed ? this . workspaceSeed . position : { x : 0 , y : 0 , zoom : 1 , } , } ; projectStorage . areas . forEach ( ( areaStorage ) => { const areaSeed = { author : areaStorage . author , created : areaStorage . created , source : areaStorage . source , effects : { threshold : null , } , position : { top : areaStorage . position . top , left : areaStorage . position . left , width : areaStorage . position . width , height : areaStorage . position . height , } , } ; if ( areaStorage . effects ) { areaStorage . effects . forEach ( ( effect ) => { areaSeed . effects [ effect . tid ] = effect ; } ) ; } workspaceSeed . areas . push ( areaSeed ) ; } ) ; const loadPromises = [ ] ; projectStorage . sources . forEach ( ( sourceId ) => { loadPromises . push ( this . storage . loadSource ( sourceId ) . then ( ( sourceStorage ) => { workspaceSeed . sources . push ( { id : sourceStorage . id , title : sourceStorage . title , dataURL : sourceStorage . dataURL , } ) ; return true ; } , err => false ) ) ; } ) ; if ( projectStorage . facsimile . processed ) { loadPromises . push ( this . storage . loadFacsimile ( projectStorage . facsimile . processed ) . then ( ( facsimileStorage ) => { workspaceSeed . facsimile . processed = { id : facsimileStorage . id , dataURL : facsimileStorage . dataURL , } ; return true ; } , err => false ) ) ; } if ( projectStorage . facsimile . generated ) { loadPromises . push ( this . storage . loadFacsimile ( projectStorage . facsimile . generated ) . then ( ( facsimileStorage ) => { workspaceSeed . facsimile . generated = { id : facsimileStorage . id , dataURL : facsimileStorage . dataURL , } ; return true ; } , err => false ) ) ; } Promise . all ( loadPromises ) . then ( resolutions => { if ( every ( resolutions ) ) { resolve ( workspaceSeed ) ; } else { console . warn ( this . translation . instant ( 'CORE.DATA.WARN.LOAD' ) , resolutions ) ; reject ( ) ; } } ) ; } ) ; } private loadProjectList ( ) { const projectList = [ ] ; this . storage . loadProjectList ( ) . then ( ( projectIds ) => { const promises = [ ] ; projectIds . forEach ( id => { promises . push ( this . storage . loadProject ( id ) . then ( ( project ) => { if ( project ) { const projectListItem = { id : project . id , created : project . created , updated : project . updated , title : project . properties . title , author : project . properties . author , } ; projectList . push ( projectListItem ) ; } } ) ) ; } ) ; Promise . all ( promises ) . then ( ( ) => { this . projectList = projectList ; this . projectListSub . next ( projectList ) ; } ) ; } ) ; } private loadSettings ( ) { this . storage . loadSettings ( ) . then ( ( settings ) => { if ( ! settings ) { settings = { userFullName : 'Anonymous' , clusterAnalysis : { threshold : 200 , } , classification : { model : environment . config . apiServices . classification . models . length ? environment . config . apiServices . classification . models [ 0 ] : null , } , } ; this . storage . saveSettings ( settings ) ; } this . settingsSub . next ( settings ) ; } ) ; } private nullifyProject ( ) { this . workspaceSeed = null ; this . workspaceSeedSub . next ( null ) ; this . project = null ; this . projectSub . next ( null ) ; } private addToProjectList ( id ) { return this . storage . loadProjectList ( ) . then ( projectList => { if ( ! includes ( projectList , id ) ) { projectList . push ( id ) ; } return this . storage . saveProjectList ( projectList ) ; } ) ; } private removeFromProjectList ( id ) { return this . storage . loadProjectList ( ) . then ( projectList => { projectList = without ( projectList , id ) ; return this . storage . saveProjectList ( projectList ) ; } ) ; } }