import * as React from "react" ; import { Callback , Callback1 , } from "polar-shared/src/util/Functions" ; import { IDocMeta } from "polar-shared/src/metadata/IDocMeta" ; import { AnnotationMutations } from "polar-shared/src/metadata/mutations/AnnotationMutations" ; import { IPageMeta } from "polar-shared/src/metadata/IPageMeta" ; import { usePersistenceLayerContext , useRepoDocMetaManager , useTagsContext } from "../../../apps/repository/js/persistence_layer/PersistenceLayerApp" ; import { TaggedCallbacks } from "../../../apps/repository/js/annotation_repo/TaggedCallbacks" ; import { arrayStream } from "polar-shared/src/util/ArrayStreams" ; import { Tag , Tags } from "polar-shared/src/tags/Tags" ; import { useDialogManager } from "../mui/dialogs/MUIDialogControllers" ; import { AreaHighlights , AreaHighlightWriteOpts } from "../metadata/AreaHighlights" ; import { AreaHighlightRects } from "../metadata/AreaHighlightRects" ; import { Arrays } from "polar-shared/src/util/Arrays" ; import { useDocMetaLookupContext } from "./DocMetaLookupContextProvider" ; import { IAnnotationRef , IAnnotationRefWithDocMeta } from "polar-shared/src/metadata/AnnotationRefs" ; import { useLogger } from "../mui/MUILogger" ; import { Preconditions } from "polar-shared/src/Preconditions" ; import { DocMetas } from "../metadata/DocMetas" ; import ComputeNewTagsStrategy = Tags . ComputeNewTagsStrategy ; import TaggedCallbacksOpts = TaggedCallbacks . TaggedCallbacksOpts ; import ITagsHolder = TaggedCallbacks . ITagsHolder ; import { IAnnotationMutationCallbacks , IAnnotationMutationSelectedWithDocMeta , IAnnotationMutationSelected , IAnnotationMutationHolderWithDocMeta , IAnnotationMutationHolder , ITaggedMutation , IDeleteMutation , IDeleteMutationWithDocMeta , DocAnnotationsMutator , IAreaHighlightMutation , ITextHighlightMutation , ICommentMutation , IFlashcardMutation , IColorMutation } from "./AnnotationMutationsContext" ; import { Analytics } from "../analytics/Analytics" ; param updateStore param refresher export type AnnotationMutationCallbacksFactory = ( updateStore : ( docMetas : ReadonlyArray < IDocMeta > ) => ReadonlyArray < IDocMeta > , refresher : ( ) => void ) => IAnnotationMutationCallbacks ; export function useAnnotationMutationCallbacksFactory ( ) { const dialogs = useDialogManager ( ) ; const persistenceLayerContext = usePersistenceLayerContext ( ) ; const docMetaLookupContext = useDocMetaLookupContext ( ) ; const tagsContext = useTagsContext ( ) ; const repoDocMetaManager = useRepoDocMetaManager ( ) ; const log = useLogger ( ) ; return React . useCallback ( ( updateStore , refresher ) => { async function writeUpdatedDocMetas ( updatedDocMetas ) { updatedDocMetas = updatedDocMetas . map ( DocMetas . updated ) ; updatedDocMetas = updateStore ( updatedDocMetas ) ; refresher ( ) ; const { persistenceLayerProvider } = persistenceLayerContext ; for ( const docMeta of updatedDocMetas ) { const persistenceLayer = persistenceLayerProvider ( ) ; await persistenceLayer . writeDocMeta ( docMeta ) ; } } type AnnotationMutator < T extends IAnnotationMutationSelectedWithDocMeta > = ( docMeta : IDocMeta , pageMeta : IPageMeta , mutation : T ) => void ; Deprecated param mutation param annotationMutator async function handleUpdate < T extends IAnnotationMutationSelected > ( mutation , annotationMutator ) { const selected = docMetaLookupContext . lookupAnnotations ( mutation . selected ) ; const partitions = arrayStream ( selected ) . partition ( annotation => [ annotation . docMetaRef . id , docMetaLookupContext . lookup ( annotation . docMetaRef . id ) ! ] ) ; for ( const partition of Object . values ( partitions ) ) { const docMeta = partition . key ; for ( const annotation of selected ) { const pageMeta = DocMetas . getPageMeta ( docMeta , annotation . pageNum ) ; annotationMutator ( docMeta , pageMeta , { ... mutation , selected : [ annotation ] } ) ; } } const updatedDocMetas = Object . values ( partitions ) . map ( current => current . key ) ; await writeUpdatedDocMetas ( updatedDocMetas ) ; } type AnnotationMutator2 < M > = ( mutation : IAnnotationMutationHolderWithDocMeta < M > ) => void ; /**
         *
         */ async function handleUpdate2 < M > ( holders , annotationMutator ) { const resolvedHolders = docMetaLookupContext . lookupAnnotationHolders ( holders ) ; for ( const resolvedHolder of resolvedHolders ) { annotationMutator ( resolvedHolder ) ; } const updatedDocMetas = Object . values ( resolvedHolders ) . map ( current => current . annotation . docMeta ) ; await writeUpdatedDocMetas ( updatedDocMetas ) ; } function doTagged ( annotations , tags , strategy = 'set' ) { handleUpdate ( { selected : annotations } , ( docMeta , pageMeta , mutation ) => { for ( const current of mutation . selected ) { const newTags = Tags . computeNewTags ( current . original . tags , tags , strategy ) ; const updates = { tags : Tags . toMap ( newTags ) } ; AnnotationMutations . update ( { ... current , docMeta } , { ... current . original , ... updates } ) ; } } ) . catch ( err => log . error ( err ) ) ; } function createTaggedCallback ( mutation ) { function toTarget ( annotation ) { return { ... annotation , tags : annotation . original . tags } ; } function buildRelatedOptionsCalculator ( ) { if ( repoDocMetaManager ) { const { relatedTagsManager } = repoDocMetaManager ; return relatedTagsManager . toRelatedOptionsCalculator ( ) ; } return undefined ; } const relatedOptionsCalculator = buildRelatedOptionsCalculator ( ) ; const opts = { targets : ( ) => mutation . selected . map ( toTarget ) , tagsProvider : tagsContext . tagsProvider , dialogs , doTagged , relatedOptionsCalculator } ; return TaggedCallbacks . create ( opts ) ; } function onTagged ( mutation ) { createTaggedCallback ( mutation ) ( ) ; } function useDeletedCallback ( mutation ) { return React . useCallback ( ( ) => { onDeleted ( mutation ) ; } , [ mutation ] ) ; } function onDeleted ( mutation ) { const annotations = docMetaLookupContext . lookupAnnotations ( mutation . selected ) ; if ( annotations . length === 0 ) { log . warn ( "no repoAnnotation" ) ; return ; } dialogs . confirm ( { title : "Are you sure you want to delete this item?" , subtitle : "This is a permanent operation and can't be undone." , onAccept : ( ) => doDeleted ( annotations ) } ) ; } function doDeleted ( annotations ) { const mutation = { selected : docMetaLookupContext . lookupAnnotations ( annotations ) } ; handleUpdate ( mutation , DocAnnotationsMutator . onDeleted ) . catch ( err => log . error ( err ) ) ; } function onAreaHighlight ( mutation ) { async function doAsync ( ) { const { docMeta , pageMeta , areaHighlight , capturedScreenshot , position } = mutation ; Preconditions . assertPresent ( capturedScreenshot , 'capturedScreenshot' ) ; function toAreaHighlightRect ( ) { const rect = Arrays . first ( Object . values ( areaHighlight . rects ) ) ; return AreaHighlightRects . createFromRect ( rect ! ) ; } const areaHighlightRect = toAreaHighlightRect ( ) ; const { persistenceLayerProvider } = persistenceLayerContext ; const writeOpts = { datastore : persistenceLayerProvider ( ) , docMeta , pageMeta , areaHighlight , areaHighlightRect , position , capturedScreenshot } ; const writer = AreaHighlights . createWriter ( writeOpts ) ; const [ , committer ] = writer . prepare ( ) ; updateStore ( [ docMeta ] ) ; await committer . commit ( ) ; updateStore ( [ docMeta ] ) ; refresher ( ) ; if ( mutation . type === "create" ) { Analytics . event2 ( 'doc-highlightCreated' , { type : 'area' } ) ; } } doAsync ( ) . catch ( err => log . error ( err ) ) ; } function onTextHighlight ( mutation ) { switch ( mutation . type ) { case "revert" : case "update" : handleUpdate ( mutation , DocAnnotationsMutator . onTextHighlight ) . catch ( err => log . error ( err ) ) ; break ; case "create" : const { docMeta , pageMeta , textHighlight } = mutation ; pageMeta . textHighlights [ textHighlight . id ] = textHighlight ; writeUpdatedDocMetas ( [ docMeta ] ) . then ( ( ) => Analytics . event2 ( 'doc-highlightCreated' , { type : 'text' } ) ) . catch ( err => log . error ( err ) ) ; break ; } } function createCommentCallback ( annotation ) { return React . useCallback ( ( mutation ) => { const holder = { annotation , mutation } ; onComment ( [ holder ] ) ; } , [ annotation ] ) ; } function onComment ( holders ) { handleUpdate2 ( holders , DocAnnotationsMutator . onComment ) . catch ( err => log . error ( err ) ) ; } function createFlashcardCallback ( annotation ) { return React . useCallback ( ( mutation ) => { const holder = { annotation , mutation } ; onFlashcard ( [ holder ] ) ; } , [ annotation ] ) ; } function onFlashcard ( holders ) { handleUpdate2 ( holders , DocAnnotationsMutator . onFlashcard ) . catch ( err => log . error ( err ) ) ; } function createColorCallback ( selected ) { return React . useCallback ( ( mutation ) => { onColor ( { ... selected , ... mutation } ) ; } , [ selected ] ) ; } function onColor ( colorMutation ) { handleUpdate ( { selected : colorMutation . selected } , ( docMeta , pageMeta , mutation ) => { for ( const current of mutation . selected ) { const updates = { color : colorMutation . color } ; AnnotationMutations . update ( { ... current , docMeta } , { ... current . original , ... updates } ) ; } Analytics . event2 ( 'annotation-colorChanged' ) ; } ) . catch ( err => log . error ( err ) ) ; } return { writeUpdatedDocMetas , createDeletedCallback : useDeletedCallback , onDeleted , onAreaHighlight , onTextHighlight , createCommentCallback , onComment , createFlashcardCallback , onFlashcard , createColorCallback , onColor , createTaggedCallback , doTagged , onTagged , } ; } , [ dialogs , persistenceLayerContext , docMetaLookupContext , tagsContext , repoDocMetaManager , log ] ) ; }