improvements in frontend sync (WIP)

This commit is contained in:
zadam 2021-08-25 22:49:24 +02:00
parent 66374bf95f
commit d0747b125c
10 changed files with 77 additions and 70 deletions

View File

@ -41,44 +41,6 @@ class Attribute {
return `Attribute(attributeId=${this.attributeId}, type=${this.type}, name=${this.name}, value=${this.value})`; return `Attribute(attributeId=${this.attributeId}, type=${this.type}, name=${this.name}, value=${this.value})`;
} }
/**
* @return {boolean} - returns true if this attribute has the potential to influence the note in the argument.
* That can happen in multiple ways:
* 1. attribute is owned by the note
* 2. attribute is owned by the template of the note
* 3. attribute is owned by some note's ancestor and is inheritable
*/
isAffecting(affectedNote) {
if (!affectedNote) {
return false;
}
const attrNote = this.getNote();
if (!attrNote) {
// the note (owner of the attribute) is not even loaded into the cache so it should not affect anything else
return false;
}
const owningNotes = [affectedNote, ...affectedNote.getTemplateNotes()];
for (const owningNote of owningNotes) {
if (owningNote.noteId === attrNote.noteId) {
return true;
}
}
if (this.isInheritable) {
for (const owningNote of owningNotes) {
if (owningNote.hasAncestor(attrNote)) {
return true;
}
}
}
return false;
}
isDefinition() { isDefinition() {
return this.type === 'label' && (this.name.startsWith('label:') || this.name.startsWith('relation:')); return this.type === 'label' && (this.name.startsWith('label:') || this.name.startsWith('relation:'));
} }

View File

@ -1,4 +1,5 @@
import server from './server.js'; import server from './server.js';
import froca from './froca.js';
async function addLabel(noteId, name, value = "") { async function addLabel(noteId, name, value = "") {
await server.put(`notes/${noteId}/attribute`, { await server.put(`notes/${noteId}/attribute`, {
@ -20,8 +21,47 @@ async function removeAttributeById(noteId, attributeId) {
await server.remove(`notes/${noteId}/attributes/${attributeId}`); await server.remove(`notes/${noteId}/attributes/${attributeId}`);
} }
/**
* @return {boolean} - returns true if this attribute has the potential to influence the note in the argument.
* That can happen in multiple ways:
* 1. attribute is owned by the note
* 2. attribute is owned by the template of the note
* 3. attribute is owned by some note's ancestor and is inheritable
*/
function isAffecting(attrRow, affectedNote) {
if (!affectedNote || !attrRow) {
return false;
}
const attrNote = froca.notes[attrRow.noteId];
if (!attrNote) {
// the note (owner of the attribute) is not even loaded into the cache so it should not affect anything else
return false;
}
const owningNotes = [affectedNote, ...affectedNote.getTemplateNotes()];
for (const owningNote of owningNotes) {
if (owningNote.noteId === attrNote.noteId) {
return true;
}
}
if (this.isInheritable) {
for (const owningNote of owningNotes) {
if (owningNote.hasAncestor(attrNote)) {
return true;
}
}
}
return false;
}
export default { export default {
addLabel, addLabel,
setLabel, setLabel,
removeAttributeById removeAttributeById,
isAffecting
} }

View File

@ -8,6 +8,7 @@ import froca from "../../services/froca.js";
import attributeRenderer from "../../services/attribute_renderer.js"; import attributeRenderer from "../../services/attribute_renderer.js";
import noteCreateService from "../../services/note_create.js"; import noteCreateService from "../../services/note_create.js";
import treeService from "../../services/tree.js"; import treeService from "../../services/tree.js";
import attributeService from "../../services/attributes.js";
const HELP_TEXT = ` const HELP_TEXT = `
<p>To add label, just type e.g. <code>#rock</code> or if you want to add also value then e.g. <code>#year = 2020</code></p> <p>To add label, just type e.g. <code>#rock</code> or if you want to add also value then e.g. <code>#year = 2020</code></p>
@ -511,7 +512,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
} }
entitiesReloadedEvent({loadResults}) { entitiesReloadedEvent({loadResults}) {
if (loadResults.getAttributes(this.componentId).find(attr => attr.isAffecting(this.note))) { if (loadResults.getAttributes(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) {
this.refresh(); this.refresh();
} }
} }

View File

@ -1,5 +1,4 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js";
import utils from "../services/utils.js";
import protectedSessionHolder from "../services/protected_session_holder.js"; import protectedSessionHolder from "../services/protected_session_holder.js";
import SpacedUpdate from "../services/spaced_update.js"; import SpacedUpdate from "../services/spaced_update.js";
import server from "../services/server.js"; import server from "../services/server.js";
@ -20,6 +19,7 @@ import DeletedTypeWidget from "./type_widgets/deleted.js";
import ReadOnlyTextTypeWidget from "./type_widgets/read_only_text.js"; import ReadOnlyTextTypeWidget from "./type_widgets/read_only_text.js";
import ReadOnlyCodeTypeWidget from "./type_widgets/read_only_code.js"; import ReadOnlyCodeTypeWidget from "./type_widgets/read_only_code.js";
import NoneTypeWidget from "./type_widgets/none.js"; import NoneTypeWidget from "./type_widgets/none.js";
import attributeService from "../services/attributes.js";
const TPL = ` const TPL = `
<div class="note-detail"> <div class="note-detail">
@ -246,12 +246,12 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
const label = attrs.find(attr => const label = attrs.find(attr =>
attr.type === 'label' attr.type === 'label'
&& ['readOnly', 'autoReadOnlyDisabled', 'cssClass', 'displayRelations'].includes(attr.name) && ['readOnly', 'autoReadOnlyDisabled', 'cssClass', 'displayRelations'].includes(attr.name)
&& attr.isAffecting(this.note)); && attributeService.isAffecting(attr, this.note));
const relation = attrs.find(attr => const relation = attrs.find(attr =>
attr.type === 'relation' attr.type === 'relation'
&& ['template', 'renderNote'].includes(attr.name) && ['template', 'renderNote'].includes(attr.name)
&& attr.isAffecting(this.note)); && attributeService.isAffecting(attr, this.note));
if (label || relation) { if (label || relation) {
// probably incorrect event // probably incorrect event

View File

@ -126,7 +126,7 @@ export default class NoteIconWidget extends NoteContextAwareWidget {
for (const attr of loadResults.getAttributes()) { for (const attr of loadResults.getAttributes()) {
if (attr.type === 'label' if (attr.type === 'label'
&& ['iconClass', 'workspaceIconClass'].includes(attr.name) && ['iconClass', 'workspaceIconClass'].includes(attr.name)
&& attr.isAffecting(this.note)) { && attributeService.isAffecting(attr, this.note)) {
this.refresh(); this.refresh();
break; break;

View File

@ -972,36 +972,36 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const noteIdsToUpdate = new Set(); const noteIdsToUpdate = new Set();
const noteIdsToReload = new Set(); const noteIdsToReload = new Set();
for (const attr of loadResults.getAttributes()) { for (const ecAttr of loadResults.getAttributes()) {
if (attr.type === 'label' && ['iconClass', 'cssClass', 'workspace', 'workspaceIconClass', 'archived'].includes(attr.name)) { if (ecAttr.type === 'label' && ['iconClass', 'cssClass', 'workspace', 'workspaceIconClass', 'archived'].includes(ecAttr.name)) {
if (attr.isInheritable) { if (ecAttr.isInheritable) {
noteIdsToReload.add(attr.noteId); noteIdsToReload.add(ecAttr.noteId);
} }
else { else {
noteIdsToUpdate.add(attr.noteId); noteIdsToUpdate.add(ecAttr.noteId);
} }
} }
else if (attr.type === 'relation' && attr.name === 'template') { else if (ecAttr.type === 'relation' && ecAttr.name === 'template') {
// missing handling of things inherited from template // missing handling of things inherited from template
noteIdsToReload.add(attr.noteId); noteIdsToReload.add(ecAttr.noteId);
} }
else if (attr.type === 'relation' && attr.name === 'imageLink') { else if (ecAttr.type === 'relation' && ecAttr.name === 'imageLink') {
const note = froca.getNoteFromCache(attr.noteId); const note = froca.getNoteFromCache(ecAttr.noteId);
if (note && note.getChildNoteIds().includes(attr.value)) { if (note && note.getChildNoteIds().includes(ecAttr.value)) {
// there's new/deleted imageLink betwen note and its image child - which can show/hide // there's new/deleted imageLink betwen note and its image child - which can show/hide
// the image (if there is a imageLink relation between parent and child then it is assumed to be "contained" in the note and thus does not have to be displayed in the tree) // the image (if there is a imageLink relation between parent and child then it is assumed to be "contained" in the note and thus does not have to be displayed in the tree)
noteIdsToReload.add(attr.noteId); noteIdsToReload.add(ecAttr.noteId);
} }
} }
} }
for (const branch of loadResults.getBranches()) { for (const ecBranch of loadResults.getBranches()) {
// adding noteId itself to update all potential clones // adding noteId itself to update all potential clones
noteIdsToUpdate.add(branch.noteId); noteIdsToUpdate.add(ecBranch.noteId);
for (const node of this.getNodesByBranch(branch)) { for (const node of this.getNodesByBranch(ecBranch)) {
if (branch.isDeleted) { if (ecBranch.isDeleted) {
if (node.isActive()) { if (node.isActive()) {
const newActiveNode = node.getNextSibling() const newActiveNode = node.getNextSibling()
|| node.getPrevSibling() || node.getPrevSibling()
@ -1016,22 +1016,22 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
node.remove(); node.remove();
} }
noteIdsToUpdate.add(branch.parentNoteId); noteIdsToUpdate.add(ecBranch.parentNoteId);
} }
} }
if (!branch.isDeleted) { if (!ecBranch.isDeleted) {
for (const parentNode of this.getNodesByNoteId(branch.parentNoteId)) { for (const parentNode of this.getNodesByNoteId(ecBranch.parentNoteId)) {
if (parentNode.isFolder() && !parentNode.isLoaded()) { if (parentNode.isFolder() && !parentNode.isLoaded()) {
continue; continue;
} }
const found = (parentNode.getChildren() || []).find(child => child.data.noteId === branch.noteId); const found = (parentNode.getChildren() || []).find(child => child.data.noteId === ecBranch.noteId);
if (!found) { if (!found) {
// make sure it's loaded // make sure it's loaded
await froca.getNote(branch.noteId); await froca.getNote(ecBranch.noteId);
const frocaBranch = froca.getBranch(branch.branchId); const frocaBranch = froca.getBranch(ecBranch.branchId);
// we're forcing lazy since it's not clear if the whole required subtree is in froca // we're forcing lazy since it's not clear if the whole required subtree is in froca
parentNode.addChildren([this.prepareNode(frocaBranch, true)]); parentNode.addChildren([this.prepareNode(frocaBranch, true)]);
@ -1039,7 +1039,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
this.sortChildren(parentNode); this.sortChildren(parentNode);
// this might be a first child which would force an icon change // this might be a first child which would force an icon change
noteIdsToUpdate.add(branch.parentNoteId); noteIdsToUpdate.add(ecBranch.parentNoteId);
} }
} }
} }

View File

@ -1,6 +1,7 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js"; import NoteContextAwareWidget from "../note_context_aware_widget.js";
import AttributeDetailWidget from "../attribute_widgets/attribute_detail.js"; import AttributeDetailWidget from "../attribute_widgets/attribute_detail.js";
import attributeRenderer from "../../services/attribute_renderer.js"; import attributeRenderer from "../../services/attribute_renderer.js";
import attributeService from "../../services/attributes.js";
const TPL = ` const TPL = `
<div class="inherited-attributes-widget"> <div class="inherited-attributes-widget">
@ -88,7 +89,7 @@ export default class InheritedAttributesWidget extends NoteContextAwareWidget {
} }
entitiesReloadedEvent({loadResults}) { entitiesReloadedEvent({loadResults}) {
if (loadResults.getAttributes(this.componentId).find(attr => attr.isAffecting(this.note))) { if (loadResults.getAttributes(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) {
this.refresh(); this.refresh();
} }
} }

View File

@ -1,6 +1,7 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js"; import NoteContextAwareWidget from "../note_context_aware_widget.js";
import AttributeDetailWidget from "../attribute_widgets/attribute_detail.js"; import AttributeDetailWidget from "../attribute_widgets/attribute_detail.js";
import AttributeEditorWidget from "../attribute_widgets/attribute_editor.js"; import AttributeEditorWidget from "../attribute_widgets/attribute_editor.js";
import attributeService from "../../services/attributes.js";
const TPL = ` const TPL = `
<div class="attribute-list"> <div class="attribute-list">
@ -75,7 +76,7 @@ export default class OwnedAttributeListWidget extends NoteContextAwareWidget {
} }
entitiesReloadedEvent({loadResults}) { entitiesReloadedEvent({loadResults}) {
if (loadResults.getAttributes(this.componentId).find(attr => attr.isAffecting(this.note))) { if (loadResults.getAttributes(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) {
this.refreshWithNote(this.note, true); this.refreshWithNote(this.note, true);
this.getTitle(this.note); this.getTitle(this.note);

View File

@ -3,6 +3,7 @@ import ws from "../../services/ws.js";
import treeService from "../../services/tree.js"; import treeService from "../../services/tree.js";
import noteAutocompleteService from "../../services/note_autocomplete.js"; import noteAutocompleteService from "../../services/note_autocomplete.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js"; import NoteContextAwareWidget from "../note_context_aware_widget.js";
import attributeService from "../../services/attributes.js";
const TPL = ` const TPL = `
<div> <div>
@ -294,7 +295,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
} }
entitiesReloadedEvent({loadResults}) { entitiesReloadedEvent({loadResults}) {
if (loadResults.getAttributes(this.componentId).find(attr => attr.isAffecting(this.note))) { if (loadResults.getAttributes(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) {
this.refresh(); this.refresh();
this.getTitle(this.note); this.getTitle(this.note);

View File

@ -4,6 +4,7 @@ import utils from "../services/utils.js";
import keyboardActionService from "../services/keyboard_actions.js"; import keyboardActionService from "../services/keyboard_actions.js";
import appContext from "../services/app_context.js"; import appContext from "../services/app_context.js";
import froca from "../services/froca.js"; import froca from "../services/froca.js";
import attributeService from "../services/attributes.js";
/*! /*!
* Draggabilly v2.3.0 * Draggabilly v2.3.0
@ -668,7 +669,7 @@ export default class TabRowWidget extends BasicWidget {
if (loadResults.isNoteReloaded(noteContext.noteId) || if (loadResults.isNoteReloaded(noteContext.noteId) ||
loadResults.getAttributes().find(attr => loadResults.getAttributes().find(attr =>
['workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor'].includes(attr.name) ['workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor'].includes(attr.name)
&& attr.isAffecting(noteContext.note)) && attributeService.isAffecting(attr, noteContext.note))
) { ) {
const $tab = this.getTabById(noteContext.ntxId); const $tab = this.getTabById(noteContext.ntxId);