UNPKG

@rmlio/matey

Version:

Web-based editor for YARRRML rules.

324 lines (272 loc) 11.3 kB
const urify = require("urify"); const path = require("path"); const fs = require("fs"); const handlebars = require("handlebars"); const $ = require("jquery"); const {downloadString} = require("./util/util"); /** * Class for manipulating the Matey UI in the web page */ module.exports = class Front { constructor(matey) { this.matey = matey; this.persister = matey.persister; } /** * Initialize the UI for the data editor */ init() { // read URIs for images needed in HTML body which will be inserted into template string const img22url = urify(path.join('assets/img', '22.png')); const img31url = urify(path.join('assets/img', '31.png')); // read HTML template from matey.html and insert image URIs const htmlSource = fs.readFileSync(__dirname + '/../assets/html/matey.html', 'utf8'); const htmlTmpl = handlebars.compile(htmlSource); const html = htmlTmpl({ img22url: img22url, img31url: img31url }); // insert HTML content into page $("#" + this.matey.id).html(html); // initialize Input and Delete button for data editor this.$inputButtonDiv = $('#input-button-matey'); this.$deleteButtonSpan = $('#data-source-delete-matey'); // initialize output button this.$outputButtonDiv = $('#output-button-matey'); this.$deleteButtonSpan.find('button').on('click', (e) => { e.stopPropagation(); matey.editorManager.deleteDataEditor($(e.target).data('delete-editor-id')); }); // bind buttons for generating LD and RML to corresponding functions document.getElementById("clear-btn-matey").onclick = this.matey.clearAll.bind(this.matey); document.getElementById("ld-btn-matey").onclick = this.matey.runMappingRemote.bind(this.matey); // bind button for creating new data source to corresponding function $('#data-create-matey').on('click', () => { const dataPath = prompt("Create a new data path", "source_" + this.matey.editorManager.getNumberOfDataEditors() + '.csv'); if (dataPath !== null) { // create a new, empty data editor and set it as the active one this.matey.editorManager.createAndOpenDataEditor(dataPath, ''); } }); // bind button for loading remote data source to corresponding function $('#data-load-matey').on('click', () => { const url = prompt("Enter data source URL"); const dataPath = prompt("Create a new data path", "source_" + this.matey.editorManager.getNumberOfDataEditors() + '.csv'); if (url !== null && dataPath !== null) { this.matey.loadRemoteDataSource(url, dataPath); } }); // bind button for loading remote YARRRML rules to corresponding function $('#yarrrml-load-matey').on('click', () => { const url = prompt("Enter YARRRML rules URL"); if (url !== null) { this.matey.loadRemoteYarrrml(url); } }); // bind download buttons to their corresponding functions $('#data-dl-matey').on('click', () => { const activeEditor = this.matey.editorManager.getActiveDataEditor(); downloadString(activeEditor.editor.getValue(), activeEditor.type, activeEditor.path); }); $('#yarrrml-dl-matey').on('click', () => { downloadString(this.matey.editorManager.getYARRRML(), 'text', 'yarrrml.yaml'); }); $('#turtle-dl-matey').on('click', () => { const activeEditor = this.matey.editorManager.getActiveOutputEditor(); console.log(activeEditor) downloadString(activeEditor.editor.getValue(), 'text', activeEditor.path); }); } /** * Places the editors in a certain arrangement, specified by given layout * @param {String} layout - specifies the layout in which editors should be arranged */ updateLayout(layout) { const inputDiv = $('#div-input-data-matey'); const yarrrmlDiv = $('#div-yarrrml-matey'); const outputDiv = $('#div-output-data-matey'); const btn22 = $('#layout-22-matey'); const btn31 = $('#layout-31-matey'); switch (layout) { case '2x2': //<div class="col-md-4" id="div-output-data-matey"> inputDiv.attr('class', 'col-md-6'); yarrrmlDiv.attr('class', 'col-md-6'); outputDiv.attr('class', 'col-md-6'); btn22.hide(); btn31.show(); this.persister.set('layout', '2x2'); break; default: inputDiv.attr('class', 'col-md-4'); yarrrmlDiv.attr('class', 'col-md-4'); outputDiv.attr('class', 'col-md-4'); btn22.show(); btn31.hide(); this.persister.set('layout', '3x1'); break; } } /** * Creates and initializes buttons that will load examples into input editors when pressed. All these buttons * are placed into the HTML element with the given id. * @param {String} id - identifier to div element which will contain buttons to load examples * @param {Array} examples - examples for which buttons must be made */ loadExamples(id, examples) { const $exampleButton = $('#example-btn-matey'); const $el = $('#' + id); examples.forEach((example) => { const $option = $('<option value="' + example.label + '">' + (example.icon ? '<span class="icon-' + example.icon + '"></span>&nbsp;' : '') + example.label + '</button>'); $el.append($option); }); $exampleButton.on('click', () => { const selectedLabel = $el.val(); let selectedExample; for (const example of examples) { if (example.label === selectedLabel) { selectedExample = example; break; } } this.matey.loadExample(selectedExample, true); }); } /** * Updates the delete button for data editors. If there are no data editors, the button will be hidden, else * the button is shown and will store in it the id of the data editor that should be deleted when the button is pressed. * @param id - id of the data editor corresponding to the delete button */ updateDeleteButton(id) { if (this.matey.editorManager.getNumberOfDataEditors() === 0) { this.$deleteButtonSpan.hide(); } else { this.$deleteButtonSpan.show(); this.$deleteButtonSpan.find('button').data('delete-editor-id', id); } } /** * Creates the UI components for a new Ace Editor * @param {String} path - path of the data in data editor * @param {number} index - identifier for data editor element * * @param {String} value - of the data in data editor * @returns {{ * elem: (jQuery|HTMLElement) element containing the ace editor, * input: (jQuery|HTMLElement) element containing button for data editor, * dropdownA: (jQuery|HTMLElement) element containing dropdown for data editor, * }} */ createDataEditor(path, index, value) { const $dropdownA = $(`<a class="dropdown-item rounded-0" data-toggle="tab" id="dataeditor-link-${index}" href="#dataeditor-${index}">${path}</a>`); $('#dropdown-data-chooser-matey').append($dropdownA); const $dataValue = $(`<div class="ace-editor tab-pane" data-matey-label="${path}" id="dataeditor-${index}"><pre><code>${value}</code></pre></div>`); $('#data-matey').append($dataValue); const inputButton = $(`<span>Input: ${path}</span>`); $dropdownA.on('click', e => { this.$inputButtonDiv.html(inputButton); this.updateDeleteButton(index); this.$inputButtonDiv.text($(`Input: ${path}`)); e.preventDefault(); $dropdownA.tab('show'); }); return { elem: $dataValue, input: inputButton, dropdownA: $dropdownA, } } /** * Removes the data editor UI. * @param index - index of data editor that must to be deleted */ deleteDataEditor(index) { this.$inputButtonDiv.html('Input: Data sources'); this.updateDeleteButton(null); } /** * Creates an HTML element for an alert message and displays is in the page for a certain time period * @param message - the alert message to be displayed * @param type - of alert * @param timeout - how long alert has to stay open */ doAlert(message, type = 'primary', timeout = 2000) { // read HTML template for alert element const htmlSource = fs.readFileSync(__dirname + '/../assets/html/alert.html', 'utf8'); const htmlTmpl = handlebars.compile(htmlSource); const html = htmlTmpl({ type: type, message: message }); const $alert = $(html); $('#alerts-matey').append($alert); setTimeout(() => { $alert.alert('close'); }, timeout); } /** * Resets content of data editors */ destroyDataEditors() { $('#data-matey').html(''); $('#dropdown-data-chooser-matey').html(''); } /** * Resets content of output editors */ destroyOutputEditors() { this.$outputButtonDiv.text('Output: Generated RDF'); $('#output-matey').html(''); $('#dropdown-out-chooser-matey').html(''); } /** * Set the text of the output button * @param {String} text - text to be placed in the button */ setOutputButtonDivText(text) { this.$outputButtonDiv.text(text); } /** * Create an input button for a certain datasource * @param {Object} dataPart - object that contains path of the datasource * @returns {HTMLElement} HTML element containing the button. */ createInputButton(dataPart) { return $(`<span>Input: ${dataPart.path}</span>`); } /** * Creates and initializes ui for an abstract ace editor * @param {Object} dataPart - object that contains value, type and path of data in data editor * @param {String} dataType - type of the data to show, for example: RDF output, RML Mapping, etc. * @param {number} index - identifier for data editor element * @param selectValue - determines cursor position after new value is set. `undefined` or null is selectAll, -1 is at the document start, and 1 is at the end * @param {String} prefix - prefix for he id's * @param {String} divId - id of the div to add the editor to * @param {String} dropdownId - id op the dropdown for the selector for this editor * @returns {{ * elem: (jQuery|HTMLElement) element containing the ace editor, * input: (jQuery|HTMLElement) element containing button for data editor, * dropdownA: (jQuery|HTMLElement) element containing dropdown for data editor, * }} */ createAbstractEditor(dataPart, dataType, index, selectValue = null, prefix, divId, dropdownId) { this.rmlEditorClass = ''; if (dataType == 'RML mapping') { this.rmlEditorClass = 'rml-editor'; } const $dropdownA = $(`<a class="dropdown-item rounded-0" data-toggle="tab" id="${prefix}-link-${index}" href="#${prefix}-${index}">${dataType}: ${dataPart.path}</a>`); $(`#${dropdownId}`).append($dropdownA); const $dataValue = $(`<div class="ace-editor tab-pane ${this.rmlEditorClass}" data-matey-label="${dataPart.path}" id="${prefix}-${index}"><pre><code>${dataPart.value}</code></pre></div>`); $(`#${divId}`).append($dataValue); const input = this.createInputButton(dataPart); $dropdownA.on('click', e => { this.updateDeleteButton(index); e.preventDefault(); $dropdownA.tab('show'); }); return { elem: $dataValue, input, dropdownA: $dropdownA, } } }