moved tree initialization into the widget

This commit is contained in:
zadam 2020-01-12 09:12:13 +01:00
parent 9e031dcd60
commit 0f8a7bad06
4 changed files with 166 additions and 122 deletions

View File

@ -4,10 +4,11 @@ const $contextMenuContainer = $("#context-menu-container");
let dateContextMenuOpenedMs = 0;
/**
* @param {NoteTreeWidget} treeWidget
* @param event - originating click event (used to get coordinates to display menu at position)
* @param {object} contextMenu - needs to have getContextMenuItems() and selectContextMenuItem(e, cmd)
*/
async function initContextMenu(event, contextMenu) {
async function initContextMenu(treeWidget, event, contextMenu) {
event.stopPropagation();
$contextMenuContainer.empty();

View File

@ -18,6 +18,10 @@ import keyboardActionService from "./keyboard_actions.js";
let tree;
function setTree(treeInstance) {
tree = treeInstance;
}
let setFrontendAsLoaded;
const frontendLoaded = new Promise(resolve => { setFrontendAsLoaded = resolve; });
@ -424,104 +428,6 @@ async function treeInitialized() {
setFrontendAsLoaded();
}
async function initFancyTree($tree, treeData) {
utils.assertArguments(treeData);
$tree.fancytree({
autoScroll: true,
keyboard: false, // we takover keyboard handling in the hotkeys plugin
extensions: ["hotkeys", "dnd5", "clones"],
source: treeData,
scrollParent: $tree,
minExpandLevel: 2, // root can't be collapsed
click: (event, data) => {
const targetType = data.targetType;
const node = data.node;
if (targetType === 'title' || targetType === 'icon') {
if (event.shiftKey) {
node.setSelected(!node.isSelected());
node.setFocus(true);
}
else if (event.ctrlKey) {
noteDetailService.loadNoteDetail(node.data.noteId, { newTab: true });
}
else {
node.setActive();
clearSelectedNodes();
}
return false;
}
},
activate: async (event, data) => {
// click event won't propagate so let's close context menu manually
contextMenuWidget.hideContextMenu();
const notePath = await treeUtils.getNotePath(data.node);
noteDetailService.switchToNote(notePath);
},
expand: (event, data) => setExpandedToServer(data.node.data.branchId, true),
collapse: (event, data) => setExpandedToServer(data.node.data.branchId, false),
init: (event, data) => treeInitialized(), // don't collapse to short form
hotkeys: {
keydown: await treeKeyBindingService.getKeyboardBindings()
},
dnd5: dragAndDropSetup,
lazyLoad: function(event, data) {
const noteId = data.node.data.noteId;
data.result = treeCache.getNote(noteId).then(note => treeBuilder.prepareBranch(note));
},
clones: {
highlightActiveClones: true
},
enhanceTitle: async function (event, data) {
const node = data.node;
const $span = $(node.span);
if (node.data.noteId !== 'root'
&& node.data.noteId === await hoistedNoteService.getHoistedNoteId()
&& $span.find('.unhoist-button').length === 0) {
const unhoistButton = $('<span>&nbsp; (<a class="unhoist-button">unhoist</a>)</span>');
$span.append(unhoistButton);
}
const note = await treeCache.getNote(node.data.noteId);
if (note.type === 'search' && $span.find('.refresh-search-button').length === 0) {
const refreshSearchButton = $('<span>&nbsp; <span class="refresh-search-button bx bx-recycle" title="Refresh saved search results"></span></span>');
$span.append(refreshSearchButton);
}
},
// this is done to automatically lazy load all expanded search notes after tree load
loadChildren: (event, data) => {
data.node.visit((subNode) => {
// Load all lazy/unloaded child nodes
// (which will trigger `loadChildren` recursively)
if (subNode.isUndefined() && subNode.isExpanded()) {
subNode.load();
}
});
}
});
$tree.on('contextmenu', '.fancytree-node', function(e) {
const node = $.ui.fancytree.getNode(e);
contextMenuWidget.initContextMenu(e, new TreeContextMenu(node));
return false; // blocks default browser right click menu
});
tree = $.ui.fancytree.getTree("#tree");
}
async function reload() {
const notes = await loadTreeData();
@ -745,12 +651,6 @@ async function sortAlphabetically(noteId) {
await reload();
}
async function showTree($tree) {
const treeData = await loadTreeData();
await initFancyTree($tree, treeData);
}
ws.subscribeToMessages(message => {
if (message.type === 'refresh-tree') {
reload();
@ -908,7 +808,6 @@ export default {
getSelectedOrActiveNodes,
clearSelectedNodes,
sortAlphabetically,
showTree,
loadTreeData,
treeInitialized,
setExpandedToServer,
@ -923,5 +822,6 @@ export default {
scrollToActiveNote,
createNewTopLevelNote,
duplicateNote,
getNodeByKey
getNodeByKey,
setTree
};

View File

@ -12,7 +12,12 @@ import protectedSessionHolder from "./protected_session_holder.js";
import searchNotesService from "./search_notes.js";
class TreeContextMenu {
constructor(node) {
/**
* @param {NoteTreeWidget} treeWidget
* @param {FancytreeNode} node
*/
constructor(treeWidget, node) {
this.treeWidget = treeWidget;
this.node = node;
}
@ -37,7 +42,7 @@ class TreeContextMenu {
// some actions don't support multi-note so they are disabled when notes are selected
// the only exception is when the only selected note is the one that was right-clicked, then
// it's clear what the user meant to do.
const selNodes = treeService.getSelectedNodes();
const selNodes = this.treeWidget.getSelectedNodes();
const noSelectedNotes = selNodes.length === 0
|| (selNodes.length === 1 && selNodes[0] === this.node);
@ -128,19 +133,19 @@ class TreeContextMenu {
protectedSessionService.protectSubtree(this.node.data.noteId, false);
}
else if (cmd === "copy") {
clipboard.copy(treeService.getSelectedOrActiveNodes(this.node));
clipboard.copy(this.treeWidget.getSelectedOrActiveNodes(this.node));
}
else if (cmd === "cloneTo") {
const nodes = treeService.getSelectedOrActiveNodes(this.node);
const nodes = this.treeWidget.getSelectedOrActiveNodes(this.node);
const noteIds = nodes.map(node => node.data.noteId);
import("../dialogs/clone_to.js").then(d => d.showDialog(noteIds));
}
else if (cmd === "cut") {
clipboard.cut(treeService.getSelectedOrActiveNodes(this.node));
clipboard.cut(this.treeWidget.getSelectedOrActiveNodes(this.node));
}
else if (cmd === "moveTo") {
const nodes = treeService.getSelectedOrActiveNodes(this.node);
const nodes = this.treeWidget.getSelectedOrActiveNodes(this.node);
import("../dialogs/move_to.js").then(d => d.showDialog(nodes));
}
@ -151,7 +156,7 @@ class TreeContextMenu {
clipboard.pasteInto(this.node);
}
else if (cmd === "delete") {
treeChangesService.deleteNodes(treeService.getSelectedOrActiveNodes(this.node));
treeChangesService.deleteNodes(this.treeWidget.getSelectedOrActiveNodes(this.node));
}
else if (cmd === "export") {
const exportDialog = await import('../dialogs/export.js');
@ -162,7 +167,7 @@ class TreeContextMenu {
importDialog.showDialog(this.node);
}
else if (cmd === "collapseSubtree") {
treeService.collapseTree(this.node);
this.treeWidget.collapseTree(this.node);
}
else if (cmd === "forceNoteSync") {
syncService.forceNoteSync(this.node.data.noteId);

View File

@ -5,6 +5,13 @@ import keyboardActionService from "../services/keyboard_actions.js";
import treeService from "../services/tree.js";
import treeUtils from "../services/tree_utils.js";
import noteDetailService from "../services/note_detail.js";
import utils from "../services/utils.js";
import contextMenuWidget from "../services/context_menu.js";
import treeKeyBindingService from "../services/tree_keybindings.js";
import dragAndDropSetup from "../services/drag_and_drop.js";
import treeCache from "../services/tree_cache.js";
import treeBuilder from "../services/tree_builder.js";
import TreeContextMenu from "../services/tree_context_menu.js";
const TPL = `
<style>
@ -22,16 +29,25 @@ const TPL = `
`;
export default class NoteTreeWidget extends BasicWidget {
constructor(appContext) {
super(appContext);
this.tree = null;
}
async doRender($widget) {
$widget.append($(TPL));
const $tree = $widget.find('#tree');
await treeService.showTree($tree);
const treeData = await treeService.loadTreeData();
await this.initFancyTree($tree, treeData);
$tree.on("click", ".unhoist-button", hoistedNoteService.unhoist);
$tree.on("click", ".refresh-search-button", searchNotesService.refreshSearch);
// this does not belong here ...
keyboardActionService.setGlobalActionHandler('CollapseTree', () => treeService.collapseTree()); // don't use shortened form since collapseTree() accepts argument
// fancytree doesn't support middle click so this is a way to support it
@ -51,15 +67,137 @@ export default class NoteTreeWidget extends BasicWidget {
});
}
createTopLevelNoteListener() {
treeService.createNewTopLevelNote();
async initFancyTree($tree, treeData) {
utils.assertArguments(treeData);
$tree.fancytree({
autoScroll: true,
keyboard: false, // we takover keyboard handling in the hotkeys plugin
extensions: ["hotkeys", "dnd5", "clones"],
source: treeData,
scrollParent: $tree,
minExpandLevel: 2, // root can't be collapsed
click: (event, data) => {
const targetType = data.targetType;
const node = data.node;
if (targetType === 'title' || targetType === 'icon') {
if (event.shiftKey) {
node.setSelected(!node.isSelected());
node.setFocus(true);
}
else if (event.ctrlKey) {
noteDetailService.loadNoteDetail(node.data.noteId, { newTab: true });
}
else {
node.setActive();
treeService.clearSelectedNodes();
}
return false;
}
},
activate: async (event, data) => {
// click event won't propagate so let's close context menu manually
contextMenuWidget.hideContextMenu();
const notePath = await treeUtils.getNotePath(data.node);
noteDetailService.switchToNote(notePath);
},
expand: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, true),
collapse: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, false),
init: (event, data) => treeService.treeInitialized(),
hotkeys: {
keydown: await treeKeyBindingService.getKeyboardBindings()
},
dnd5: dragAndDropSetup,
lazyLoad: function(event, data) {
const noteId = data.node.data.noteId;
data.result = treeCache.getNote(noteId).then(note => treeBuilder.prepareBranch(note));
},
clones: {
highlightActiveClones: true
},
enhanceTitle: async function (event, data) {
const node = data.node;
const $span = $(node.span);
if (node.data.noteId !== 'root'
&& node.data.noteId === await hoistedNoteService.getHoistedNoteId()
&& $span.find('.unhoist-button').length === 0) {
const unhoistButton = $('<span>&nbsp; (<a class="unhoist-button">unhoist</a>)</span>');
$span.append(unhoistButton);
}
const note = await treeCache.getNote(node.data.noteId);
if (note.type === 'search' && $span.find('.refresh-search-button').length === 0) {
const refreshSearchButton = $('<span>&nbsp; <span class="refresh-search-button bx bx-recycle" title="Refresh saved search results"></span></span>');
$span.append(refreshSearchButton);
}
},
// this is done to automatically lazy load all expanded search notes after tree load
loadChildren: (event, data) => {
data.node.visit((subNode) => {
// Load all lazy/unloaded child nodes
// (which will trigger `loadChildren` recursively)
if (subNode.isUndefined() && subNode.isExpanded()) {
subNode.load();
}
});
}
});
$tree.on('contextmenu', '.fancytree-node', e => {
const node = $.ui.fancytree.getNode(e);
contextMenuWidget.initContextMenu(this, e, new TreeContextMenu(this, node));
return false; // blocks default browser right click menu
});
this.tree = $.ui.fancytree.getTree("#tree");
treeService.setTree(this.tree);
}
collapseTreeListener() {
treeService.collapseTree();
/** @return {FancytreeNode[]} */
getSelectedNodes(stopOnParents = false) {
return this.tree.getSelectedNodes(stopOnParents);
}
scrollToActiveNoteListener() {
treeService.scrollToActiveNote();
/** @return {FancytreeNode[]} */
getSelectedOrActiveNodes(node) {
let notes = this.getSelectedNodes(true);
if (notes.length === 0) {
notes.push(node);
}
return notes;
}
async collapseTree(node = null) {
if (!node) {
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
node = getNodesByNoteId(hoistedNoteId)[0];
}
node.setExpanded(false);
node.visit(node => node.setExpanded(false));
}
createTopLevelNoteListener() { treeService.createNewTopLevelNote(); }
collapseTreeListener() { this.collapseTree(); }
scrollToActiveNoteListener() { treeService.scrollToActiveNote(); }
}