From df69b1d8ddf7ebe5431ea2f7a36197d985d9f04c Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 18 Jul 2020 23:45:28 +0200 Subject: [PATCH] attr detail handles label/relation definition updates --- db/migrations/0160__attr_def_short.js | 16 +- src/public/app/entities/attribute.js | 32 +-- .../promoted_attribute_definition_parser.js | 35 ++++ src/public/app/widgets/attribute_detail.js | 187 +++++++++++++----- src/public/app/widgets/attribute_editor.js | 18 +- 5 files changed, 190 insertions(+), 98 deletions(-) create mode 100644 src/public/app/services/promoted_attribute_definition_parser.js diff --git a/db/migrations/0160__attr_def_short.js b/db/migrations/0160__attr_def_short.js index 091d12e4b..3e01a97e5 100644 --- a/db/migrations/0160__attr_def_short.js +++ b/db/migrations/0160__attr_def_short.js @@ -10,16 +10,16 @@ module.exports = () => { tokens.push('promoted'); } - if (obj.labelType) { - tokens.push(obj.labelType); - } - if (obj.multiplicityType === 'singlevalue') { tokens.push('single'); } else if (obj.multiplicityType === 'multivalue') { tokens.push('multi'); } + if (obj.labelType) { + tokens.push(obj.labelType); + } + if (obj.numberPrecision) { tokens.push('precision='+obj.numberPrecision); } @@ -38,16 +38,16 @@ module.exports = () => { tokens.push('promoted'); } - if (obj.inverseRelation) { - tokens.push('inverse=' + obj.inverseRelation); - } - if (obj.multiplicityType === 'singlevalue') { tokens.push('single'); } else if (obj.multiplicityType === 'multivalue') { tokens.push('multi'); } + if (obj.inverseRelation) { + tokens.push('inverse=' + obj.inverseRelation); + } + const newValue = tokens.join(','); sql.execute('UPDATE attributes SET value = ? WHERE attributeId = ?', [newValue, attr.attributeId]); diff --git a/src/public/app/entities/attribute.js b/src/public/app/entities/attribute.js index 9a4f937af..48dee04fe 100644 --- a/src/public/app/entities/attribute.js +++ b/src/public/app/entities/attribute.js @@ -1,3 +1,5 @@ +import promotedAttributeDefinitionParser from '../services/promoted_attribute_definition_parser.js'; + class Attribute { constructor(treeCache, row) { this.treeCache = treeCache; @@ -76,35 +78,7 @@ class Attribute { } getDefinition() { - const tokens = this.value.split(',').map(t => t.trim()); - const defObj = {}; - - for (const token of tokens) { - if (token === 'promoted') { - defObj.isPromoted = true; - } - else if (['text', 'number', 'boolean', 'date', 'url'].includes(token)) { - defObj.labelType = token; - } - else if (['single', 'multi'].includes(token)) { - defObj.multiplicity = token; - } - else if (token.startsWith('precision')) { - const chunks = token.split('='); - - defObj.numberPrecision = parseInt(chunks[1]); - } - else if (token.startsWith('inverse')) { - const chunks = token.split('='); - - defObj.inverseRelation = chunks[1]; - } - else { - console.log("Unrecognized attribute definition token:", token); - } - } - - return defObj; + return promotedAttributeDefinitionParser.parse(this.value); } } diff --git a/src/public/app/services/promoted_attribute_definition_parser.js b/src/public/app/services/promoted_attribute_definition_parser.js new file mode 100644 index 000000000..1fcc69751 --- /dev/null +++ b/src/public/app/services/promoted_attribute_definition_parser.js @@ -0,0 +1,35 @@ +function parse(value) { + const tokens = value.split(',').map(t => t.trim()); + const defObj = {}; + + for (const token of tokens) { + if (token === 'promoted') { + defObj.isPromoted = true; + } + else if (['text', 'number', 'boolean', 'date', 'url'].includes(token)) { + defObj.labelType = token; + } + else if (['single', 'multi'].includes(token)) { + defObj.multiplicity = token; + } + else if (token.startsWith('precision')) { + const chunks = token.split('='); + + defObj.numberPrecision = parseInt(chunks[1]); + } + else if (token.startsWith('inverse')) { + const chunks = token.split('='); + + defObj.inverseRelation = chunks[1]; + } + else { + console.log("Unrecognized attribute definition token:", token); + } + } + + return defObj; +} + +export default { + parse +}; diff --git a/src/public/app/widgets/attribute_detail.js b/src/public/app/widgets/attribute_detail.js index 0d3d4d2f1..b5d1860f9 100644 --- a/src/public/app/widgets/attribute_detail.js +++ b/src/public/app/widgets/attribute_detail.js @@ -4,6 +4,7 @@ import treeService from "../services/tree.js"; import linkService from "../services/link.js"; import BasicWidget from "./basic_widget.js"; import noteAutocompleteService from "../services/note_autocomplete.js"; +import promotedAttributeDefinitionParser from '../services/promoted_attribute_definition_parser.js'; const TPL = `
@@ -27,15 +28,15 @@ const TPL = ` margin-bottom: 10px; } - .attr-edit { + .attr-edit-table { width: 100%; } - .attr-edit th { + .attr-edit-table th { text-align: left; } - .attr-edit td input { + .attr-edit-table td input { width: 100%; } @@ -53,59 +54,70 @@ const TPL = `
- +
- + - + - + - + - + - + - + - + - + + + + + - +
Name:
Value:
Target note:
- +
Promoted:
Multiplicity: - + +
Type: - + + + + +
Precision: +
+ +
+ digits +
+
+
Inverse relation:
- +
Inheritable:
@@ -140,22 +152,37 @@ export default class AttributeDetailWidget extends BasicWidget { this.$relatedNotesList = this.$relatedNotesContainer.find('.related-notes-list'); this.$relatedNotesMoreNotes = this.$relatedNotesContainer.find('.related-notes-more-notes'); - this.$attrEditName = this.$widget.find('.attr-edit-name'); - this.$attrEditName.on('keyup', () => this.updateParent()); + this.$attrInputName = this.$widget.find('.attr-input-name'); + this.$attrInputName.on('keyup', () => this.updateParent()); - this.$attrValueRow = this.$widget.find('.attr-value-row'); - this.$attrEditValue = this.$widget.find('.attr-edit-value'); - this.$attrEditValue.on('keyup', () => this.updateParent()); + this.$attrRowValue = this.$widget.find('.attr-row-value'); + this.$attrInputValue = this.$widget.find('.attr-input-value'); + this.$attrInputValue.on('keyup', () => this.updateParent()); - this.$attrDefinitionPromoted = this.$widget.find('.attr-definition-promoted'); - this.$attrDefinitionMultiplicity = this.$widget.find('.attr-definition-multiplicity'); - this.$attrDefinitionLabelType = this.$widget.find('.attr-definition-label-type'); - this.$attrDefinitionInverseRelation = this.$widget.find('.attr-definition-inverse-relation'); + this.$attrRowPromoted = this.$widget.find('.attr-row-promoted'); + this.$attrInputPromoted = this.$widget.find('.attr-input-promoted'); + this.$attrInputPromoted.on('change', () => this.updateDefinition()); - this.$attrTargetNoteRow = this.$widget.find('.attr-target-note-row'); - this.$attrEditTargetNote = this.$widget.find('.attr-edit-target-note'); + this.$attrRowMultiplicity = this.$widget.find('.attr-row-multiplicity'); + this.$attrInputMultiplicity = this.$widget.find('.attr-input-multiplicity'); + this.$attrInputMultiplicity.on('change', () => this.updateDefinition()); - noteAutocompleteService.initNoteAutocomplete(this.$attrEditTargetNote) + this.$attrRowLabelType = this.$widget.find('.attr-row-label-type'); + this.$attrInputLabelType = this.$widget.find('.attr-input-label-type'); + this.$attrInputLabelType.on('change', () => this.updateDefinition()); + + this.$attrRowNumberPrecision = this.$widget.find('.attr-row-number-precision'); + this.$attrInputNumberPrecision = this.$widget.find('.attr-input-number-precision'); + this.$attrInputNumberPrecision.on('change', () => this.updateDefinition()); + + this.$attrRowInverseRelation = this.$widget.find('.attr-row-inverse-relation'); + this.$attrInputInverseRelation = this.$widget.find('.attr-input-inverse-relation'); + this.$attrInputInverseRelation.on('keyup', () => this.updateDefinition()); + + this.$attrRowTargetNote = this.$widget.find('.attr-row-target-note'); + this.$attrInputTargetNote = this.$widget.find('.attr-input-target-note'); + + noteAutocompleteService.initNoteAutocomplete(this.$attrInputTargetNote) .on('autocomplete:selected', (event, suggestion, dataset) => { if (!suggestion.notePath) { return false; @@ -166,8 +193,8 @@ export default class AttributeDetailWidget extends BasicWidget { this.triggerCommand('updateAttributeList', { attributes: this.allAttributes }); }); - this.$attrEditInheritable = this.$widget.find('.attr-edit-inheritable'); - this.$attrEditInheritable.on('change', () => this.updateParent()); + this.$attrInputInheritable = this.$widget.find('.attr-input-inheritable'); + this.$attrInputInheritable.on('change', () => this.updateParent()); this.$closeAttrDetailButton = this.$widget.find('.close-attr-detail-button'); this.$attrIsOwnedBy = this.$widget.find('.attr-is-owned-by'); @@ -191,9 +218,13 @@ export default class AttributeDetailWidget extends BasicWidget { return; } - const attrType = this.getAttrType(attribute); + this.attrType = this.getAttrType(attribute); - this.$title.text(ATTR_TITLES[attrType]); + const definition = this.attrType.endsWith('-definition') + ? promotedAttributeDefinitionParser.parse(attribute.value) + : {}; + + this.$title.text(ATTR_TITLES[this.attrType]); this.allAttributes = allAttributes; this.attribute = attribute; @@ -246,33 +277,53 @@ export default class AttributeDetailWidget extends BasicWidget { .append(await linkService.createNoteLink(attribute.noteId)) } - this.$attrEditName + this.$attrInputName .val(attribute.name) .attr('readonly', () => !isOwned); - this.$attrValueRow.toggle(attrType === 'label'); - this.$attrTargetNoteRow.toggle(attrType === 'relation'); + this.$attrRowValue.toggle(this.attrType === 'label'); + this.$attrRowTargetNote.toggle(this.attrType === 'relation'); - this.$attrDefinitionPromoted.toggle(['label-definition', 'relation-definition'].includes(attrType)); - this.$attrDefinitionMultiplicity.toggle(['label-definition', 'relation-definition'].includes(attrType)); - this.$attrDefinitionLabelType.toggle(attrType === 'label-definition'); - this.$attrDefinitionInverseRelation.toggle(attrType === 'relation-definition'); + this.$attrRowPromoted.toggle(['label-definition', 'relation-definition'].includes(this.attrType)); + this.$attrInputPromoted + .prop("checked", !!definition.isPromoted) + .attr('disabled', () => !isOwned); + + this.$attrRowMultiplicity.toggle(['label-definition', 'relation-definition'].includes(this.attrType)); + this.$attrInputMultiplicity + .val(definition.multiplicity) + .attr('disabled', () => !isOwned); + + this.$attrRowLabelType.toggle(this.attrType === 'label-definition'); + this.$attrInputLabelType + .val(definition.labelType) + .attr('disabled', () => !isOwned); + + this.$attrRowNumberPrecision.toggle(this.attrType === 'label-definition' && definition.labelType === 'number'); + this.$attrInputNumberPrecision + .val(definition.numberPrecision) + .attr('disabled', () => !isOwned); + + this.$attrRowInverseRelation.toggle(this.attrType === 'relation-definition'); + this.$attrInputInverseRelation + .val(definition.inverseRelation) + .attr('disabled', () => !isOwned); if (attribute.type === 'label') { - this.$attrEditValue + this.$attrInputValue .val(attribute.value) .attr('readonly', () => !isOwned); } else if (attribute.type === 'relation') { const targetNote = await treeCache.getNote(attribute.value); - this.$attrEditTargetNote + this.$attrInputTargetNote .attr('readonly', () => !isOwned) .val(targetNote ? targetNote.title : "") .setSelectedNotePath(attribute.value); } - this.$attrEditInheritable + this.$attrInputInheritable .prop("checked", !!attribute.isInheritable) .attr('disabled', () => !isOwned); @@ -301,9 +352,37 @@ export default class AttributeDetailWidget extends BasicWidget { } updateParent() { - this.attribute.name = this.$attrEditName.val(); - this.attribute.value = this.$attrEditValue.val(); - this.attribute.isInheritable = this.$attrEditInheritable.is(":checked"); + this.attribute.name = this.$attrInputName.val(); + this.attribute.value = this.$attrInputValue.val(); + this.attribute.isInheritable = this.$attrInputInheritable.is(":checked"); + + this.triggerCommand('updateAttributeList', { attributes: this.allAttributes }); + } + + updateDefinition() { + this.attribute.name = this.$attrInputName.val(); + this.attribute.isInheritable = this.$attrInputInheritable.is(":checked"); + + const props = []; + + if (this.$attrInputPromoted.is(":checked")) { + props.push("promoted"); + } + + props.push(this.$attrInputMultiplicity.val()); + + if (this.attrType === 'label-definition') { + props.push(this.$attrInputLabelType.val()); + } + else if (this.attrType === 'relation-definition' && this.$attrInputInverseRelation.val().trim().length > 0) { + props.push("inverse=" + this.$attrInputInverseRelation.val()); + } + + this.$attrRowNumberPrecision.toggle( + this.attrType === 'label-definition' + && this.$attrInputLabelType.val() === 'number'); + + this.attribute.value = props.join(","); this.triggerCommand('updateAttributeList', { attributes: this.allAttributes }); } diff --git a/src/public/app/widgets/attribute_editor.js b/src/public/app/widgets/attribute_editor.js index 80c0054be..1b3e5320e 100644 --- a/src/public/app/widgets/attribute_editor.js +++ b/src/public/app/widgets/attribute_editor.js @@ -234,20 +234,24 @@ export default class AttributeEditorWidget extends TabAwareWidget { return; } - let type, name; + let type, name, value; if (command === 'addNewLabel') { type = 'label'; - name = 'fillName'; + name = 'myLabel'; + value = ''; } else if (command === 'addNewRelation') { type = 'relation'; - name = 'fillName'; + name = 'myRelation'; + value = ''; } else if (command === 'addNewLabelDefinition') { type = 'label'; - name = 'label:fillName'; + name = 'label:myLabel'; + value = 'promoted,single,text'; } else if (command === 'addNewRelationDefinition') { type = 'label'; - name = 'relation:fillName'; + name = 'relation:myRelation'; + value = 'promoted,single'; } else { return; } @@ -255,7 +259,7 @@ export default class AttributeEditorWidget extends TabAwareWidget { attrs.push({ type, name, - value: '', + value, isInheritable: false }); @@ -347,7 +351,7 @@ export default class AttributeEditorWidget extends TabAwareWidget { let matchedAttr = null; for (const attr of parsedAttrs) { - if (clickIndex >= attr.startIndex && clickIndex <= attr.endIndex) { + if (clickIndex > attr.startIndex && clickIndex <= attr.endIndex) { matchedAttr = attr; break; }