class moving

This commit is contained in:
zadam
2021-01-30 15:50:46 +01:00
parent 552d213817
commit ae26f22114
8 changed files with 39 additions and 39 deletions

View File

@@ -1,74 +0,0 @@
import TabAwareWidget from "../tab_aware_widget.js";
import AttributeDetailWidget from "./attribute_detail.js";
import AttributeEditorWidget from "./attribute_editor.js";
const TPL = `
<div class="attribute-list">
<style>
.attribute-list {
margin-left: 7px;
margin-right: 7px;
margin-top: 3px;
position: relative;
}
.attribute-list-editor p {
margin: 0 !important;
}
</style>
<div class="attr-editor-placeholder"></div>
</div>
`;
export default class OwnedAttributeListWidget extends TabAwareWidget {
constructor() {
super();
this.attributeDetailWidget = new AttributeDetailWidget().setParent(this);
this.attributeEditorWidget = new AttributeEditorWidget(this.attributeDetailWidget).setParent(this);
this.child(this.attributeEditorWidget, this.attributeDetailWidget);
}
renderTitle(note) {
const ownedAttrs = note.getAttributes().filter(attr => attr.noteId === this.noteId && !attr.isAutoLink)
this.$title.text(`Owned attrs (${ownedAttrs.length})`);
return {
show: true,
$title: this.$title
};
}
doRender() {
this.$widget = $(TPL);
this.overflowing();
this.$widget.find('.attr-editor-placeholder').replaceWith(this.attributeEditorWidget.render());
this.$widget.append(this.attributeDetailWidget.render());
this.$title = $('<div>');
}
async saveAttributesCommand() {
await this.attributeEditorWidget.save();
}
async reloadAttributesCommand() {
await this.attributeEditorWidget.refresh();
}
async updateAttributeListCommand({attributes}) {
await this.attributeEditorWidget.updateAttributeList(attributes);
}
entitiesReloadedEvent({loadResults}) {
if (loadResults.getAttributes(this.componentId).find(attr => attr.isAffecting(this.note))) {
this.refreshWithNote(this.note, true);
this.renderTitle(this.note);
}
}
}

View File

@@ -1,298 +0,0 @@
import server from "../../services/server.js";
import ws from "../../services/ws.js";
import treeService from "../../services/tree.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
import TabAwareWidget from "../tab_aware_widget.js";
const TPL = `
<div>
<style>
.promoted-attributes-container {
margin: auto;
display: flex;
flex-direction: row;
flex-shrink: 0;
flex-grow: 0;
justify-content: space-evenly;
overflow: auto;
max-height: 400px;
flex-wrap: wrap;
}
.promoted-attribute-cell {
display: flex;
align-items: center;
margin: 10px;
}
.promoted-attribute-cell div.input-group {
margin-left: 10px;
}
</style>
<div class="promoted-attributes-container"></div>
</div>
`;
export default class PromotedAttributesWidget extends TabAwareWidget {
doRender() {
this.$widget = $(TPL);
this.overflowing();
this.$container = this.$widget.find(".promoted-attributes-container");
this.$title = $('<div>');
}
renderTitle(note) {
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
if (promotedDefAttrs.length === 0) {
return { show: false };
}
this.$title.text(`Promoted attrs (${promotedDefAttrs.length})`);
return {
show: true,
activate: true,
$title: this.$title
};
}
async refreshWithNote(note) {
this.$container.empty();
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
const ownedAttributes = note.getOwnedAttributes();
if (promotedDefAttrs.length === 0) {
this.toggleInt(false);
return;
}
const $cells = [];
for (const definitionAttr of promotedDefAttrs) {
const valueType = definitionAttr.name.startsWith('label:') ? 'label' : 'relation';
const valueName = definitionAttr.name.substr(valueType.length + 1);
let valueAttrs = ownedAttributes.filter(el => el.name === valueName && el.type === valueType);
if (valueAttrs.length === 0) {
valueAttrs.push({
attributeId: "",
type: valueType,
name: valueName,
value: ""
});
}
if (definitionAttr.getDefinition().multiplicity === 'single') {
valueAttrs = valueAttrs.slice(0, 1);
}
for (const valueAttr of valueAttrs) {
const $cell = await this.createPromotedAttributeCell(definitionAttr, valueAttr, valueName);
$cells.push($cell);
}
}
// we replace the whole content in one step so there can't be any race conditions
// (previously we saw promoted attributes doubling)
this.$container.empty().append(...$cells);
this.toggleInt(true);
}
async createPromotedAttributeCell(definitionAttr, valueAttr, valueName) {
const definition = definitionAttr.getDefinition();
const $input = $("<input>")
.prop("tabindex", 200 + definitionAttr.position)
.prop("attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one
.prop("attribute-type", valueAttr.type)
.prop("attribute-name", valueAttr.name)
.prop("value", valueAttr.value)
.addClass("form-control")
.addClass("promoted-attribute-input")
.on('change', event => this.promotedAttributeChanged(event));
const $actionCell = $("<div>");
const $multiplicityCell = $("<td>")
.addClass("multiplicity")
.attr("nowrap", true);
const $wrapper = $('<div class="promoted-attribute-cell">')
.append($("<strong>").text(valueName))
.append($("<div>").addClass("input-group").append($input))
.append($actionCell)
.append($multiplicityCell);
if (valueAttr.type === 'label') {
if (definition.labelType === 'text') {
$input.prop("type", "text");
// no need to await for this, can be done asynchronously
server.get('attributes/values/' + encodeURIComponent(valueAttr.name)).then(attributeValues => {
if (attributeValues.length === 0) {
return;
}
attributeValues = attributeValues.map(attribute => ({ value: attribute }));
$input.autocomplete({
appendTo: document.querySelector('body'),
hint: false,
autoselect: false,
openOnFocus: true,
minLength: 0,
tabAutocomplete: false
}, [{
displayKey: 'value',
source: function (term, cb) {
term = term.toLowerCase();
const filtered = attributeValues.filter(attr => attr.value.toLowerCase().includes(term));
cb(filtered);
}
}]);
$input.on('autocomplete:selected', e => this.promotedAttributeChanged(e))
});
}
else if (definition.labelType === 'number') {
$input.prop("type", "number");
let step = 1;
for (let i = 0; i < (definition.numberPrecision || 0) && i < 10; i++) {
step /= 10;
}
$input.prop("step", step);
$input
.css("text-align", "right")
.css("width", "120");
}
else if (definition.labelType === 'boolean') {
$input.prop("type", "checkbox");
// hack, without this the checkbox is invisible
// we should be using a different bootstrap structure for checkboxes
$input.css('width', '80px');
if (valueAttr.value === "true") {
$input.prop("checked", "checked");
}
}
else if (definition.labelType === 'date') {
$input.prop("type", "date");
}
else if (definition.labelType === 'url') {
$input.prop("placeholder", "http://website...");
const $openButton = $("<span>")
.addClass("input-group-text open-external-link-button bx bx-window-open")
.prop("title", "Open external link")
.on('click', () => window.open($input.val(), '_blank'));
$input.after($("<div>")
.addClass("input-group-append")
.append($openButton));
}
else {
ws.logError("Unknown labelType=" + definitionAttr.labelType);
}
}
else if (valueAttr.type === 'relation') {
if (valueAttr.value) {
$input.val(await treeService.getNoteTitle(valueAttr.value));
}
// no need to wait for this
noteAutocompleteService.initNoteAutocomplete($input);
$input.on('autocomplete:noteselected', (event, suggestion, dataset) => {
this.promotedAttributeChanged(event);
});
$input.setSelectedNotePath(valueAttr.value);
}
else {
ws.logError("Unknown attribute type=" + valueAttr.type);
return;
}
if (definition.multiplicity === "multi") {
const addButton = $("<span>")
.addClass("bx bx-plus pointer")
.prop("title", "Add new attribute")
.on('click', async () => {
const $new = await this.createPromotedAttributeCell(definitionAttr, {
attributeId: "",
type: valueAttr.type,
name: valueName,
value: ""
}, valueName);
$wrapper.after($new);
$new.find('input').trigger('focus');
});
const removeButton = $("<span>")
.addClass("bx bx-trash pointer")
.prop("title", "Remove this attribute")
.on('click', async () => {
if (valueAttr.attributeId) {
await server.remove("notes/" + this.noteId + "/attributes/" + valueAttr.attributeId, this.componentId);
}
$wrapper.remove();
});
$multiplicityCell
.append(" &nbsp;")
.append(addButton)
.append(" &nbsp;")
.append(removeButton);
}
return $wrapper;
}
async promotedAttributeChanged(event) {
const $attr = $(event.target);
let value;
if ($attr.prop("type") === "checkbox") {
value = $attr.is(':checked') ? "true" : "false";
}
else if ($attr.prop("attribute-type") === "relation") {
const selectedPath = $attr.getSelectedNotePath();
value = selectedPath ? treeService.getNoteIdFromNotePath(selectedPath) : "";
}
else {
value = $attr.val();
}
const result = await server.put(`notes/${this.noteId}/attribute`, {
attributeId: $attr.prop("attribute-id"),
type: $attr.prop("attribute-type"),
name: $attr.prop("attribute-name"),
value: value
}, this.componentId);
$attr.prop("attribute-id", result.attributeId);
}
entitiesReloadedEvent({loadResults}) {
if (loadResults.getAttributes(this.componentId).find(attr => attr.isAffecting(this.note))) {
this.refresh();
this.renderTitle(this.note);
}
}
}