attr detail handles label/relation definition updates

This commit is contained in:
zadam 2020-07-18 23:45:28 +02:00
parent 9d46c7253b
commit df69b1d8dd
5 changed files with 190 additions and 98 deletions

View File

@ -10,16 +10,16 @@ module.exports = () => {
tokens.push('promoted'); tokens.push('promoted');
} }
if (obj.labelType) {
tokens.push(obj.labelType);
}
if (obj.multiplicityType === 'singlevalue') { if (obj.multiplicityType === 'singlevalue') {
tokens.push('single'); tokens.push('single');
} else if (obj.multiplicityType === 'multivalue') { } else if (obj.multiplicityType === 'multivalue') {
tokens.push('multi'); tokens.push('multi');
} }
if (obj.labelType) {
tokens.push(obj.labelType);
}
if (obj.numberPrecision) { if (obj.numberPrecision) {
tokens.push('precision='+obj.numberPrecision); tokens.push('precision='+obj.numberPrecision);
} }
@ -38,16 +38,16 @@ module.exports = () => {
tokens.push('promoted'); tokens.push('promoted');
} }
if (obj.inverseRelation) {
tokens.push('inverse=' + obj.inverseRelation);
}
if (obj.multiplicityType === 'singlevalue') { if (obj.multiplicityType === 'singlevalue') {
tokens.push('single'); tokens.push('single');
} else if (obj.multiplicityType === 'multivalue') { } else if (obj.multiplicityType === 'multivalue') {
tokens.push('multi'); tokens.push('multi');
} }
if (obj.inverseRelation) {
tokens.push('inverse=' + obj.inverseRelation);
}
const newValue = tokens.join(','); const newValue = tokens.join(',');
sql.execute('UPDATE attributes SET value = ? WHERE attributeId = ?', [newValue, attr.attributeId]); sql.execute('UPDATE attributes SET value = ? WHERE attributeId = ?', [newValue, attr.attributeId]);

View File

@ -1,3 +1,5 @@
import promotedAttributeDefinitionParser from '../services/promoted_attribute_definition_parser.js';
class Attribute { class Attribute {
constructor(treeCache, row) { constructor(treeCache, row) {
this.treeCache = treeCache; this.treeCache = treeCache;
@ -76,35 +78,7 @@ class Attribute {
} }
getDefinition() { getDefinition() {
const tokens = this.value.split(',').map(t => t.trim()); return promotedAttributeDefinitionParser.parse(this.value);
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;
} }
} }

View File

@ -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
};

View File

@ -4,6 +4,7 @@ import treeService from "../services/tree.js";
import linkService from "../services/link.js"; import linkService from "../services/link.js";
import BasicWidget from "./basic_widget.js"; import BasicWidget from "./basic_widget.js";
import noteAutocompleteService from "../services/note_autocomplete.js"; import noteAutocompleteService from "../services/note_autocomplete.js";
import promotedAttributeDefinitionParser from '../services/promoted_attribute_definition_parser.js';
const TPL = ` const TPL = `
<div class="attr-detail"> <div class="attr-detail">
@ -27,15 +28,15 @@ const TPL = `
margin-bottom: 10px; margin-bottom: 10px;
} }
.attr-edit { .attr-edit-table {
width: 100%; width: 100%;
} }
.attr-edit th { .attr-edit-table th {
text-align: left; text-align: left;
} }
.attr-edit td input { .attr-edit-table td input {
width: 100%; width: 100%;
} }
@ -53,59 +54,70 @@ const TPL = `
<div class="attr-is-owned-by"></div> <div class="attr-is-owned-by"></div>
<table class="attr-edit"> <table class="attr-edit-table">
<tr> <tr>
<th>Name:</th> <th>Name:</th>
<td><input type="text" class="attr-edit-name form-control form-control-sm" /></td> <td><input type="text" class="attr-input-name form-control" /></td>
</tr> </tr>
<tr class="attr-value-row"> <tr class="attr-row-value">
<th>Value:</th> <th>Value:</th>
<td><input type="text" class="attr-edit-value form-control form-control-sm" /></td> <td><input type="text" class="attr-input-value form-control" /></td>
</tr> </tr>
<tr class="attr-target-note-row"> <tr class="attr-row-target-note">
<th>Target note:</th> <th>Target note:</th>
<td> <td>
<div class="input-group"> <div class="input-group">
<input type="text" class="attr-edit-target-note form-control" /> <input type="text" class="attr-input-target-note form-control" />
</div> </div>
</td> </td>
</tr> </tr>
<tr class="attr-definition-promoted"> <tr class="attr-row-promoted">
<th>Promoted:</th> <th>Promoted:</th>
<td><input type="checkbox" class="attr-edit-inheritable form-control form-control-sm" /></td> <td><input type="checkbox" class="attr-input-promoted form-control form-control-sm" /></td>
</tr> </tr>
<tr class="attr-definition-multiplicity"> <tr class="attr-row-multiplicity">
<th>Multiplicity:</th> <th>Multiplicity:</th>
<td> <td>
<select class="form-control"> <select class="attr-input-multiplicity form-control">
<option>Single value</option> <option value="single">Single value</option>
<option>Multi value</option> <option value="multi">Multi value</option>
</select> </select>
</td> </td>
</tr> </tr>
<tr class="attr-definition-label-type"> <tr class="attr-row-label-type">
<th>Type:</th> <th>Type:</th>
<td> <td>
<select class="form-control"> <select class="attr-input-label-type form-control">
<option>Text</option> <option value="text">Text</option>
<option>Number</option> <option value="number">Number</option>
<option>Boolean</option> <option value="boolean">Boolean</option>
<option>Date</option> <option value="date">Date</option>
<option>URL</option> <option value="url">URL</option>
</select> </select>
</td> </td>
</tr> </tr>
<tr class="attr-definition-inverse-relation"> <tr class="attr-row-number-precision">
<th>Precision:</th>
<td>
<div class="input-group">
<input type="number" class="form-control attr-input-number-precision" style="text-align: right">
<div class="input-group-append">
<span class="input-group-text">digits</span>
</div>
</div>
</td>
</tr>
<tr class="attr-row-inverse-relation">
<th>Inverse relation:</th> <th>Inverse relation:</th>
<td> <td>
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" /> <input type="text" class="attr-input-inverse-relation form-control" />
</div> </div>
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Inheritable:</th> <th>Inheritable:</th>
<td><input type="checkbox" class="attr-edit-inheritable form-control form-control-sm" /></td> <td><input type="checkbox" class="attr-input-inheritable form-control form-control-sm" /></td>
</tr> </tr>
</table> </table>
@ -140,22 +152,37 @@ export default class AttributeDetailWidget extends BasicWidget {
this.$relatedNotesList = this.$relatedNotesContainer.find('.related-notes-list'); this.$relatedNotesList = this.$relatedNotesContainer.find('.related-notes-list');
this.$relatedNotesMoreNotes = this.$relatedNotesContainer.find('.related-notes-more-notes'); this.$relatedNotesMoreNotes = this.$relatedNotesContainer.find('.related-notes-more-notes');
this.$attrEditName = this.$widget.find('.attr-edit-name'); this.$attrInputName = this.$widget.find('.attr-input-name');
this.$attrEditName.on('keyup', () => this.updateParent()); this.$attrInputName.on('keyup', () => this.updateParent());
this.$attrValueRow = this.$widget.find('.attr-value-row'); this.$attrRowValue = this.$widget.find('.attr-row-value');
this.$attrEditValue = this.$widget.find('.attr-edit-value'); this.$attrInputValue = this.$widget.find('.attr-input-value');
this.$attrEditValue.on('keyup', () => this.updateParent()); this.$attrInputValue.on('keyup', () => this.updateParent());
this.$attrDefinitionPromoted = this.$widget.find('.attr-definition-promoted'); this.$attrRowPromoted = this.$widget.find('.attr-row-promoted');
this.$attrDefinitionMultiplicity = this.$widget.find('.attr-definition-multiplicity'); this.$attrInputPromoted = this.$widget.find('.attr-input-promoted');
this.$attrDefinitionLabelType = this.$widget.find('.attr-definition-label-type'); this.$attrInputPromoted.on('change', () => this.updateDefinition());
this.$attrDefinitionInverseRelation = this.$widget.find('.attr-definition-inverse-relation');
this.$attrTargetNoteRow = this.$widget.find('.attr-target-note-row'); this.$attrRowMultiplicity = this.$widget.find('.attr-row-multiplicity');
this.$attrEditTargetNote = this.$widget.find('.attr-edit-target-note'); 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) => { .on('autocomplete:selected', (event, suggestion, dataset) => {
if (!suggestion.notePath) { if (!suggestion.notePath) {
return false; return false;
@ -166,8 +193,8 @@ export default class AttributeDetailWidget extends BasicWidget {
this.triggerCommand('updateAttributeList', { attributes: this.allAttributes }); this.triggerCommand('updateAttributeList', { attributes: this.allAttributes });
}); });
this.$attrEditInheritable = this.$widget.find('.attr-edit-inheritable'); this.$attrInputInheritable = this.$widget.find('.attr-input-inheritable');
this.$attrEditInheritable.on('change', () => this.updateParent()); this.$attrInputInheritable.on('change', () => this.updateParent());
this.$closeAttrDetailButton = this.$widget.find('.close-attr-detail-button'); this.$closeAttrDetailButton = this.$widget.find('.close-attr-detail-button');
this.$attrIsOwnedBy = this.$widget.find('.attr-is-owned-by'); this.$attrIsOwnedBy = this.$widget.find('.attr-is-owned-by');
@ -191,9 +218,13 @@ export default class AttributeDetailWidget extends BasicWidget {
return; 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.allAttributes = allAttributes;
this.attribute = attribute; this.attribute = attribute;
@ -246,33 +277,53 @@ export default class AttributeDetailWidget extends BasicWidget {
.append(await linkService.createNoteLink(attribute.noteId)) .append(await linkService.createNoteLink(attribute.noteId))
} }
this.$attrEditName this.$attrInputName
.val(attribute.name) .val(attribute.name)
.attr('readonly', () => !isOwned); .attr('readonly', () => !isOwned);
this.$attrValueRow.toggle(attrType === 'label'); this.$attrRowValue.toggle(this.attrType === 'label');
this.$attrTargetNoteRow.toggle(attrType === 'relation'); this.$attrRowTargetNote.toggle(this.attrType === 'relation');
this.$attrDefinitionPromoted.toggle(['label-definition', 'relation-definition'].includes(attrType)); this.$attrRowPromoted.toggle(['label-definition', 'relation-definition'].includes(this.attrType));
this.$attrDefinitionMultiplicity.toggle(['label-definition', 'relation-definition'].includes(attrType)); this.$attrInputPromoted
this.$attrDefinitionLabelType.toggle(attrType === 'label-definition'); .prop("checked", !!definition.isPromoted)
this.$attrDefinitionInverseRelation.toggle(attrType === 'relation-definition'); .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') { if (attribute.type === 'label') {
this.$attrEditValue this.$attrInputValue
.val(attribute.value) .val(attribute.value)
.attr('readonly', () => !isOwned); .attr('readonly', () => !isOwned);
} }
else if (attribute.type === 'relation') { else if (attribute.type === 'relation') {
const targetNote = await treeCache.getNote(attribute.value); const targetNote = await treeCache.getNote(attribute.value);
this.$attrEditTargetNote this.$attrInputTargetNote
.attr('readonly', () => !isOwned) .attr('readonly', () => !isOwned)
.val(targetNote ? targetNote.title : "") .val(targetNote ? targetNote.title : "")
.setSelectedNotePath(attribute.value); .setSelectedNotePath(attribute.value);
} }
this.$attrEditInheritable this.$attrInputInheritable
.prop("checked", !!attribute.isInheritable) .prop("checked", !!attribute.isInheritable)
.attr('disabled', () => !isOwned); .attr('disabled', () => !isOwned);
@ -301,9 +352,37 @@ export default class AttributeDetailWidget extends BasicWidget {
} }
updateParent() { updateParent() {
this.attribute.name = this.$attrEditName.val(); this.attribute.name = this.$attrInputName.val();
this.attribute.value = this.$attrEditValue.val(); this.attribute.value = this.$attrInputValue.val();
this.attribute.isInheritable = this.$attrEditInheritable.is(":checked"); 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 }); this.triggerCommand('updateAttributeList', { attributes: this.allAttributes });
} }

View File

@ -234,20 +234,24 @@ export default class AttributeEditorWidget extends TabAwareWidget {
return; return;
} }
let type, name; let type, name, value;
if (command === 'addNewLabel') { if (command === 'addNewLabel') {
type = 'label'; type = 'label';
name = 'fillName'; name = 'myLabel';
value = '';
} else if (command === 'addNewRelation') { } else if (command === 'addNewRelation') {
type = 'relation'; type = 'relation';
name = 'fillName'; name = 'myRelation';
value = '';
} else if (command === 'addNewLabelDefinition') { } else if (command === 'addNewLabelDefinition') {
type = 'label'; type = 'label';
name = 'label:fillName'; name = 'label:myLabel';
value = 'promoted,single,text';
} else if (command === 'addNewRelationDefinition') { } else if (command === 'addNewRelationDefinition') {
type = 'label'; type = 'label';
name = 'relation:fillName'; name = 'relation:myRelation';
value = 'promoted,single';
} else { } else {
return; return;
} }
@ -255,7 +259,7 @@ export default class AttributeEditorWidget extends TabAwareWidget {
attrs.push({ attrs.push({
type, type,
name, name,
value: '', value,
isInheritable: false isInheritable: false
}); });
@ -347,7 +351,7 @@ export default class AttributeEditorWidget extends TabAwareWidget {
let matchedAttr = null; let matchedAttr = null;
for (const attr of parsedAttrs) { for (const attr of parsedAttrs) {
if (clickIndex >= attr.startIndex && clickIndex <= attr.endIndex) { if (clickIndex > attr.startIndex && clickIndex <= attr.endIndex) {
matchedAttr = attr; matchedAttr = attr;
break; break;
} }