2018-03-25 13:02:39 -04:00
import Branch from "../entities/branch.js" ;
import NoteShort from "../entities/note_short.js" ;
2020-01-25 11:52:45 +01:00
import Attribute from "../entities/attribute.js" ;
2020-01-25 13:46:55 +01:00
import server from "./server.js" ;
2020-02-01 18:29:18 +01:00
import NoteComplement from "../entities/note_complement.js" ;
2018-03-25 12:29:00 -04:00
2019-04-14 18:32:56 +02:00
/ * *
* TreeCache keeps a read only cache of note tree structure in frontend ' s memory .
2019-10-27 19:17:32 +01:00
* - notes are loaded lazily when unknown noteId is requested
* - when note is loaded , all its parent and child branches are loaded as well . For a branch to be used , it ' s not must be loaded before
* - deleted notes are present in the cache as well , but they don ' t have any branches . As a result check for deleted branch is done by presence check - if the branch is not there even though the corresponding note has been loaded , we can infer it is deleted .
*
* Note and branch deletions are corner cases and usually not needed .
2019-04-14 18:32:56 +02:00
* /
2018-03-25 12:29:00 -04:00
class TreeCache {
2018-08-16 23:00:04 +02:00
constructor ( ) {
2020-02-01 22:29:32 +01:00
this . initializedPromise = this . loadInitialTree ( ) ;
2018-08-16 23:00:04 +02:00
}
2020-02-01 22:29:32 +01:00
async loadInitialTree ( ) {
2020-03-18 22:35:54 +01:00
const resp = await server . get ( 'tree' ) ;
2020-02-01 22:29:32 +01:00
2020-05-03 22:49:20 +02:00
await this . loadParents ( resp , false ) ;
2020-02-01 22:29:32 +01:00
// clear the cache only directly before adding new content which is important for e.g. switching to protected session
2018-03-25 12:29:00 -04:00
/** @type {Object.<string, NoteShort>} */
this . notes = { } ;
2018-04-16 20:40:18 -04:00
/** @type {Object.<string, Branch>} */
this . branches = { } ;
2020-01-25 11:52:45 +01:00
/** @type {Object.<string, Attribute>} */
this . attributes = { } ;
2020-02-01 18:29:18 +01:00
/** @type {Object.<string, Promise<NoteComplement>>} */
this . noteComplementPromises = { } ;
2019-10-25 21:47:14 +02:00
2020-03-18 22:35:54 +01:00
this . addResp ( resp ) ;
2019-10-25 21:47:14 +02:00
}
2020-05-03 22:49:20 +02:00
async loadParents ( resp , additiveLoad ) {
2020-03-18 22:35:54 +01:00
const noteIds = new Set ( resp . notes . map ( note => note . noteId ) ) ;
const missingNoteIds = [ ] ;
2020-05-03 22:49:20 +02:00
const existingNotes = additiveLoad ? this . notes : { } ;
2020-03-18 22:35:54 +01:00
for ( const branch of resp . branches ) {
2020-05-03 22:49:20 +02:00
if ( ! ( branch . parentNoteId in existingNotes ) && ! noteIds . has ( branch . parentNoteId ) && branch . parentNoteId !== 'none' ) {
2020-03-18 22:35:54 +01:00
missingNoteIds . push ( branch . parentNoteId ) ;
}
}
for ( const attr of resp . attributes ) {
2020-05-03 22:49:20 +02:00
if ( attr . type === 'relation' && attr . name === 'template' && ! ( attr . value in existingNotes ) && ! noteIds . has ( attr . value ) ) {
2020-03-18 22:35:54 +01:00
missingNoteIds . push ( attr . value ) ;
}
2020-06-09 22:59:22 +02:00
if ( ! ( attr . noteId in existingNotes ) && ! noteIds . has ( attr . noteId ) ) {
missingNoteIds . push ( attr . noteId ) ;
}
2020-03-18 22:35:54 +01:00
}
if ( missingNoteIds . length > 0 ) {
const newResp = await server . post ( 'tree/load' , { noteIds : missingNoteIds } ) ;
resp . notes = resp . notes . concat ( newResp . notes ) ;
resp . branches = resp . branches . concat ( newResp . branches ) ;
resp . attributes = resp . attributes . concat ( newResp . attributes ) ;
2020-05-03 22:49:20 +02:00
await this . loadParents ( resp , additiveLoad ) ;
2020-03-18 22:35:54 +01:00
}
}
addResp ( resp ) {
const noteRows = resp . notes ;
const branchRows = resp . branches ;
const attributeRows = resp . attributes ;
2019-10-26 09:51:08 +02:00
for ( const noteRow of noteRows ) {
const { noteId } = noteRow ;
const oldNote = this . notes [ noteId ] ;
if ( oldNote ) {
for ( const childNoteId of oldNote . children ) {
const childNote = this . notes [ childNoteId ] ;
if ( childNote ) {
childNote . parents = childNote . parents . filter ( p => p !== noteId ) ;
2019-10-27 19:17:32 +01:00
delete this . branches [ childNote . parentToBranch [ noteId ] ] ;
2019-10-26 09:51:08 +02:00
delete childNote . parentToBranch [ noteId ] ;
}
}
2019-04-13 22:10:16 +02:00
2019-10-26 09:51:08 +02:00
for ( const parentNoteId of oldNote . parents ) {
const parentNote = this . notes [ parentNoteId ] ;
2019-04-13 22:10:16 +02:00
2019-10-26 09:51:08 +02:00
if ( parentNote ) {
parentNote . children = parentNote . children . filter ( p => p !== noteId ) ;
2019-04-13 22:10:16 +02:00
2019-10-27 19:17:32 +01:00
delete this . branches [ parentNote . childToBranch [ noteId ] ] ;
2019-10-26 09:51:08 +02:00
delete parentNote . childToBranch [ noteId ] ;
}
}
2019-10-20 12:29:34 +02:00
}
2019-04-13 22:10:16 +02:00
2020-01-31 20:52:31 +01:00
const note = new NoteShort ( this , noteRow ) ;
2019-04-13 22:10:16 +02:00
2019-10-26 09:51:08 +02:00
this . notes [ note . noteId ] = note ;
2020-01-31 20:52:31 +01:00
}
2019-04-13 22:10:16 +02:00
2020-01-31 20:52:31 +01:00
for ( const branchRow of branchRows ) {
const branch = new Branch ( this , branchRow ) ;
2019-04-13 22:10:16 +02:00
2020-01-31 20:52:31 +01:00
this . branches [ branch . branchId ] = branch ;
const childNote = this . notes [ branch . noteId ] ;
if ( childNote ) {
childNote . addParent ( branch . parentNoteId , branch . branchId ) ;
2019-10-26 09:51:08 +02:00
}
2019-04-13 22:56:45 +02:00
2020-01-31 20:52:31 +01:00
const parentNote = this . notes [ branch . parentNoteId ] ;
2019-04-13 22:56:45 +02:00
2020-01-31 20:52:31 +01:00
if ( parentNote ) {
parentNote . addChild ( branch . noteId , branch . branchId ) ;
2019-10-26 09:51:08 +02:00
}
}
2020-01-25 11:52:45 +01:00
for ( const attributeRow of attributeRows ) {
const { attributeId } = attributeRow ;
this . attributes [ attributeId ] = new Attribute ( this , attributeRow ) ;
const note = this . notes [ attributeRow . noteId ] ;
if ( ! note . attributes . includes ( attributeId ) ) {
note . attributes . push ( attributeId ) ;
}
if ( attributeRow . type === 'relation' ) {
const targetNote = this . notes [ attributeRow . value ] ;
if ( targetNote ) {
2020-02-25 16:31:44 +01:00
if ( ! targetNote . targetRelations . includes ( attributeId ) ) {
targetNote . targetRelations . push ( attributeId ) ;
2020-01-25 11:52:45 +01:00
}
}
}
}
2019-10-26 09:51:08 +02:00
}
2020-01-26 11:41:40 +01:00
async reloadNotes ( noteIds ) {
if ( noteIds . length === 0 ) {
return ;
}
2020-01-29 20:14:02 +01:00
noteIds = Array . from ( new Set ( noteIds ) ) ; // make noteIds unique
2019-10-26 09:51:08 +02:00
const resp = await server . post ( 'tree/load' , { noteIds } ) ;
2020-05-03 22:49:20 +02:00
await this . loadParents ( resp , true ) ;
2020-03-18 22:35:54 +01:00
this . addResp ( resp ) ;
2019-11-04 20:20:21 +01:00
for ( const note of resp . notes ) {
if ( note . type === 'search' ) {
const searchResults = await server . get ( 'search-note/' + note . noteId ) ;
2019-11-16 19:07:32 +01:00
if ( ! searchResults ) {
throw new Error ( ` Search note ${ note . noteId } failed. ` ) ;
}
2019-11-04 20:20:21 +01:00
// force to load all the notes at once instead of one by one
2020-01-26 11:41:40 +01:00
await this . getNotes ( searchResults . map ( res => res . noteId ) ) ;
2019-11-04 20:20:21 +01:00
const branches = resp . branches . filter ( b => b . noteId === note . noteId || b . parentNoteId === note . noteId ) ;
searchResults . forEach ( ( result , index ) => branches . push ( {
// branchId should be repeatable since sometimes we reload some notes without rerendering the tree
branchId : "virt" + result . noteId + '-' + note . noteId ,
noteId : result . noteId ,
parentNoteId : note . noteId ,
2020-01-26 11:41:40 +01:00
prefix : this . getBranch ( result . branchId ) . prefix ,
2019-11-04 20:20:21 +01:00
notePosition : ( index + 1 ) * 10
} ) ) ;
// update this note with standard (parent) branches + virtual (children) branches
2020-03-18 22:35:54 +01:00
this . addResp ( {
notes : [ note ] ,
branches ,
attributes : [ ]
} ) ;
2019-11-04 20:20:21 +01:00
}
}
2019-04-13 22:56:45 +02:00
}
2020-03-18 22:35:54 +01:00
/** @return {NoteShort[]} */
getNotesFromCache ( noteIds , silentNotFoundError = false ) {
return noteIds . map ( noteId => {
if ( ! this . notes [ noteId ] && ! silentNotFoundError ) {
2020-08-02 22:53:57 +02:00
console . trace ( ` Can't find note " ${ noteId } " ` ) ;
2020-03-18 22:35:54 +01:00
return null ;
}
else {
return this . notes [ noteId ] ;
}
} ) . filter ( note => ! ! note ) ;
}
2019-04-13 22:10:16 +02:00
/** @return {Promise<NoteShort[]>} */
2018-08-16 23:00:04 +02:00
async getNotes ( noteIds , silentNotFoundError = false ) {
2019-12-16 22:00:44 +01:00
const missingNoteIds = noteIds . filter ( noteId => ! this . notes [ noteId ] ) ;
2018-04-16 20:40:18 -04:00
2020-01-26 11:41:40 +01:00
await this . reloadNotes ( missingNoteIds ) ;
2018-04-16 20:40:18 -04:00
2018-04-16 23:13:33 -04:00
return noteIds . map ( noteId => {
2018-08-16 23:00:04 +02:00
if ( ! this . notes [ noteId ] && ! silentNotFoundError ) {
2020-01-25 13:46:55 +01:00
console . log ( ` Can't find note " ${ noteId } " ` ) ;
2018-08-06 11:30:37 +02:00
2018-08-12 12:59:38 +02:00
return null ;
2018-04-16 23:13:33 -04:00
}
else {
return this . notes [ noteId ] ;
}
2019-11-17 10:24:06 +01:00
} ) . filter ( note => ! ! note ) ;
2018-04-16 23:13:33 -04:00
}
2019-04-13 22:10:16 +02:00
/** @return {Promise<boolean>} */
async noteExists ( noteId ) {
const notes = await this . getNotes ( [ noteId ] , true ) ;
return notes . length === 1 ;
}
/** @return {Promise<NoteShort>} */
2019-09-08 11:25:57 +02:00
async getNote ( noteId , silentNotFoundError = false ) {
2018-05-26 16:16:34 -04:00
if ( noteId === 'none' ) {
2020-05-03 13:52:12 +02:00
console . trace ( ` No 'none' note. ` ) ;
2019-12-10 21:31:24 +01:00
return null ;
}
else if ( ! noteId ) {
2020-07-23 23:38:38 +02:00
console . trace ( ` Falsy noteId ${ noteId } , returning null. ` ) ;
2018-05-26 16:16:34 -04:00
return null ;
}
2019-09-08 11:25:57 +02:00
return ( await this . getNotes ( [ noteId ] , silentNotFoundError ) ) [ 0 ] ;
2018-03-25 12:29:00 -04:00
}
2020-01-15 21:36:01 +01:00
getNoteFromCache ( noteId ) {
return this . notes [ noteId ] ;
}
2020-05-03 13:15:08 +02:00
getBranches ( branchIds , silentNotFoundError = false ) {
2019-10-26 09:58:00 +02:00
return branchIds
2020-05-03 13:15:08 +02:00
. map ( branchId => this . getBranch ( branchId , silentNotFoundError ) )
. filter ( b => ! ! b ) ;
2019-10-26 09:58:00 +02:00
}
2018-04-16 20:40:18 -04:00
2019-10-26 09:58:00 +02:00
/** @return {Branch} */
2019-10-26 22:50:46 +02:00
getBranch ( branchId , silentNotFoundError = false ) {
2019-10-26 09:58:00 +02:00
if ( ! ( branchId in this . branches ) ) {
2019-10-26 22:50:46 +02:00
if ( ! silentNotFoundError ) {
console . error ( ` Not existing branch ${ branchId } ` ) ;
}
2018-04-16 20:40:18 -04:00
}
2019-10-26 20:48:56 +02:00
else {
return this . branches [ branchId ] ;
}
2018-03-25 12:29:00 -04:00
}
2020-01-21 21:43:23 +01:00
async getBranchId ( parentNoteId , childNoteId ) {
const child = await this . getNote ( childNoteId ) ;
2020-07-26 22:58:22 +02:00
if ( ! child ) {
console . error ( ` Could not find branchId for parent= ${ parentNoteId } , child= ${ childNoteId } since child does not exist ` ) ;
return null ;
}
2020-01-21 21:43:23 +01:00
return child . parentToBranch [ parentNoteId ] ;
}
2020-01-26 10:42:24 +01:00
2020-06-14 14:30:57 +02:00
/ * *
* @ return { Promise < NoteComplement > }
* /
2020-02-01 18:29:18 +01:00
async getNoteComplement ( noteId ) {
if ( ! this . noteComplementPromises [ noteId ] ) {
this . noteComplementPromises [ noteId ] = server . get ( 'notes/' + noteId ) . then ( row => new NoteComplement ( row ) ) ;
2020-08-04 21:57:08 +02:00
// we don't want to keep large payloads forever in memory so we clean that up quite quickly
// this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components)
// this is also a work around for missing invalidation after change
this . noteComplementPromises [ noteId ] . then (
( ) => setTimeout ( ( ) => this . noteComplementPromises [ noteId ] = null , 1000 )
) ;
2020-02-01 18:29:18 +01:00
}
return await this . noteComplementPromises [ noteId ] ;
}
2018-03-25 12:29:00 -04:00
}
const treeCache = new TreeCache ( ) ;
2020-06-09 22:59:22 +02:00
export default treeCache ;