removed attributes dialog

This commit is contained in:
zadam 2020-07-04 10:18:01 +02:00
parent 2e24111c2b
commit a0b3bc858d
6 changed files with 4 additions and 327 deletions

View File

@ -1,314 +0,0 @@
import server from '../services/server.js';
import toastService from "../services/toast.js";
import treeService from "../services/tree.js";
import attributeAutocompleteService from "../services/attribute_autocomplete.js";
import utils from "../services/utils.js";
import linkService from "../services/link.js";
import libraryLoader from "../services/library_loader.js";
import noteAutocompleteService from "../services/note_autocomplete.js";
import appContext from "../services/app_context.js";
const $dialog = $("#attributes-dialog");
const $saveAttributesButton = $("#save-attributes-button");
const $ownedAttributesBody = $('#owned-attributes-table tbody');
function AttributesModel() {
const self = this;
this.ownedAttributes = ko.observableArray();
this.inheritedAttributes = ko.observableArray();
this.availableTypes = [
{ text: "Label", value: "label" },
{ text: "Label definition", value: "label-definition" },
{ text: "Relation", value: "relation" },
{ text: "Relation definition", value: "relation-definition" }
];
this.availableLabelTypes = [
{ text: "Text", value: "text" },
{ text: "Number", value: "number" },
{ text: "Boolean", value: "boolean" },
{ text: "Date", value: "date" },
{ text: "URL", value: "url"}
];
this.multiplicityTypes = [
{ text: "Single value", value: "singlevalue" },
{ text: "Multi value", value: "multivalue" }
];
this.typeChanged = function(data, event) {
self.getTargetAttribute(event.target).valueHasMutated();
};
this.labelTypeChanged = function(data, event) {
self.getTargetAttribute(event.target).valueHasMutated();
};
this.updateAttributePositions = function() {
let position = 10;
// we need to update positions by searching in the DOM, because order of the
// attributes in the viewmodel (self.ownedAttributes()) stays the same
$ownedAttributesBody.find('input[name="position"]').each(function() {
const attribute = self.getTargetAttribute(this);
attribute().position = position;
position += 10;
});
};
async function showAttributes(noteId, attributes) {
const ownedAttributes = attributes.filter(attr => attr.noteId === noteId);
for (const attr of ownedAttributes) {
attr.labelValue = attr.type === 'label' ? attr.value : '';
attr.relationValue = attr.type === 'relation' ? (await treeService.getNoteTitle(attr.value)) : '';
attr.selectedPath = attr.type === 'relation' ? attr.value : '';
attr.labelDefinition = (attr.type === 'label-definition' && attr.value) ? attr.value : {
labelType: "text",
multiplicityType: "singlevalue",
isPromoted: true,
numberPrecision: 0
};
attr.relationDefinition = (attr.type === 'relation-definition' && attr.value) ? attr.value : {
multiplicityType: "singlevalue",
inverseRelation: "",
isPromoted: true
};
delete attr.value;
}
self.ownedAttributes(ownedAttributes.map(ko.observable));
addLastEmptyRow();
const inheritedAttributes = attributes.filter(attr => attr.noteId !== noteId);
self.inheritedAttributes(inheritedAttributes);
}
this.loadAttributes = async function() {
const noteId = appContext.tabManager.getActiveTabNoteId();
const attributes = await server.get('notes/' + noteId + '/attributes');
await showAttributes(noteId, attributes);
// attribute might not be rendered immediatelly so could not focus
setTimeout(() => $(".attribute-type-select:last").trigger('focus'), 1000);
};
this.deleteAttribute = function(data, event) {
const attribute = self.getTargetAttribute(event.target);
const attributeData = attribute();
if (attributeData) {
attributeData.isDeleted = true;
attribute(attributeData);
addLastEmptyRow();
}
};
function isValid() {
for (let attributes = self.ownedAttributes(), i = 0; i < attributes.length; i++) {
if (self.isEmptyName(i) || self.isEmptyRelationTarget(i)) {
return false;
}
}
return true;
}
this.save = async function() {
// we need to defocus from input (in case of enter-triggered save) because value is updated
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
$saveAttributesButton.trigger('focus');
if (!isValid()) {
alert("Please fix all validation errors and try saving again.");
return;
}
self.updateAttributePositions();
const noteId = appContext.tabManager.getActiveTabNoteId();
const attributesToSave = self.ownedAttributes()
.map(attribute => attribute())
.filter(attribute => attribute.attributeId !== "" || attribute.name !== "");
for (const attr of attributesToSave) {
if (attr.type === 'label') {
attr.value = attr.labelValue;
}
else if (attr.type === 'relation') {
attr.value = treeService.getNoteIdFromNotePath(attr.selectedPath);
}
else if (attr.type === 'label-definition') {
attr.value = JSON.stringify(attr.labelDefinition);
}
else if (attr.type === 'relation-definition') {
attr.value = JSON.stringify(attr.relationDefinition);
}
delete attr.labelValue;
delete attr.relationValue;
delete attr.labelDefinition;
delete attr.relationDefinition;
}
const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave);
await showAttributes(noteId, attributes);
toastService.showMessage("Attributes have been saved.");
};
function addLastEmptyRow() {
const attributes = self.ownedAttributes().filter(attr => !attr().isDeleted);
const last = attributes.length === 0 ? null : attributes[attributes.length - 1]();
if (!last || last.name.trim() !== "") {
self.ownedAttributes.push(ko.observable({
attributeId: '',
type: 'label',
name: '',
labelValue: '',
relationValue: '',
isInheritable: false,
isDeleted: false,
position: 0,
labelDefinition: {
labelType: "text",
multiplicityType: "singlevalue",
isPromoted: true,
numberPrecision: 0
},
relationDefinition: {
multiplicityType: "singlevalue",
inverseRelation: "",
isPromoted: true
}
}));
}
}
this.attributeChanged = function (data, event) {
addLastEmptyRow();
const attribute = self.getTargetAttribute(event.target);
attribute.valueHasMutated();
};
this.isEmptyName = function(index) {
const cur = self.ownedAttributes()[index]();
if (cur.name.trim() || cur.isDeleted) {
return false;
}
if (cur.attributeId) {
// name is empty and attribute already exists so this is NO-GO
return true;
}
if (cur.type === 'relation-definition' || cur.type === 'label-definition') {
// for definitions there's no possible empty value so we always require name
return true;
}
if (cur.type === 'label' && cur.labelValue) {
return true;
}
if (cur.type === 'relation' && cur.relationValue) {
return true;
}
return false;
};
this.isEmptyRelationTarget = function(index) {
const cur = self.ownedAttributes()[index]();
return cur.type === "relation" && !cur.isDeleted && cur.name && !cur.relationValue;
};
this.getTargetAttribute = function(target) {
const context = ko.contextFor(target);
const index = context.$index();
return self.ownedAttributes()[index];
}
}
let attributesModel;
function initKoPlugins() {
ko.bindingHandlers.noteLink = {
init: async function (element, valueAccessor, allBindings, viewModel, bindingContext) {
const noteId = ko.unwrap(valueAccessor());
if (noteId) {
const link = await linkService.createNoteLink(noteId);
$(element).append(link);
}
}
};
ko.bindingHandlers.noteAutocomplete = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
noteAutocompleteService.initNoteAutocomplete($(element));
$(element).setSelectedNotePath(bindingContext.$data.selectedPath);
$(element).on('autocomplete:selected', function (event, suggestion, dataset) {
bindingContext.$data.selectedPath = $(element).val().trim() ? suggestion.path : '';
});
}
};
}
export async function showDialog() {
await libraryLoader.requireLibrary(libraryLoader.KNOCKOUT);
// lazily apply bindings on first use
if (!attributesModel) {
attributesModel = new AttributesModel();
initKoPlugins();
ko.applyBindings(attributesModel, $dialog[0]);
}
await attributesModel.loadAttributes();
utils.openDialog($dialog);
}
$dialog.on('focus', '.attribute-name', function (e) {
attributeAutocompleteService.initAttributeNameAutocomplete({
$el: $(this),
attributeType: () => {
const attribute = attributesModel.getTargetAttribute(this);
return (attribute().type === 'relation' || attribute().type === 'relation-definition') ? 'relation' : 'label';
},
open: true
});
});
$dialog.on('focus', '.label-value', function (e) {
attributeAutocompleteService.initLabelValueAutocomplete({
$el: $(this),
open: true
})
});

View File

@ -12,10 +12,6 @@ export default class DialogCommandExecutor extends Component {
import("../dialogs/recent_changes.js").then(d => d.showDialog());
}
showAttributesCommand() {
import("../dialogs/attributes.js").then(d => d.showDialog());
}
showNoteInfoCommand() {
import("../dialogs/note_info.js").then(d => d.showDialog());
}
@ -74,4 +70,4 @@ export default class DialogCommandExecutor extends Component {
showBackendLogCommand() {
import("../dialogs/backend_log.js").then(d => d.showDialog());
}
}
}

View File

@ -45,8 +45,6 @@ const LINK_MAP = {
const PRINT_THIS = {js: ["libraries/printThis.js"]};
const KNOCKOUT = {js: ["libraries/knockout.min.js"]};
const CALENDAR_WIDGET = {css: ["stylesheets/calendar.css"]};
async function requireLibrary(library) {
@ -96,6 +94,5 @@ export default {
RELATION_MAP,
LINK_MAP,
PRINT_THIS,
KNOCKOUT,
CALENDAR_WIDGET
}
}

View File

@ -80,7 +80,6 @@ const TPL = `
</span>
</div>
<a data-trigger-command="showNoteRevisions" class="dropdown-item show-note-revisions-button">Revisions</a>
<a data-trigger-command="showAttributes" class="dropdown-item show-attributes-button"><kbd data-command="showAttributes"></kbd> Attributes</a>
<a data-trigger-command="showLinkMap" class="dropdown-item show-link-map-button"><kbd data-command="showLinkMap"></kbd> Link map</a>
<a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</a>
<a class="dropdown-item import-files-button">Import files</a>

View File

@ -230,10 +230,10 @@ const DEFAULT_KEYBOARD_ACTIONS = [
{
separator: "Dialogs"
},
{
{ // FIXME
actionName: "showAttributes",
defaultShortcuts: ["Alt+A"],
description: "Shows Attributes dialog",
description: "Shows Attributes",
scope: "window"
},
{

View File

@ -138,7 +138,6 @@
<li><kbd data-command="toggleZenMode"></kbd> - Zen mode - display only note editor, everything else is hidden</li>
<li><kbd data-command="searchNotes"></kbd> - toggle search form in tree pane</li>
<li><kbd data-command="findInText"></kbd> - in page search</li>
<li><kbd data-command="showAttributes"></kbd> - show note attributes dialog</li>
</ul>
</p>
</div>