2018-03-26 00:29:00 +08:00
|
|
|
import utils from "./utils.js";
|
2018-03-26 01:02:39 +08:00
|
|
|
import Branch from "../entities/branch.js";
|
|
|
|
import NoteShort from "../entities/note_short.js";
|
2019-10-20 16:00:18 +08:00
|
|
|
import toastService from "./toast.js";
|
2019-08-27 02:21:43 +08:00
|
|
|
import ws from "./ws.js";
|
2018-04-17 08:40:18 +08:00
|
|
|
import server from "./server.js";
|
2018-03-26 00:29:00 +08:00
|
|
|
|
2019-04-15 00:32:56 +08:00
|
|
|
/**
|
|
|
|
* TreeCache keeps a read only cache of note tree structure in frontend's memory.
|
|
|
|
*/
|
2018-03-26 00:29:00 +08:00
|
|
|
class TreeCache {
|
2018-08-17 05:00:04 +08:00
|
|
|
constructor() {
|
|
|
|
this.init();
|
|
|
|
}
|
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
load(noteRows, branchRows, relations) {
|
2018-08-17 05:00:04 +08:00
|
|
|
this.init();
|
|
|
|
|
|
|
|
this.addResp(noteRows, branchRows, relations);
|
|
|
|
}
|
|
|
|
|
|
|
|
init() {
|
2019-04-14 04:10:16 +08:00
|
|
|
/** @type {Object.<string, string>} */
|
2018-04-17 08:40:18 +08:00
|
|
|
this.parents = {};
|
2019-04-14 04:10:16 +08:00
|
|
|
/** @type {Object.<string, string>} */
|
2018-04-17 08:40:18 +08:00
|
|
|
this.children = {};
|
2019-04-14 04:10:16 +08:00
|
|
|
|
|
|
|
/** @type {Object.<string, string>} */
|
2018-03-26 00:29:00 +08:00
|
|
|
this.childParentToBranch = {};
|
|
|
|
|
|
|
|
/** @type {Object.<string, NoteShort>} */
|
|
|
|
this.notes = {};
|
2018-04-17 08:40:18 +08:00
|
|
|
|
|
|
|
/** @type {Object.<string, Branch>} */
|
|
|
|
this.branches = {};
|
|
|
|
}
|
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
addResp(noteRows, branchRows, relations) {
|
2018-03-26 00:29:00 +08:00
|
|
|
for (const noteRow of noteRows) {
|
|
|
|
const note = new NoteShort(this, noteRow);
|
|
|
|
|
|
|
|
this.notes[note.noteId] = note;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const branchRow of branchRows) {
|
|
|
|
const branch = new Branch(this, branchRow);
|
|
|
|
|
|
|
|
this.addBranch(branch);
|
|
|
|
}
|
2018-04-17 08:40:18 +08:00
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
for (const relation of relations) {
|
2018-04-17 08:40:18 +08:00
|
|
|
this.addBranchRelationship(relation.branchId, relation.childNoteId, relation.parentNoteId);
|
|
|
|
}
|
2018-03-26 00:29:00 +08:00
|
|
|
}
|
|
|
|
|
2019-04-14 04:56:45 +08:00
|
|
|
/**
|
2019-10-20 18:29:34 +08:00
|
|
|
* Reload notes and their children.
|
2019-04-14 04:56:45 +08:00
|
|
|
*/
|
2019-10-20 18:29:34 +08:00
|
|
|
async reloadNotesAndTheirChildren(noteIds) {
|
|
|
|
// first load the data before clearing the cache
|
|
|
|
const resp = await server.post('tree/load', { noteIds });
|
2019-04-14 04:10:16 +08:00
|
|
|
|
2019-10-20 18:29:34 +08:00
|
|
|
for (const noteId of noteIds) {
|
|
|
|
for (const childNoteId of this.children[noteId] || []) {
|
|
|
|
this.parents[childNoteId] = this.parents[childNoteId].filter(p => p !== noteId);
|
2019-04-14 04:10:16 +08:00
|
|
|
|
2019-10-20 18:29:34 +08:00
|
|
|
const branchId = this.getBranchIdByChildParent(childNoteId, noteId);
|
2019-04-14 04:10:16 +08:00
|
|
|
|
2019-10-20 18:29:34 +08:00
|
|
|
delete this.branches[branchId];
|
|
|
|
delete this.childParentToBranch[childNoteId + '-' + noteId];
|
|
|
|
}
|
2019-04-14 04:10:16 +08:00
|
|
|
|
2019-10-20 18:29:34 +08:00
|
|
|
this.children[noteId] = [];
|
2019-04-14 04:10:16 +08:00
|
|
|
|
2019-10-20 18:29:34 +08:00
|
|
|
delete this.notes[noteId];
|
|
|
|
}
|
2019-04-14 04:10:16 +08:00
|
|
|
|
|
|
|
this.addResp(resp.notes, resp.branches, resp.relations);
|
|
|
|
}
|
|
|
|
|
2019-04-14 04:56:45 +08:00
|
|
|
/**
|
|
|
|
* Reloads parents of given noteId - useful when new note is created to make sure note is loaded
|
|
|
|
* in a correct order.
|
|
|
|
*/
|
|
|
|
async reloadParents(noteId) {
|
|
|
|
// to be able to find parents we need first to make sure it is actually loaded
|
|
|
|
await this.getNote(noteId);
|
|
|
|
|
2019-10-20 18:29:34 +08:00
|
|
|
await this.reloadNotesAndTheirChildren(this.parents[noteId] || []);
|
2019-04-14 04:56:45 +08:00
|
|
|
|
|
|
|
// this is done to load the new parents for the noteId
|
2019-10-20 18:29:34 +08:00
|
|
|
await this.reloadNotesAndTheirChildren([noteId]);
|
2019-04-14 04:56:45 +08:00
|
|
|
}
|
|
|
|
|
2019-04-14 04:10:16 +08:00
|
|
|
/** @return {Promise<NoteShort[]>} */
|
2018-08-17 05:00:04 +08:00
|
|
|
async getNotes(noteIds, silentNotFoundError = false) {
|
2018-04-17 11:13:33 +08:00
|
|
|
const missingNoteIds = noteIds.filter(noteId => this.notes[noteId] === undefined);
|
2018-04-17 08:40:18 +08:00
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
if (missingNoteIds.length > 0) {
|
|
|
|
const resp = await server.post('tree/load', { noteIds: missingNoteIds });
|
2018-04-17 08:40:18 +08:00
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
this.addResp(resp.notes, resp.branches, resp.relations);
|
2018-04-17 08:40:18 +08:00
|
|
|
}
|
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
return noteIds.map(noteId => {
|
2018-08-17 05:00:04 +08:00
|
|
|
if (!this.notes[noteId] && !silentNotFoundError) {
|
2019-08-27 02:21:43 +08:00
|
|
|
ws.logError(`Can't find note "${noteId}"`);
|
2018-08-06 17:30:37 +08:00
|
|
|
|
2018-08-12 18:59:38 +08:00
|
|
|
return null;
|
2018-04-17 11:13:33 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return this.notes[noteId];
|
|
|
|
}
|
2018-08-12 18:59:38 +08:00
|
|
|
}).filter(note => note !== null);
|
2018-04-17 11:13:33 +08:00
|
|
|
}
|
|
|
|
|
2019-04-14 04:10:16 +08:00
|
|
|
/** @return {Promise<boolean>} */
|
|
|
|
async noteExists(noteId) {
|
|
|
|
const notes = await this.getNotes([noteId], true);
|
|
|
|
|
|
|
|
return notes.length === 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @return {Promise<NoteShort>} */
|
2019-09-08 17:25:57 +08:00
|
|
|
async getNote(noteId, silentNotFoundError = false) {
|
2018-05-27 04:16:34 +08:00
|
|
|
if (noteId === 'none') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-09-08 17:25:57 +08:00
|
|
|
return (await this.getNotes([noteId], silentNotFoundError))[0];
|
2018-03-26 00:29:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
addBranch(branch) {
|
|
|
|
this.branches[branch.branchId] = branch;
|
|
|
|
|
2018-04-17 08:40:18 +08:00
|
|
|
this.addBranchRelationship(branch.branchId, branch.noteId, branch.parentNoteId);
|
|
|
|
}
|
2018-03-26 00:29:00 +08:00
|
|
|
|
2018-04-17 08:40:18 +08:00
|
|
|
addBranchRelationship(branchId, childNoteId, parentNoteId) {
|
2018-05-27 04:16:34 +08:00
|
|
|
if (parentNoteId === 'none') { // applies only to root element
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-17 08:40:18 +08:00
|
|
|
this.childParentToBranch[childNoteId + '-' + parentNoteId] = branchId;
|
|
|
|
|
|
|
|
this.parents[childNoteId] = this.parents[childNoteId] || [];
|
|
|
|
|
|
|
|
if (!this.parents[childNoteId].includes(parentNoteId)) {
|
|
|
|
this.parents[childNoteId].push(parentNoteId);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.children[parentNoteId] = this.children[parentNoteId] || [];
|
|
|
|
|
|
|
|
if (!this.children[parentNoteId].includes(childNoteId)) {
|
|
|
|
this.children[parentNoteId].push(childNoteId);
|
|
|
|
}
|
2018-03-26 00:29:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
add(note, branch) {
|
|
|
|
this.notes[note.noteId] = note;
|
|
|
|
|
|
|
|
this.addBranch(branch);
|
|
|
|
}
|
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
async getBranches(branchIds) {
|
|
|
|
const missingBranchIds = branchIds.filter(branchId => this.branches[branchId] === undefined);
|
2018-04-17 08:40:18 +08:00
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
if (missingBranchIds.length > 0) {
|
|
|
|
const resp = await server.post('tree/load', { branchIds: branchIds });
|
2018-04-17 08:40:18 +08:00
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
this.addResp(resp.notes, resp.branches, resp.relations);
|
2018-04-17 08:40:18 +08:00
|
|
|
}
|
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
return branchIds.map(branchId => {
|
|
|
|
if (!this.branches[branchId]) {
|
|
|
|
throw new Error(`Can't find branch ${branchId}`);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return this.branches[branchId];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @return Branch */
|
|
|
|
async getBranch(branchId) {
|
|
|
|
return (await this.getBranches([branchId]))[0];
|
2018-03-26 00:29:00 +08:00
|
|
|
}
|
|
|
|
|
2018-03-26 02:49:20 +08:00
|
|
|
/** @return Branch */
|
|
|
|
async getBranchByChildParent(childNoteId, parentNoteId) {
|
2018-04-17 11:13:33 +08:00
|
|
|
const branchId = this.getBranchIdByChildParent(childNoteId, parentNoteId);
|
2018-04-17 08:40:18 +08:00
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
return await this.getBranch(branchId);
|
|
|
|
}
|
|
|
|
|
|
|
|
getBranchIdByChildParent(childNoteId, parentNoteId) {
|
|
|
|
const key = childNoteId + '-' + parentNoteId;
|
2018-04-17 08:40:18 +08:00
|
|
|
const branchId = this.childParentToBranch[key];
|
2018-03-26 00:29:00 +08:00
|
|
|
|
2018-04-17 08:40:18 +08:00
|
|
|
if (!branchId) {
|
2019-10-20 16:00:18 +08:00
|
|
|
toastService.throwError("Cannot find branch for child-parent=" + key);
|
2018-03-26 00:29:00 +08:00
|
|
|
}
|
|
|
|
|
2018-04-17 11:13:33 +08:00
|
|
|
return branchId;
|
2018-03-26 00:29:00 +08:00
|
|
|
}
|
2018-03-27 10:11:45 +08:00
|
|
|
|
|
|
|
/* Move note from one parent to another. */
|
2019-03-19 04:59:53 +08:00
|
|
|
async moveNote(childNoteId, oldParentNoteId, newParentNoteId, beforeNoteId, afterNoteId) {
|
2018-03-27 10:11:45 +08:00
|
|
|
utils.assertArguments(childNoteId, oldParentNoteId, newParentNoteId);
|
|
|
|
|
|
|
|
if (oldParentNoteId === newParentNoteId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-19 06:03:41 +08:00
|
|
|
const branchId = this.childParentToBranch[childNoteId + '-' + oldParentNoteId];
|
2019-01-06 02:25:22 +08:00
|
|
|
const branch = await this.getBranch(branchId);
|
|
|
|
branch.parentNoteId = newParentNoteId;
|
|
|
|
|
2019-03-19 06:03:41 +08:00
|
|
|
this.childParentToBranch[childNoteId + '-' + newParentNoteId] = branchId;
|
|
|
|
delete this.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId
|
2018-03-27 10:11:45 +08:00
|
|
|
|
|
|
|
// remove old associations
|
2019-03-19 06:03:41 +08:00
|
|
|
this.parents[childNoteId] = this.parents[childNoteId].filter(p => p !== oldParentNoteId);
|
|
|
|
this.children[oldParentNoteId] = this.children[oldParentNoteId].filter(ch => ch !== childNoteId);
|
2018-03-27 10:11:45 +08:00
|
|
|
|
|
|
|
// add new associations
|
2019-03-19 06:03:41 +08:00
|
|
|
this.parents[childNoteId].push(newParentNoteId);
|
2018-03-27 10:11:45 +08:00
|
|
|
|
2019-03-19 06:03:41 +08:00
|
|
|
const children = this.children[newParentNoteId] = this.children[newParentNoteId] || []; // this might be first child
|
2019-03-19 04:59:53 +08:00
|
|
|
|
|
|
|
// we try to put the note into precise order which might be used again by lazy-loaded nodes
|
|
|
|
if (beforeNoteId && children.includes(beforeNoteId)) {
|
|
|
|
children.splice(children.indexOf(beforeNoteId), 0, childNoteId);
|
|
|
|
}
|
|
|
|
else if (afterNoteId && children.includes(afterNoteId)) {
|
|
|
|
children.splice(children.indexOf(afterNoteId) + 1, 0, childNoteId);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
children.push(childNoteId);
|
|
|
|
}
|
2018-03-27 10:11:45 +08:00
|
|
|
}
|
2018-03-26 00:29:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const treeCache = new TreeCache();
|
|
|
|
|
|
|
|
export default treeCache;
|