UI for search actions (WIP)

This commit is contained in:
zadam
2021-01-19 22:10:24 +01:00
parent cb2361e9c4
commit 1f923403de
12 changed files with 514 additions and 173 deletions

View File

@@ -88,6 +88,13 @@ class Attribute {
getDefinition() {
return promotedAttributeDefinitionParser.parse(this.value);
}
get dto() {
const dto = Object.assign({}, this);
delete dto.treeCache;
return dto;
}
}
export default Attribute;

View File

@@ -3,6 +3,7 @@ import appContext from "./app_context.js";
import utils from './utils.js';
import noteCreateService from './note_create.js';
import treeService from './tree.js';
import treeCache from "./tree_cache.js";
// this key needs to have this value so it's hit by the tooltip
const SELECTED_NOTE_PATH_KEY = "data-note-path";
@@ -249,6 +250,14 @@ function init() {
.find(".go-to-selected-note-button")
.toggleClass("disabled", true);
}
$.fn.setNote = async function (noteId) {
const note = noteId ? await treeCache.getNote(noteId, true) : null;
$(this)
.val(note ? note.title : "")
.setSelectedNotePath(noteId);
}
}
export default {

View File

@@ -0,0 +1,39 @@
import server from "../../services/server.js";
import ws from "../../services/ws.js";
export default class AbstractAction {
constructor(attribute, actionDef) {
this.attribute = attribute;
this.actionDef = actionDef;
}
render() {
try {
const $rendered = this.doRender();
$rendered.attr('data-attribute-id', this.attribute.attributeId);
return $rendered;
}
catch (e) {
logError(`Failed rendering search action: ${JSON.stringify(this.attribute.dto)} with error: ${e.message} ${e.stack}`);
return null;
}
}
// to be overriden
doRender() {}
async saveAction(data) {
const actionObject = Object.assign({ name: this.constructor.actionName }, data);
await server.put(`notes/${this.attribute.noteId}/attribute`, {
attributeId: this.attribute.attributeId,
type: 'label',
name: 'action',
value: JSON.stringify(actionObject)
});
await ws.waitForMaxKnownEntityChangeId();
}
}

View File

@@ -0,0 +1,37 @@
import SpacedUpdate from "../../services/spaced_update.js";
import AbstractAction from "./abstract_action.js";
const TPL = `
<tr>
<td>
Delete label:
</td>
<td>
<div style="display: flex; align-items: center">
<div style="margin-right: 15px;" class="text-nowrap">Label name:</div>
<input type="text" class="form-control label-name"/>
</div>
</td>
<td>
<span class="bx bx-x icon-action" data-action-conf-del></span>
</td>
</tr>`;
export default class DeleteLabelSearchAction extends AbstractAction {
static get actionName() { return "deleteLabel"; }
doRender() {
const $action = $(TPL);
const $labelName = $action.find('.label-name');
$labelName.val(this.actionDef.labelName || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({ labelName: $labelName.val() });
}, 1000)
$labelName.on('input', () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,21 @@
import AbstractAction from "./abstract_action.js";
const TPL = `
<tr>
<td colspan="2">
<span class="bx bx-trash"></span>
Delete matched note
</td>
<td>
<span class="bx bx-x icon-action" data-action-conf-del></span>
</td>
</tr>`;
export default class DeleteNoteSearchAction extends AbstractAction {
static get actionName() { return "deleteNote"; }
doRender() {
return $(TPL);
}
}

View File

@@ -0,0 +1,37 @@
import SpacedUpdate from "../../services/spaced_update.js";
import AbstractAction from "./abstract_action.js";
const TPL = `
<tr>
<td>
Delete relation:
</td>
<td>
<div style="display: flex; align-items: center">
<div style="margin-right: 15px;" class="text-nowrap">Relation name:</div>
<input type="text" class="form-control relation-name"/>
</div>
</td>
<td>
<span class="bx bx-x icon-action" data-action-conf-del></span>
</td>
</tr>`;
export default class DeleteRelationSearchAction extends AbstractAction {
static get actionName() { return "deleteRelation"; }
doRender() {
const $action = $(TPL);
const $relationName = $action.find('.relation-name');
$relationName.val(this.actionDef.relationName || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({ relationName: $relationName.val() });
}, 1000)
$relationName.on('input', () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,51 @@
import SpacedUpdate from "../../services/spaced_update.js";
import AbstractAction from "./abstract_action.js";
const TPL = `
<tr>
<td>
Rename label:
</td>
<td>
<div style="display: flex; align-items: center">
<div style="display: flex; align-items: center">
<div style="margin-right: 15px;">From:</div>
<input type="text" class="form-control old-label-name" placeholder="old name"/>
<div style="margin-right: 15px; margin-left: 15px;">To:</div>
<input type="text" class="form-control new-label-name" placeholder="new name"/>
</div>
</div>
</td>
<td>
<span class="bx bx-x icon-action" data-action-conf-del></span>
</td>
</tr>`;
export default class RenameLabelSearchAction extends AbstractAction {
static get actionName() { return "renameLabel"; }
doRender() {
const $action = $(TPL);
const $oldLabelName = $action.find('.old-label-name');
$oldLabelName.val(this.actionDef.oldLabelName || "");
const $newLabelName = $action.find('.new-label-name');
$newLabelName.val(this.actionDef.newLabelName || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
oldLabelName: $oldLabelName.val(),
newLabelName: $newLabelName.val()
});
}, 1000)
$oldLabelName.on('input', () => spacedUpdate.scheduleUpdate());
$newLabelName.on('input', () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,51 @@
import SpacedUpdate from "../../services/spaced_update.js";
import AbstractAction from "./abstract_action.js";
const TPL = `
<tr>
<td>
Set label value:
</td>
<td>
<div style="display: flex; align-items: center">
<div style="display: flex; align-items: center">
<div style="margin-right: 15px;" class="text-nowrap">Set label</div>
<input type="text" class="form-control label-name" placeholder="label name"/>
<div style="margin-right: 15px; margin-left: 15px;" class="text-nowrap">to value</div>
<input type="text" class="form-control label-value" placeholder="new value"/>
</div>
</div>
</td>
<td>
<span class="bx bx-x icon-action" data-action-conf-del></span>
</td>
</tr>`;
export default class SetLabelValueSearchAction extends AbstractAction {
static get actionName() { return "setLabelValue"; }
doRender() {
const $action = $(TPL);
const $labelName = $action.find('.label-name');
$labelName.val(this.actionDef.labelName || "");
const $labelValue = $action.find('.label-value');
$labelValue.val(this.actionDef.labelValue || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
labelName: $labelName.val(),
labelValue: $labelValue.val()
});
}, 1000)
$labelName.on('input', () => spacedUpdate.scheduleUpdate());
$labelValue.on('input', () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,54 @@
import SpacedUpdate from "../../services/spaced_update.js";
import AbstractAction from "./abstract_action.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
const TPL = `
<tr>
<td>
Set relation target note:
</td>
<td>
<div style="display: flex; align-items: center">
<div style="margin-right: 15px;" class="text-nowrap">Set relation</div>
<input type="text" class="form-control relation-name" placeholder="relation name"/>
</div>
<div style="display: flex; align-items: center; margin-top: 10px;">
<div style="margin-right: 15px;" class="text-nowrap">target to note</div>
<input type="text" class="form-control target-note"/>
</div>
</td>
<td>
<span class="bx bx-x icon-action" data-action-conf-del></span>
</td>
</tr>`;
export default class SetRelationTargetSearchAction extends AbstractAction {
static get actionName() { return "setLabelValue"; }
doRender() {
const $action = $(TPL);
const $relationName = $action.find('.relation-name');
$relationName.val(this.actionDef.relationName || "");
const $targetNote = $action.find('.target-note');
noteAutocompleteService.initNoteAutocomplete($targetNote);
$targetNote.setNote(this.actionDef.targetNoteId);
$targetNote.on('autocomplete:closed', () => spacedUpdate.scheduleUpdate());
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
relationName: $relationName.val(),
targetNoteId: $targetNote.getSelectedNoteId()
});
}, 1000)
$relationName.on('input', () => spacedUpdate.scheduleUpdate());
$targetNote.on('input', () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -5,6 +5,12 @@ import TabAwareWidget from "./tab_aware_widget.js";
import treeCache from "../services/tree_cache.js";
import ws from "../services/ws.js";
import utils from "../services/utils.js";
import DeleteNoteSearchAction from "./search_actions/delete_note.js";
import DeleteLabelSearchAction from "./search_actions/delete_label.js";
import DeleteRelationSearchAction from "./search_actions/delete_relation.js";
import RenameLabelSearchAction from "./search_actions/rename_label.js";
import SetLabelValueSearchAction from "./search_actions/set_label_value.js";
import SetRelationTargetSearchAction from "./search_actions/set_relation_target.js";
const TPL = `
<div class="search-definition-widget">
@@ -95,14 +101,18 @@ const TPL = `
<div class="dropdown-menu">
<a class="dropdown-item" href="#" data-action-add="deleteNote">
Delete note</a>
<a class="dropdown-item" href="#" data-action-add="deleteAttribute">
Delete attribute</a>
<a class="dropdown-item" href="#" data-action-add="renameAttribute">
Rename attribute</a>
<a class="dropdown-item" href="#" data-action-add="changeLabelValue">
Change label value</a>
<a class="dropdown-item" href="#" data-action-add="changeRelationTarget">
Change relation target</a>
<a class="dropdown-item" href="#" data-action-add="deleteLabel">
Delete label</a>
<a class="dropdown-item" href="#" data-action-add="deleteRelation">
Delete relation</a>
<a class="dropdown-item" href="#" data-action-add="renameLabel">
Rename label</a>
<a class="dropdown-item" href="#" data-action-add="renameRelation">
Rename relation</a>
<a class="dropdown-item" href="#" data-action-add="setLabelValue">
Set label value</a>
<a class="dropdown-item" href="#" data-action-add="setRelationTarget">
Set relation target</a>
<a class="dropdown-item" href="#" data-action-add="executeScript">
Execute script</a>
</div>
@@ -166,27 +176,7 @@ const TPL = `
</td>
</tr>
</tbody>
<tbody class="action-options">
<tr>
<td>
Rename attribute name:
</td>
<td>
<div style="display: flex; align-items: center">
<div style="margin-right: 15px;">From:</div>
<input type="text" class="form-control" placeholder="old name"/>
<div style="margin-right: 15px; margin-left: 15px;">To:</div>
<input type="text" class="form-control" placeholder="new name"/>
</div>
</td>
<td>
<span class="bx bx-x icon-action"></span>
</td>
</tr>
</tbody>
<tbody class="action-options"></tbody>
<tbody>
<tr>
<td colspan="3">
@@ -210,35 +200,18 @@ const TPL = `
</div>
</div>`;
const ACTION_TPLS = {
deleteNote: `
<tr>
<td colspan="2">
<span class="bx bx-trash"></span>
Delete matched note
</td>
<td>
<span class="bx bx-x icon-action" data-action-conf-del></span>
</td>
</tr>`,
deleteAttribute: `
<tr>
<td>
Delete attribute:
</td>
<td>
<div style="display: flex; align-items: center">
<div style="margin-right: 15px;">Attribute name:</div>
<input type="text" class="form-control"/>
</div>
</td>
<td>
<span class="bx bx-x icon-action"></span>
</td>
</tr>`
};
const ACTION_CLASSES = {};
for (const clazz of [
DeleteNoteSearchAction,
DeleteLabelSearchAction,
DeleteRelationSearchAction,
RenameLabelSearchAction,
SetLabelValueSearchAction,
SetRelationTargetSearchAction
]) {
ACTION_CLASSES[clazz.actionName] = clazz;
}
export default class SearchDefinitionWidget extends TabAwareWidget {
static getType() { return "search"; }
@@ -403,11 +376,8 @@ export default class SearchDefinitionWidget extends TabAwareWidget {
}
const ancestorNoteId = this.note.getRelationValue('ancestor');
const ancestorNote = ancestorNoteId ? await treeCache.getNote(ancestorNoteId, true) : null;
this.$ancestor
.val(ancestorNote ? ancestorNote.title : "")
.setSelectedNotePath(ancestorNoteId);
await this.$ancestor.setNote(ancestorNoteId);
if (note.hasLabel('orderBy')) {
this.$orderBy.val(note.getLabelValue('orderBy'));
@@ -425,14 +395,20 @@ export default class SearchDefinitionWidget extends TabAwareWidget {
actionDef = JSON.parse(actionAttr.value);
}
catch (e) {
console.log(`Parsing of attribute: '${actionAttr.value}' failed with error: ${e.message}`);
logError(`Parsing of attribute: '${actionAttr.value}' failed with error: ${e.message}`);
continue;
}
const $actionConf = $(ACTION_TPLS[actionDef.name]);
$actionConf.attr('data-attribute-id', actionAttr.attributeId);
const ActionClass = ACTION_CLASSES[actionDef.name];
this.$actionOptions.append($actionConf);
if (!ActionClass) {
logError(`No action class for '${actionDef.name}' found.`);
continue;
}
const action = new ActionClass(actionAttr, actionDef);
this.$actionOptions.append(action.render());
}
this.$searchAndExecuteButton.css('visibility', actionLabels.length > 0 ? 'visible' : 'hidden');