import { Hashcodes } from 'polar-shared/src/util/Hashcodes' ; import { Preconditions } from 'polar-shared/src/Preconditions' ; import { AreaHighlight } from './AreaHighlight' ; import { ISODateTimeString , ISODateTimeStrings } from 'polar-shared/src/metadata/ISODateTimeStrings' ; import { Image } from './Image' ; import { Datastore } from '../datastore/Datastore' ; import { PersistenceLayer } from '../datastore/PersistenceLayer' ; import { Images } from './Images' ; import { DocMetas } from './DocMetas' ; import { Backend } from 'polar-shared/src/datastore/Backend' ; import { ArrayBuffers } from 'polar-shared/src/util/ArrayBuffers' ; import { Attachment } from './Attachment' ; import { AreaHighlightRect } from './AreaHighlightRect' ; import { HighlightRects , Position } from "polar-shared/src/metadata/IBaseHighlight" ; import { DatastoreFileCache } from '../datastore/DatastoreFileCache' ; import { ICapturedScreenshot } from '../screenshots/Screenshot' ; import { ILTRect } from 'polar-shared/src/util/rects/ILTRect' ; import { DataURLs } from 'polar-shared/src/util/DataURLs' ; import { Rects } from '../Rects' ; import { IPageMeta } from "polar-shared/src/metadata/IPageMeta" ; import { IDocMeta } from "polar-shared/src/metadata/IDocMeta" ; import { IAreaHighlight } from "polar-shared/src/metadata/IAreaHighlight" ; import { IImage } from "polar-shared/src/metadata/IImage" ; import { BackendFileRef } from "polar-shared/src/datastore/BackendFileRef" ; export class AreaHighlights { public static update ( id , docMeta , pageMeta , updates ) { const existing = pageMeta . areaHighlights [ id ] ! ; if ( ! existing ) { throw new Error ( "No existing for id: " + id ) ; } const updated = new AreaHighlight ( { ... existing , ... updates } ) ; pageMeta . areaHighlights [ id ] = updated ; } public static toCorrectScale2 ( overlayRect , currentScale ) { const rescaleFactor = 1 / currentScale ; return Rects . scale ( Rects . createFromBasicRect ( overlayRect ) , rescaleFactor ) ; } public static createID ( created ) { return Hashcodes . createID ( created ) ; } param opts return { AreaHighlight } public static create ( opts = { } ) { Preconditions . assertNotNull ( opts . rect , "rect" ) ; const created = ISODateTimeStrings . create ( ) ; const id = AreaHighlights . createID ( created ) ; return new AreaHighlight ( { id , guid : id , created , rects : { "0" : opts . rect } } ) ; } public static createWriter ( opts ) { return new DefaultAreaHighlightWriter ( opts ) ; } public static async delete ( opts ) { const { datastore , docMeta , pageMeta , areaHighlight } = opts ; const { image } = areaHighlight ; DocMetas . withBatchedMutations ( docMeta , ( ) => { delete pageMeta . areaHighlights [ areaHighlight . id ] ; if ( image ) { delete docMeta . docInfo . attachments [ image . id ] ; } } ) ; if ( image ) { await datastore . deleteFile ( image . src . backend , image . src ) ; } } } export interface DoWriteOpts { readonly datastore : PersistenceLayer ; readonly docMeta : IDocMeta ; readonly pageMeta : IPageMeta ; readonly pageNum : number ; readonly areaHighlight : AreaHighlight ; readonly target : HTMLElement ; readonly areaHighlightRect : AreaHighlightRect ; readonly boxRect : ILTRect ; } export interface AreaHighlightDeleteOpts { readonly datastore : Datastore | PersistenceLayer ; readonly docMeta : IDocMeta ; readonly pageMeta : IPageMeta ; readonly areaHighlight : IAreaHighlight ; } export interface AreaHighlightWriteOpts { readonly datastore : PersistenceLayer ; readonly docMeta : IDocMeta ; readonly pageMeta : IPageMeta ; readonly areaHighlight : IAreaHighlight ; readonly areaHighlightRect : AreaHighlightRect ; readonly position : Position ; readonly capturedScreenshot : ICapturedScreenshot ; } export interface AreaHighlightWriter { prepare ( ) : [ AreaHighlight , AreaHighlightCommitter ] ; } class DefaultAreaHighlightWriter implements AreaHighlightWriter { constructor ( private readonly opts ) { Preconditions . assertPresent ( opts , 'opts' ) ; Preconditions . assertPresent ( opts . capturedScreenshot , 'opts.capturedScreenshot' ) ; } public prepare ( ) { const { docMeta , capturedScreenshot , pageMeta , areaHighlight , areaHighlightRect , position } = this . opts ; Preconditions . assertPresent ( capturedScreenshot , 'capturedScreenshot' ) ; const { type , width , height } = capturedScreenshot ; const id = Images . createID ( ) ; const ext = Images . toExt ( type ) ; const fileRef = { backend : Backend . IMAGE , name : `${ id }.${ ext }` } ; const oldImage = areaHighlight . image ; const image = new Image ( { id , type , width , height , rel : 'screenshot' , src : fileRef , } ) ; const toBlob = ( ) => { if ( typeof capturedScreenshot . data === 'string' ) { return DataURLs . toBlob ( capturedScreenshot . data ) ; } else { return ArrayBuffers . toBlob ( capturedScreenshot . data ) ; } } ; const blob = toBlob ( ) ; const blobURL = URL . createObjectURL ( blob ) ; DatastoreFileCache . writeFile ( Backend . IMAGE , image . src , { url : blobURL } ) ; if ( areaHighlight . image ) { delete docMeta . docInfo . attachments [ areaHighlight . image . id ] ; } docMeta . docInfo . attachments [ image . id ] = new Attachment ( { fileRef } ) ; const rects = { } ; rects [ "0" ] = < any > areaHighlightRect ; const newAreaHighlight = new AreaHighlight ( { ... areaHighlight , image , rects , position , lastUpdated : ISODateTimeStrings . create ( ) } ) ; delete pageMeta . areaHighlights [ areaHighlight . id ] ; pageMeta . areaHighlights [ newAreaHighlight . id ] = newAreaHighlight ; const committer = new DefaultAreaHighlightCommitter ( this . opts , image , blob , oldImage ) ; return [ newAreaHighlight , committer ] ; } } export interface AreaHighlightCommitter { commit ( ) : Promise < void > ; } class DefaultAreaHighlightCommitter implements AreaHighlightCommitter { constructor ( private readonly opts , private readonly image , private readonly blob , private readonly oldImage ) { } public async commit ( ) { const { datastore , docMeta } = this . opts ; const { image , oldImage , blob } = this ; const writeFilePromise = datastore . writeFile ( image . src . backend , image . src , blob ) ; const deleteFilePromise = oldImage ? datastore . deleteFile ( oldImage . src . backend , oldImage . src ) : Promise . resolve ( ) ; const writeDocMetaPromise = datastore . writeDocMeta ( docMeta ) ; await Promise . all ( [ writeFilePromise , deleteFilePromise , writeDocMetaPromise ] ) ; } } interface IAreaHighlightOpts { readonly rect ? : any ; }