UNPKG

solid-ui

Version:

UI library for writing Solid read-write-web applications

1,228 lines (1,183 loc) • 81.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.appendForm = appendForm; Object.defineProperty(exports, "basicField", { enumerable: true, get: function get() { return _basic.basicField; } }); exports.buildCheckboxForm = buildCheckboxForm; exports.editFormButton = editFormButton; Object.defineProperty(exports, "field", { enumerable: true, get: function get() { return _fieldFunction.field; } }); Object.defineProperty(exports, "fieldFunction", { enumerable: true, get: function get() { return _fieldFunction.fieldFunction; } }); Object.defineProperty(exports, "fieldLabel", { enumerable: true, get: function get() { return _basic.fieldLabel; } }); Object.defineProperty(exports, "fieldParams", { enumerable: true, get: function get() { return _fieldParams.fieldParams; } }); Object.defineProperty(exports, "fieldStore", { enumerable: true, get: function get() { return _basic.fieldStore; } }); exports.findClosest = findClosest; exports.formsFor = formsFor; exports.makeDescription = makeDescription; exports.makeSelectForCategory = makeSelectForCategory; exports.makeSelectForChoice = makeSelectForChoice; exports.makeSelectForClassifierOptions = makeSelectForClassifierOptions; exports.makeSelectForNestedCategory = makeSelectForNestedCategory; exports.makeSelectForOptions = makeSelectForOptions; Object.defineProperty(exports, "mostSpecificClassURI", { enumerable: true, get: function get() { return _fieldFunction.mostSpecificClassURI; } }); exports.newButton = newButton; exports.newThing = newThing; exports.promptForNew = promptForNew; exports.propertiesForClass = propertiesForClass; Object.defineProperty(exports, "renderNameValuePair", { enumerable: true, get: function get() { return _basic.renderNameValuePair; } }); exports.sortByLabel = sortByLabel; exports.sortBySequence = sortBySequence; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var buttons = _interopRequireWildcard(require("./buttons")); var _fieldParams = require("./forms/fieldParams"); var _fieldFunction = require("./forms/fieldFunction"); var _formStyle = require("./forms/formStyle"); var debug = _interopRequireWildcard(require("../debug")); var _error = require("./error"); var _basic = require("./forms/basic"); var _autocompleteField = require("./forms/autocomplete/autocompleteField"); var style = _interopRequireWildcard(require("../style")); var _styleConstants = _interopRequireDefault(require("../styleConstants")); var _iconBase = require("../iconBase"); var log = _interopRequireWildcard(require("../log")); var ns = _interopRequireWildcard(require("../ns")); var $rdf = _interopRequireWildcard(require("rdflib")); var _solidLogic = require("solid-logic"); var utils = _interopRequireWildcard(require("../utils")); var _multiSelect = require("./multiSelect"); var widgets = _interopRequireWildcard(require("../widgets")); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t3 in e) "default" !== _t3 && {}.hasOwnProperty.call(e, _t3) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t3)) && (i.get || i.set) ? o(f, _t3, i) : f[_t3] = e[_t3]); return f; })(e, t); } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } /* F O R M S * * A Vanilla Dom implementation of the form language */ /* global alert */ // Note default export var checkMarkCharacter = "\u2713"; var cancelCharacter = "\u2715"; var dashCharacter = '-'; var kb = _solidLogic.store; _fieldFunction.field[ns.ui('AutocompleteField').uri] = _autocompleteField.autocompleteField; // /////////////////////////////////////////////////////////////////////// /* Form Field implementations ** */ /** Group of different fields ** ** One type of form field is an ordered Group of other fields. ** A Form is actually just the same as a group. ** ** @param {Document} dom The HTML Document object aka Document Object Model ** @param {Element?} container If present, the created widget will be appended to this ** @param {Map} already A hash table of (form, subject) kept to prevent recursive forms looping ** @param {Node} subject The thing about which the form displays/edits data ** @param {Node} form The form or field to be rendered ** @param {Node} dataDoc The web document in which the data is ** @param {function(ok, errorMessage)} callbackFunction Called when data is changed? ** ** @returns {Element} The HTML widget created */ function refreshOpionsSubfieldinGroup(dom, already, subject, dataDoc, callbackFunction, groupDiv, subfields) { var eles = groupDiv.children; for (var j = 0; j < subfields.length; j++) { // This is really messy. var _field = subfields[j]; var t = (0, _fieldFunction.mostSpecificClassURI)(_field); // Field type if (t === ns.ui('Options').uri) { var optionsRender = (0, _fieldFunction.fieldFunction)(dom, _field); var newOne = optionsRender(dom, null, already, subject, _field, dataDoc, callbackFunction); debug.log('Refreshing Options field by replacing it.'); // better to support actual refresh groupDiv.insertBefore(newOne, eles[j]); groupDiv.removeChild(eles[j + 1]); // Remove the old one } } } _fieldFunction.field[ns.ui('Form').uri] = _fieldFunction.field[ns.ui('Group').uri] = function (dom, container, already, subject, form, dataDoc, callbackFunction) { var box = dom.createElement('div'); var ui = ns.ui; if (container) container.appendChild(box); // Prevent loops if (!form) return; var key = subject.toNT() + '|' + form.toNT(); if (already[key]) { // been there done that box.appendChild(dom.createTextNode('Group: see above ' + key)); // TODO fix dependency cycle to solid-panes by calling outlineManager // const plist = [$rdf.st(subject, ns.owl('sameAs'), subject)] // @@ need prev subject // dom.outlineManager.appendPropertyTRs(box, plist) // dom.appendChild(plist) return box; } var already2 = {}; for (var x in already) already2[x] = 1; already2[key] = 1; var formDoc = form.doc ? form.doc() : null; // @@ if blank no way to know var weight0 = kb.any(form, ui('weight'), null, formDoc); // Say 0-3 var weight = weight0 ? Number(weight0.value) : 1; if (weight > 3 || weight < 0) return box.appendChild((0, _error.errorMessageBlock)(dom, "Form Group weight ".concat(weight, " should be 0-3"))); box.setAttribute('style', style.formGroupStyle[weight]); // Indent a group box.style.display = 'flex'; box.style.flexDirection = 'column'; box["class"] = 'form-weight-' + weight; var parts = kb.any(form, ui('parts'), null, formDoc); var subfields; if (parts) { subfields = parts.elements; } else { parts = kb.each(form, ui('part'), null, formDoc); // Warning: unordered subfields = sortBySequence(parts); } if (!parts) { return box.appendChild((0, _error.errorMessageBlock)(dom, 'No parts to form! ')); } for (var i = 0; i < subfields.length; i++) { var _field2 = subfields[i]; var subFieldFunction = (0, _fieldFunction.fieldFunction)(dom, _field2); // var itemChanged = function itemChanged(ok, body) { if (ok && body && body.widget && body.widget === 'select') { refreshOpionsSubfieldinGroup(dom, already, subject, dataDoc, callbackFunction, box, subfields); } callbackFunction(ok, { widget: 'group', change: body }); }; box.appendChild(subFieldFunction(dom, null, already2, subject, _field2, dataDoc, itemChanged)); } return box; }; /** Options field: Select one or more cases ** ** @param {Document} dom The HTML Document object aka Document Object Model ** @param {Element?} container If present, the created widget will be appended to this ** @param {Map} already A hash table of (form, subject) kept to prevent recursive forms looping ** @param {Node} subject The thing about which the form displays/edits data ** @param {Node} form The form or field to be rendered ** @param {Node} dataDoc The web document in which the data is ** @param {function(ok, errorMessage)} callbackFunction Called when data is changed? ** ** @returns {Element} The HTML widget created */ _fieldFunction.field[ns.ui('Options').uri] = function (dom, container, already, subject, form, dataDoc, callbackFunction) { var kb = _solidLogic.store; var box = dom.createElement('div'); var formDoc = form.doc ? form.doc() : null; // @@ if blank no way to know var ui = ns.ui; if (container) container.appendChild(box); var dependingOn = kb.any(form, ui('dependingOn')); if (!dependingOn) { dependingOn = ns.rdf('type'); } // @@ default to type (do we want defaults?) var cases = kb.each(form, ui('case'), null, formDoc); if (!cases) { box.appendChild((0, _error.errorMessageBlock)(dom, 'No cases to Options form. ')); } var values; if (dependingOn.sameTerm(ns.rdf('type'))) { values = Object.keys(kb.findTypeURIs(subject)).map(function (uri) { return $rdf.sym(uri); }); // Use RDF-S inference } else { values = kb.each(subject, dependingOn); } for (var i = 0; i < cases.length; i++) { var c = cases[i]; var tests = kb.each(c, ui('for'), null, formDoc); // There can be multiple 'for' var match = false; for (var j = 0; j < tests.length; j++) { var _iterator = _createForOfIteratorHelper(values), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var value = _step.value; var test = tests[j]; if (value.sameTerm(tests) || value.termType === test.termType && value.value === test.value) { match = true; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } if (match) { var _field3 = kb.the(c, ui('use')); if (!_field3) { box.appendChild((0, _error.errorMessageBlock)(dom, 'No "use" part for case in form ' + form)); return box; } else { appendForm(dom, box, already, subject, _field3, dataDoc, callbackFunction); } break; } } // @@ Add box.refresh() to sync fields with values return box; }; /** Multiple field: zero or more similar subFields ** ** @param {Document} dom The HTML Document object aka Document Object Model ** @param {Element?} container If present, the created widget will be appended to this ** @param {Map} already A hash table of (form, subject) kept to prevent recursive forms looping ** @param {Node} subject The thing about which the form displays/edits data ** @param {Node} form The form or field to be rendered ** @param {Node} dataDoc The web document in which the data is ** @param {function(ok, errorMessage)} callbackFunction Called when data is changed? ** ** @returns {Element} The HTML widget created ** ** Form properties: ** @param {Boolean} reverse Make e reverse arc in the data OPS not SPO ** @param {NamedNode} property The property to be written in the data ** @param {Boolean} ordered Is the list an ordered one where the user defined the order */ _fieldFunction.field[ns.ui('Multiple').uri] = function (dom, container, already, subject, form, dataDoc, callbackFunction) { /** Diagnostic function */ function debugString(values) { return values.map(function (x) { return x.toString().slice(-7); }).join(', '); } /** Add an item to the local quadstore not the UI or the web * * @param {Node} object The RDF object to be represented by this item. */ function addItem() { return _addItem.apply(this, arguments); } /** Make a dom representation for an item * @param {Event} anyEvent if used as an event handler * @param {Node} object The RDF object to be represented by this item. */ function _addItem() { _addItem = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee6() { var object, toBeInserted, msg, _t; return _regenerator["default"].wrap(function (_context6) { while (1) switch (_context6.prev = _context6.next) { case 0: object = newThing(dataDoc); // by default just add new nodes if (!ordered) { _context6.next = 2; break; } createListIfNecessary(); // Sets list and unsavedList list.elements.push(object); _context6.next = 1; return saveListThenRefresh(); case 1: _context6.next = 7; break; case 2: toBeInserted = reverse ? [$rdf.st(object, property, subject, dataDoc)] : [$rdf.st(subject, property, object, dataDoc)]; _context6.prev = 3; _context6.next = 4; return kb.updater.update([], toBeInserted); case 4: _context6.next = 6; break; case 5: _context6.prev = 5; _t = _context6["catch"](3); msg = 'Error adding to unordered multiple: ' + _t; box.appendChild((0, _error.errorMessageBlock)(dom, msg)); debug.error(msg); case 6: refresh(); case 7: case "end": return _context6.stop(); } }, _callee6, null, [[3, 5]]); })); return _addItem.apply(this, arguments); } function renderItem(object) { function deleteThisItem() { return _deleteThisItem.apply(this, arguments); } /** Move the object up or down in the ordered list * @param {Event} anyEvent if used as an event handler * @param {Boolean} upwards Move this up (true) or down (false). */ function _deleteThisItem() { _deleteThisItem = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee3() { var i, del; return _regenerator["default"].wrap(function (_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: if (!ordered) { _context3.next = 5; break; } debug.log('pre delete: ' + debugString(list.elements)); i = 0; case 1: if (!(i < list.elements.length)) { _context3.next = 4; break; } if (!list.elements[i].sameTerm(object)) { _context3.next = 3; break; } list.elements.splice(i, 1); _context3.next = 2; return saveListThenRefresh(); case 2: return _context3.abrupt("return"); case 3: i++; _context3.next = 1; break; case 4: _context3.next = 6; break; case 5: // unordered if (kb.holds(subject, property, object, dataDoc)) { del = [$rdf.st(subject, property, object, dataDoc)]; kb.updater.update(del, [], function (uri, ok, message) { if (ok) { body.removeChild(subField); } else { body.appendChild((0, _error.errorMessageBlock)(dom, 'Multiple: delete failed: ' + message)); } }); } case 6: case "end": return _context3.stop(); } }, _callee3); })); return _deleteThisItem.apply(this, arguments); } function moveThisItem(_x, _x2) { return _moveThisItem.apply(this, arguments); } /* A subField has been filled in * * One possibility is to not actually make the link to the thing until * this callback happens to avoid widow links */ function _moveThisItem() { _moveThisItem = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee4(event, upwards) { var i; return _regenerator["default"].wrap(function (_context4) { while (1) switch (_context4.prev = _context4.next) { case 0: // @@ possibly, allow shift+click to do move to top or bottom? debug.log('pre move: ' + debugString(list.elements)); i = 0; case 1: if (!(i < list.elements.length)) { _context4.next = 3; break; } if (!list.elements[i].sameTerm(object)) { _context4.next = 2; break; } return _context4.abrupt("continue", 3); case 2: i++; _context4.next = 1; break; case 3: if (i === list.elements.length) { alert('list move: not found element for ' + object); } if (!upwards) { _context4.next = 5; break; } if (!(i === 0)) { _context4.next = 4; break; } alert('@@ boop - already at top -temp message'); // @@ make boop sound return _context4.abrupt("return"); case 4: list.elements.splice(i - 1, 2, list.elements[i], list.elements[i - 1]); _context4.next = 7; break; case 5: if (!(i === list.elements.length - 1)) { _context4.next = 6; break; } alert('@@ boop - already at bottom -temp message'); // @@ make boop sound return _context4.abrupt("return"); case 6: list.elements.splice(i, 2, list.elements[i + 1], list.elements[i]); case 7: _context4.next = 8; return saveListThenRefresh(); case 8: case "end": return _context4.stop(); } }, _callee4); })); return _moveThisItem.apply(this, arguments); } function itemDone(ok, message) { debug.log("Item done callback for item ".concat(object.toString())); if (!ok) { // when does this happen? errors typically deal with upstream debug.error(' Item done callback: Error: ' + message); } callbackFunction(ok, message); } log.debug('Multiple: render object: ' + object); var fn = (0, _fieldFunction.fieldFunction)(dom, element); var subField = fn(dom, null, already, object, element, dataDoc, itemDone); // subfields was: body. moving to not passing that subField.subject = object; // Keep a back pointer between the DOM array and the RDF objects // delete button and move buttons if (kb.updater.editable(dataDoc.uri)) { buttons.deleteButtonWithCheck(dom, subField, multipleUIlabel, deleteThisItem); if (ordered) { // Add controsl in a frame var frame = dom.createElement('div'); frame.style.display = 'grid'; frame.style.gridTemplateColumns = 'auto 3em'; frame.style.gridTemplateRows = '50% 50%'; var moveUpButton = buttons.button(dom, _iconBase.icons.iconBase + 'noun_1369237.svg', 'Move Up', /*#__PURE__*/function () { var _ref = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(event) { return _regenerator["default"].wrap(function (_context) { while (1) switch (_context.prev = _context.next) { case 0: return _context.abrupt("return", moveThisItem(event, true)); case 1: case "end": return _context.stop(); } }, _callee); })); return function (_x3) { return _ref.apply(this, arguments); }; }()); var moveDownButton = buttons.button(dom, _iconBase.icons.iconBase + 'noun_1369241.svg', 'Move Down', /*#__PURE__*/function () { var _ref2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2(event) { return _regenerator["default"].wrap(function (_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: return _context2.abrupt("return", moveThisItem(event, false)); case 1: case "end": return _context2.stop(); } }, _callee2); })); return function (_x4) { return _ref2.apply(this, arguments); }; }()); var _shim = dom.createElement('div'); _shim.appendChild(subField); // Subfield has its own layout frame.appendChild(_shim); frame.appendChild(moveUpButton); frame.appendChild(moveDownButton); moveUpButton.style.gridColumn = 2; moveDownButton.style.gridColumn = 2; moveUpButton.style.gridRow = 1; moveDownButton.style.padding = '0em'; // don't take too much space moveUpButton.style.padding = '0em'; moveDownButton.style.gridRow = 2; _shim.style.gridColumn = 1; _shim.style.gridRowStart = 'span 2'; // Cover both rows // shim.style.gridRowEnd = 2 // Cover both rows return frame; } } return subField; // unused } // renderItem /// ///////// Body of Multiple form field implementation var plusIconURI = _iconBase.icons.iconBase + 'noun_19460_green.svg'; // white plus in green circle var kb = _solidLogic.store; var formDoc = form.doc ? form.doc() : null; // @@ if blank no way to know var box = dom.createElement('div'); var shim = box; // no shim // We don't indent multiple as it is a sort of a prefix of the next field and has contents of one. // box.setAttribute('style', 'padding-left: 2em; border: 0.05em solid green;') // Indent a multiple var ui = ns.ui; if (container) container.appendChild(box); var orderedNode = kb.any(form, ui('ordered')); var ordered = orderedNode ? $rdf.Node.toJS(orderedNode) : false; var property = kb.any(form, ui('property')); var reverse = kb.anyJS(form, ui('reverse'), null, formDoc); if (!property) { box.appendChild((0, _error.errorMessageBlock)(dom, 'No property to multiple: ' + form)); // used for arcs in the data return shim; } var multipleUIlabel = kb.any(form, ui('label')); if (!multipleUIlabel) multipleUIlabel = utils.label(property); var min = kb.any(form, ui('min')); // This is the minimum number -- default 0 min = min ? 0 + min.value : 0; var element = kb.any(form, ui('part')); // This is the form to use for each one if (!element) { box.appendChild((0, _error.errorMessageBlock)(dom, 'No part to multiple: ' + form)); return shim; } var body = box.appendChild(dom.createElement('div')); body.style.display = 'flex'; body.style.flexDirection = 'column'; var list; // The RDF collection which keeps the ordered version or null var values; // Initial values - always an array. Even when no list yet. values = reverse ? kb.any(null, property, subject, dataDoc) : kb.any(subject, property, null, dataDoc); if (ordered) { list = reverse ? kb.any(null, property, subject, dataDoc) : kb.any(subject, property, null, dataDoc); if (list) { values = list.elements; } else { values = []; } } else { values = reverse ? kb.each(null, property, subject, dataDoc) : kb.each(subject, property, null, dataDoc); list = null; } // Add control on the bottom for adding more items if (kb.updater.editable(dataDoc.uri)) { var tail = box.appendChild(dom.createElement('div')); tail.style.padding = '0.5em'; var img = tail.appendChild(dom.createElement('img')); img.setAttribute('src', plusIconURI); // plus sign img.setAttribute('style', 'margin: 0.2em; width: 1.5em; height:1.5em'); img.title = 'Click to add another ' + multipleUIlabel; var prompt = dom.createElement('span'); prompt.textContent = (values.length === 0 ? 'Add another ' : 'Add ') + multipleUIlabel; tail.addEventListener('click', /*#__PURE__*/function () { var _ref3 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee5(_eventNotUsed) { return _regenerator["default"].wrap(function (_context5) { while (1) switch (_context5.prev = _context5.next) { case 0: _context5.next = 1; return addItem(); case 1: case "end": return _context5.stop(); } }, _callee5); })); return function (_x5) { return _ref3.apply(this, arguments); }; }(), true); tail.appendChild(prompt); } function createListIfNecessary() { if (!list) { list = new $rdf.Collection(); if (reverse) { kb.add(list, property, subject, dataDoc); } else { kb.add(subject, property, list, dataDoc); } } } function saveListThenRefresh() { return _saveListThenRefresh.apply(this, arguments); } function _saveListThenRefresh() { _saveListThenRefresh = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee7() { var _t2; return _regenerator["default"].wrap(function (_context7) { while (1) switch (_context7.prev = _context7.next) { case 0: debug.log('save list: ' + debugString(list.elements)); // 20191214 createListIfNecessary(); _context7.prev = 1; _context7.next = 2; return kb.fetcher.putBack(dataDoc); case 2: _context7.next = 4; break; case 3: _context7.prev = 3; _t2 = _context7["catch"](1); box.appendChild((0, _error.errorMessageBlock)(dom, 'Error trying to put back a list: ' + _t2)); return _context7.abrupt("return"); case 4: refresh(); case 5: case "end": return _context7.stop(); } }, _callee7, null, [[1, 3]]); })); return _saveListThenRefresh.apply(this, arguments); } function refresh() { var vals; if (ordered) { var li = reverse ? kb.the(null, property, subject, dataDoc) : kb.the(subject, property, null, dataDoc); vals = li ? li.elements : []; } else { vals = reverse ? kb.each(null, property, subject, dataDoc) : kb.each(subject, property, null, dataDoc); vals.sort(); // achieve consistency on each refresh } utils.syncTableToArrayReOrdered(body, vals, renderItem); } body.refresh = refresh; // Allow live update refresh(); function asyncStuff() { return _asyncStuff.apply(this, arguments); } function _asyncStuff() { _asyncStuff = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee8() { var extra, j; return _regenerator["default"].wrap(function (_context8) { while (1) switch (_context8.prev = _context8.next) { case 0: extra = min - values.length; if (!(extra > 0)) { _context8.next = 4; break; } j = 0; case 1: if (!(j < extra)) { _context8.next = 3; break; } debug.log('Adding extra: min ' + min); _context8.next = 2; return addItem(); case 2: j++; _context8.next = 1; break; case 3: _context8.next = 4; return saveListThenRefresh(); case 4: case "end": return _context8.stop(); } }, _callee8); })); return _asyncStuff.apply(this, arguments); } asyncStuff().then(function () { debug.log(' Multiple render: async stuff ok'); }, function (err) { debug.error(' Multiple render: async stuff fails. #### ', err); }); // async return shim; }; // Multiple /* Text field ** */ // For possible date popups see e.g. http://www.dynamicdrive.com/dynamicindex7/jasoncalendar.htm // or use HTML5: http://www.w3.org/TR/2011/WD-html-markup-20110113/input.date.html // _fieldFunction.field[ns.ui('PhoneField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('EmailField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('ColorField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('DateField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('DateTimeField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('TimeField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('NumericField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('IntegerField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('DecimalField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('FloatField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('TextField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('SingleLineTextField').uri] = _basic.basicField; _fieldFunction.field[ns.ui('NamedNodeURIField').uri] = _basic.basicField; /* Multiline Text field ** */ _fieldFunction.field[ns.ui('MultiLineTextField').uri] = function (dom, container, already, subject, form, dataDoc, callbackFunction) { var ui = ns.ui; var kb = _solidLogic.store; var formDoc = form.doc ? form.doc() : null; // @@ if blank no way to know var property = kb.any(form, ui('property')); if (!property) { return (0, _error.errorMessageBlock)(dom, 'No property to text field: ' + form); } var box = dom.createElement('div'); box.style.display = 'flex'; box.style.flexDirection = 'row'; var left = box.appendChild(dom.createElement('div')); left.style.width = _styleConstants["default"].formFieldNameBoxWidth; var right = box.appendChild(dom.createElement('div')); left.appendChild((0, _basic.fieldLabel)(dom, property, form)); dataDoc = (0, _basic.fieldStore)(subject, property, dataDoc); var text = kb.anyJS(subject, property, null, dataDoc) || ''; var editable = kb.updater.editable(dataDoc.uri); var suppressEmptyUneditable = form && kb.anyJS(form, ns.ui('suppressEmptyUneditable'), null, formDoc); if (!editable && suppressEmptyUneditable && text === '') { box.style.display = 'none'; } var field = makeDescription(dom, kb, subject, property, dataDoc, callbackFunction); right.appendChild(field); if (container) container.appendChild(box); return box; }; /* Boolean field and Tri-state version (true/false/null) ** ** @@ todo: remove tristate param */ function booleanField(dom, container, already, subject, form, dataDoc, callbackFunction, tristate) { var ui = ns.ui; var kb = _solidLogic.store; var property = kb.any(form, ui('property')); if (!property) { var errorBlock = (0, _error.errorMessageBlock)(dom, 'No property to boolean field: ' + form); if (container) container.appendChild(errorBlock); return errorBlock; } var lab = kb.any(form, ui('label')); if (!lab) lab = utils.label(property, true); // Init capital dataDoc = (0, _basic.fieldStore)(subject, property, dataDoc); var state = kb.any(subject, property); if (state === undefined) { state = false; } // @@ sure we want that -- or three-state? var ins = $rdf.st(subject, property, true, dataDoc); var del = $rdf.st(subject, property, false, dataDoc); var box = buildCheckboxForm(dom, kb, lab, del, ins, form, dataDoc, tristate); if (container) container.appendChild(box); return box; } _fieldFunction.field[ns.ui('BooleanField').uri] = function (dom, container, already, subject, form, dataDoc, callbackFunction) { return booleanField(dom, container, already, subject, form, dataDoc, callbackFunction, false); }; _fieldFunction.field[ns.ui('TristateField').uri] = function (dom, container, already, subject, form, dataDoc, callbackFunction) { return booleanField(dom, container, already, subject, form, dataDoc, callbackFunction, true); }; /* Classifier field ** ** Nested categories ** ** @@ To do: If a classification changes, then change any dependent Options fields. */ _fieldFunction.field[ns.ui('Classifier').uri] = function (dom, container, already, subject, form, dataDoc, callbackFunction) { var kb = _solidLogic.store; var ui = ns.ui; var category = kb.any(form, ui('category')); if (!category) { return (0, _error.errorMessageBlock)(dom, 'No category for classifier: ' + form); } log.debug('Classifier: dataDoc=' + dataDoc); var checkOptions = function checkOptions(ok, body) { if (!ok) return callbackFunction(ok, body); return callbackFunction(ok, body); }; var box = makeSelectForNestedCategory(dom, kb, subject, category, dataDoc, checkOptions); if (container) container.appendChild(box); return box; }; /** Choice field ** ** Not nested. Generates a link to something from a given class. ** Optional subform for the thing selected. ** Generates a subForm based on a ui:use form ** Will look like: ** <div id=dropDownDiv> ** <div id=labelOfDropDown> ** <div id=selectDiv> ** <select id=dropDownSelect> ** <option> .... ** <subForm> ** Alternative implementatons could be: ** -- pop-up menu (as here) ** -- radio buttons ** -- auto-complete typing ** ** TODO: according to ontology ui:choice can also have ns.ui('default') - this is not implemented yet */ _fieldFunction.field[ns.ui('Choice').uri] = function (dom, container, already, subject, form, dataDoc, callbackFunction) { var ui = ns.ui; var kb = _solidLogic.store; var formDoc = form.doc ? form.doc() : null; // @@ if blank no way to know var p; var box = dom.createElement('div'); box.setAttribute('class', 'choiceBox'); // Set flexDirection column? if (container) container.appendChild(box); var lhs = dom.createElement('div'); lhs.setAttribute('class', 'formFieldName choiceBox-label'); box.appendChild(lhs); var rhs = dom.createElement('div'); rhs.setAttribute('class', 'formFieldValue choiceBox-selectBox'); box.appendChild(rhs); var property = kb.any(form, ui('property')); if (!property) { return box.appendChild((0, _error.errorMessageBlock)(dom, 'No property for Choice: ' + form)); } lhs.appendChild((0, _basic.fieldLabel)(dom, property, form)); var uiFrom = kb.any(form, ui('from')); if (!uiFrom) { return (0, _error.errorMessageBlock)(dom, "No 'from' for Choice: " + form); } var subForm = kb.any(form, ui('use')); // Optional // const follow = kb.anyJS(form, ui('follow'), null, formDoc) // data doc moves to new subject? var opts = { form: form, subForm: subForm, disambiguate: false }; function getSelectorOptions(dataSource) { var possible = []; var possibleProperties; possible = kb.each(undefined, ns.rdf('type'), uiFrom, formDoc); for (var x in findMembersNT(kb, uiFrom, dataSource)) { possible.push(kb.fromNT(x)); } // Use rdfs if (uiFrom.sameTerm(ns.rdfs('Class'))) { for (p in buttons.allClassURIs()) possible.push(kb.sym(p)); // log.debug("%%% Choice field: possible.length 2 = "+possible.length) } else if (uiFrom.sameTerm(ns.rdf('Property'))) { possibleProperties = buttons.propertyTriage(kb); for (p in possibleProperties.op) possible.push(kb.fromNT(p)); for (p in possibleProperties.dp) possible.push(kb.fromNT(p)); opts.disambiguate = true; // This is a big class, and the labels won't be enough. } else if (uiFrom.sameTerm(ns.owl('ObjectProperty'))) { possibleProperties = buttons.propertyTriage(kb); for (p in possibleProperties.op) possible.push(kb.fromNT(p)); opts.disambiguate = true; } else if (uiFrom.sameTerm(ns.owl('DatatypeProperty'))) { possibleProperties = buttons.propertyTriage(kb); for (p in possibleProperties.dp) possible.push(kb.fromNT(p)); opts.disambiguate = true; } return possible; // return sortByLabel(possible) } // TODO: this checks for any occurrence, regardless of true or false setting if (kb.any(form, ui('canMintNew'))) { opts.mint = '* Create new *'; // @@ could be better } var multiSelect = kb.any(form, ui('multiselect')); // Optional if (multiSelect) opts.multiSelect = true; // by default searches the dataDoc graph or optionally the full store var dataSource = kb.each(form, ui('search-full-store')).length ? null : dataDoc; // optional var selector; rhs.refresh = function () { // from ui:property var selectedOptions = kb.each(subject, property, null, dataDoc).map(function (object) { return object.value; }); // from ui:from + ui:property var possibleOptions = getSelectorOptions(dataSource); possibleOptions.push(selectedOptions); possibleOptions = sortByLabel(possibleOptions); selector = makeSelectForChoice(dom, rhs, kb, subject, property, possibleOptions, selectedOptions, uiFrom, opts, dataDoc, callbackFunction); rhs.innerHTML = ''; rhs.appendChild(selector); if (multiSelect) { var multiSelectDiv = new _multiSelect.IconicMultiSelect({ placeholder: selector.selected, select: selector, container: rhs, textField: 'textField', valueField: 'valueField' }); multiSelectDiv.init(); multiSelectDiv.subscribe(function (event) { if (event.action === 'REMOVE_OPTION') { selectedOptions = selectedOptions.filter(function (value) { return value !== event.value; }); } if (event.action === 'CLEAR_ALL_OPTIONS') { selectedOptions = []; } if (event.action === 'ADD_OPTION') { var stringValue = event.value + ''; if (stringValue.includes('Create new')) { var newObject = newThing(dataDoc); var is = []; is.push($rdf.st(subject, property, kb.sym(newObject), dataDoc)); if (uiFrom) is.push($rdf.st(newObject, ns.rdf('type'), kb.sym(uiFrom), dataDoc)); if (subForm) { addSubFormChoice(dom, rhs, {}, $rdf.sym(newObject), subForm, dataDoc, function (ok, body) { if (ok) { kb.updater.update([], is, function (uri, success, errorBody) { if (!success) rhs.appendChild((0, _error.errorMessageBlock)(dom, 'Error updating select: ' + errorBody)); }); selectedOptions.push(newObject); if (callbackFunction) callbackFunction(ok, { widget: 'select', event: 'new' }); } else { rhs.appendChild((0, _error.errorMessageBlock)(dom, 'Error updating data in field of select: ' + body)); } }); } } else selectedOptions.push(event.value); } selector.update(selectedOptions); }); } }; rhs.refresh(); if (selector && selector.refresh) selector.refresh(); return box; }; function addSubFormChoice(dom, selectDiv, already, subject, subForm, dataDoc, callbackFunction) { (0, _fieldFunction.fieldFunction)(dom, subForm)(dom, selectDiv, already, subject, subForm, dataDoc, callbackFunction); } // Documentation - non-interactive fields // _fieldFunction.field[ns.ui('Comment').uri] = _fieldFunction.field[ns.ui('Heading').uri] = function (dom, container, already, subject, form, dataDoc, _callbackFunction) { var ui = ns.ui; var kb = _solidLogic.store; var contents = kb.any(form, ui('contents')); if (!contents) contents = 'Error: No contents in comment field.'; var formDoc = form.doc ? form.doc() : null; // @@ if blank no way to know var uri = (0, _fieldFunction.mostSpecificClassURI)(form); var params = _fieldParams.fieldParams[uri] || {}; var box = dom.createElement('div'); if (container) container.appendChild(box); var p = box.appendChild(dom.createElement(params.element)); p.textContent = contents; (0, _formStyle.setFieldStyle)(p, form); // Some headings and prompts are only useful to guide user input var suppressIfUneditable = kb.anyJS(form, ns.ui('suppressIfUneditable'), null, formDoc); var editable = kb.updater.editable(dataDoc.uri); if (suppressIfUneditable && !editable) { box.style.display = 'none'; } return box; }; // A button for editing a form (in place, at the moment) // // When editing forms, make it yellow, when editing thr form form, pink // Help people understand how many levels down they are. // function editFormButton(dom, container, form, dataDoc, callbackFunction) { var b = dom.createElement('button'); b.setAttribute('type', 'button'); b.innerHTML = 'Edit ' + utils.label(ns.ui('Form')); b.addEventListener('click', function (_e) { var ff = appendForm(dom, container, {}, form, ns.ui('FormForm'), dataDoc, callbackFunction); ff.setAttribute('style', ns.ui('FormForm').sameTerm(form) ? 'background-color: #fee;' : 'background-color: #ffffe7;'); b.parentNode.removeChild(b); }, true); return b; } function appendForm(dom, container, already, subject, form, dataDoc, itemDone) { return (0, _fieldFunction.fieldFunction)(dom, form)(dom, container, already, subject, form, dataDoc, itemDone); } /** Find list of properties for class // // Three possible sources: Those mentioned in schemas, which exludes many // those which occur in the data we already have, and those predicates we // have come across anywhere and which are not explicitly excluded from // being used with this class. */ function propertiesForClass(kb, c) { var explicit = kb.each(undefined, ns.rdf('range'), c); [ns.rdfs('comment'), ns.dc('title'), // Generic things ns.foaf('name'), ns.foaf('homepage')].forEach(function (x) { explicit.push(x); }); var members = kb.each(undefined, ns.rdf('type'), c); if (members.length > 60) members = members.slice(0, 60); // Array supports slice? var used = {}; for (var i = 0; i < (members.length > 60 ? 60 : members.length); i++) { kb.statementsMatching(members[i], undefined, undefined).forEach(function (st) { used[st.predicate.uri] = true; }); } explicit.forEach(function (p) { used[p.uri] = true; }); var result = []; for (var uri in used) { result.push(kb.sym(uri)); } return result; } /** Find the closest class * @param kb The quad store * @param cla - the URI of the class * @param prop */ function findClosest(kb, cla, prop) { var agenda = [kb.sym(cla)]; // ordered - this is breadth first search while (agenda.length > 0) { var c = agenda.shift(); // first var lists = kb.each(c, prop); log.debug('Lists for ' + c + ', ' + prop + ': ' + lists.length); if (lists.length !== 0) return lists; var supers = kb.each(c, ns.rdfs('subClassOf')); for (var i = 0; i < supers.length; i++) { agenda.push(supers[i]); log.debug('findClosest: add super: ' + supers[i]); } } return []; } // Which forms apply to a given existing subject? function formsFor(subject) { var kb = _solidLogic.store; log.debug('formsFor: subject=' + subject); var t = kb.findTypeURIs(subject); var t1; for (t1 in t) { log.debug(' type: ' + t1); } var bottom = kb.bottomTypeURIs(t); // most specific var candidates = []; for (var b in bottom) { // Find the most specific log.debug('candidatesFor: trying bottom type =' + b); candidates = candidates.concat(findClosest(kb, b, ns.ui('creationForm'))); candidates = candidates.concat(findClosest(kb, b, ns.ui('annotationForm'))); } return candidates; } function sortBySequence(list) { var subfields = list.map(function (p) { var k = kb.any(p, ns.ui('sequence')); return [k || 9999, p]; }); subfields.sort(function (a, b) { return a[0] - b[0]; }); return subfields.map(function (pair) { return pair[1]; }); } function sortByLabel(list) { var subfields = list.map(function (p) { return [utils.label(p).toLowerCase(), p]; }); subfields.sort(); return subfields.map(function (pair) { return pair[1]; }); } /** Button to add a new whatever using a form // // @param form - optional form , else will look for one // @param dataDoc - optional dataDoc else will prompt for one (unimplemented) */ function newButton(dom, kb, subject, predicate, theClass, form, dataDoc, callbackFunction) { var b = dom.createElement('button'); b.setAttribute('type', 'button'); b.innerHTML = 'New ' + utils.label(theClass); b.addEventListener('click', function (_e) { b.parentNode.appendChild(promptForNew(dom, kb, subject, predicate, theClass, form, dataDoc, callbackFunction)); }, false); return b; } /** Prompt for new object of a given class // // @param dom - the document DOM for the user interface // @param kb - the graph which is the knowledge base we are working with // @param subject - a term, Thing this should be linked to when made. Optional. // @param predicate - a term, the relationship for the subject link. Optional. // @param theClass - an RDFS class containng the object about which the new information is. // @param form - the form to be used when a new one. null means please find one. // @param dataDoc - The web document being edited // @param callbackFunction - takes (boolean ok, string errorBody) // @returns a dom object with the form DOM */ function promptForNew(dom, kb, subject, predicate, theClass, form, dataDoc, callbackFunction) { var box = dom.createElement('form'); if (!form) { var lists = findClosest(kb, theClass.uri, ns.ui('creationForm')); if (lists.length === 0) { var p = box.appendChild(dom.createElement('p')); p.textContent = 'I am sorry, you need to provide information about a ' + utils.label(theClass) + " but I don't know enough information about those to ask you."; var b = box.appendChild(dom.createElement('button')); b.setAttribute('type', 'button'); b.setAttribute('style', 'float: right;'); b.innerHTML = 'Goto ' + utils.label(theClass); b.addEventListener('click', // TODO fix dependency cycle to solid-panes by calling outlineManager function (_e) { dom.outlineManager.GotoSubject(theClass, true, undefined, true, undefined); }, false); return box; } log.debug('lists[0] is ' + lists[0]); form = lists[0]; // Pick any one } log.debug('form is ' + form); box.setAttribute('style', "border: 0.05em solid ".concat(_styleConstants["default"].formBorderColor, "; color: ").concat(_styleConstants["default"].formBorderColor)); // @@color? box.innerHTML = '<h3>New ' + utils.label(theClass) + '</h3>'; var formFunction = (0, _fieldFunction.fieldFunction)(dom, form); var object = newThing(dataDoc); var gotButton = false; var itemDone = function itemDone(ok, body) { if (!ok) return callbackFunction(ok, body); var insertMe = []; if (subject && !kb.holds(subject, predicate, object, dataDoc)) { insertMe.push($rdf.st(subject, predicate, object, dataDoc)); } if (subject && !kb.holds(object, ns.rdf('type'), theClass, dataDoc)) { insertMe.push($rdf.st(object, ns.rdf('type'), theClass, dataDoc)); } if (insertMe.length) { kb.updater.update([], insertMe, linkDone); } else { callbackFunction(true, body); } if (!gotButton) { gotButton = box.appendChild(buttons.linkButton(dom, object)); } }; function linkDone(uri, ok, body) { return callbackFunction(ok, body); } log.info('paneUtils Object is ' + object); var f = formFunction(dom, box, {}, object, form, dataDoc, itemDone); var rb = buttons.removeButton(dom, f); rb.setAttribute('style', 'float: right;'); box.AJAR_subject = object; return box; } function makeDescription(dom, kb, subject, predic