solid-ui
Version:
UI library for writing Solid read-write-web applications
467 lines (463 loc) • 19.9 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Person = exports.PeoplePicker = exports.GroupPicker = exports.GroupBuilder = exports.Group = void 0;
var _taggedTemplateLiteral2 = _interopRequireDefault(require("@babel/runtime/helpers/taggedTemplateLiteral"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _escapeHtml = _interopRequireDefault(require("escape-html"));
var _uuid = _interopRequireDefault(require("uuid"));
var rdf = _interopRequireWildcard(require("rdflib"));
var debug = _interopRequireWildcard(require("../debug"));
var _dragAndDrop = require("./dragAndDrop");
var _error = require("./error");
var _iconBase = require("../iconBase");
var ns = _interopRequireWildcard(require("../ns"));
var _solidLogic = require("solid-logic");
var _templateObject;
/**
*
* People Picker Pane
*
* This pane offers a mechanism for selecting a set of individuals, groups, or
* organizations to take some action on.
*
* Assumptions
* - Assumes that the user has a type index entry for vcard:AddressBook. @@ bad assuption
*
*/
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 _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
var kb = _solidLogic.solidLogicSingleton.store;
var PeoplePicker = exports.PeoplePicker = /*#__PURE__*/function () {
function PeoplePicker(element, typeIndex, groupPickedCb, options) {
(0, _classCallCheck2["default"])(this, PeoplePicker);
// @@ can't expect typeindex to be passed
this.options = options || {};
this.element = element;
this.typeIndex = typeIndex;
this.groupPickedCb = groupPickedCb;
this.selectedgroup = this.options.selectedgroup; // current selected group if any
this.onSelectGroup = this.onSelectGroup.bind(this);
}
return (0, _createClass2["default"])(PeoplePicker, [{
key: "render",
value: function render() {
var _this = this;
var container = document.createElement('div');
container.style.maxWidth = '350px';
container.style.minHeight = '200px';
container.style.outline = '1px solid black';
container.style.display = 'flex';
if (this.selectedgroup) {
container.style.flexDirection = 'column';
var selectedGroup = document.createElement('div');
new Group(selectedGroup, this.selectedgroup).render();
var changeGroupButton = document.createElement('button');
changeGroupButton.textContent = (0, _escapeHtml["default"])('Change group');
changeGroupButton.addEventListener('click', function (_event) {
_this.selectedgroup = null;
_this.render();
});
container.appendChild(selectedGroup);
container.appendChild(changeGroupButton);
} else {
this.findAddressBook(this.typeIndex).then(function (_ref) {
var book = _ref.book;
var chooseExistingGroupButton = document.createElement('button');
chooseExistingGroupButton.textContent = (0, _escapeHtml["default"])('Pick an existing group');
chooseExistingGroupButton.style.margin = 'auto';
chooseExistingGroupButton.addEventListener('click', function (_event) {
new GroupPicker(container, book, _this.onSelectGroup).render();
});
var createNewGroupButton = document.createElement('button');
createNewGroupButton.textContent = (0, _escapeHtml["default"])('Create a new group');
createNewGroupButton.style.margin = 'auto';
createNewGroupButton.addEventListener('click', function (_event) {
_this.createNewGroup(book, _this.options.defaultNewGroupName).then(function (_ref2) {
var group = _ref2.group;
new GroupBuilder(_this.element, book, group, _this.onSelectGroup).render();
})["catch"](function (errorBody) {
_this.element.appendChild((0, _error.errorMessageBlock)(document, (0, _escapeHtml["default"])("Error creating a new group. (".concat(errorBody, ")"))));
});
});
container.appendChild(chooseExistingGroupButton);
container.appendChild(createNewGroupButton);
_this.element.innerHTML = '';
_this.element.appendChild(container);
})["catch"](function (err) {
_this.element.appendChild((0, _error.errorMessageBlock)(document, (0, _escapeHtml["default"])("Could find your groups. (".concat(err, ")"))));
});
}
this.element.innerHTML = '';
this.element.appendChild(container);
return this;
}
}, {
key: "findAddressBook",
value: function findAddressBook(typeIndex) {
return new Promise(function (resolve, reject) {
kb.fetcher.nowOrWhenFetched(typeIndex, function (ok, err) {
if (!ok) {
return reject(err);
}
var bookRegistration = kb.any(null, ns.solid('forClass'), ns.vcard('AddressBook'));
if (!bookRegistration) {
return reject(new Error('no address book registered in the solid type index ' + typeIndex));
}
var book = kb.any(bookRegistration, ns.solid('instance'));
if (!book) {
return reject(new Error('incomplete address book registration'));
}
kb.fetcher.load(book).then(function (_xhr) {
return resolve({
book: book
});
})["catch"](function (err) {
return reject(new Error('Could not load address book ' + err));
});
});
});
}
}, {
key: "createNewGroup",
value: function createNewGroup(book, defaultNewGroupName) {
var _indexes = indexes(book),
groupIndex = _indexes.groupIndex,
groupContainer = _indexes.groupContainer;
var group = rdf.sym("".concat(groupContainer.uri).concat(_uuid["default"].v4().slice(0, 8), ".ttl#this"));
var name = defaultNewGroupName || 'Untitled Group';
// NOTE that order matters here. Unfortunately this type of update is
// non-atomic in that solid requires us to send two PATCHes, either of which
// might fail.
var patchPromises = [group.doc(), groupIndex].map(function (doc) {
var typeStatement = rdf.st(group, ns.rdf('type'), ns.vcard('Group'), doc);
var nameStatement = rdf.st(group, ns.vcard('fn'), name, group.doc(), doc);
var includesGroupStatement = rdf.st(book, ns.vcard('includesGroup'), group, doc);
var toIns = doc.equals(groupIndex) ? [typeStatement, nameStatement, includesGroupStatement] : [typeStatement, nameStatement];
return patch(doc.uri, {
toIns: toIns
}).then(function () {
toIns.forEach(function (st) {
kb.add(st);
});
});
});
return Promise.all(patchPromises).then(function () {
return {
group: group
};
})["catch"](function (err) {
debug.log('Could not create new group. PATCH failed ' + err);
throw new Error("Couldn't create new group. PATCH failed for (".concat(err.xhr ? err.xhr.responseURL : '', " )"));
});
}
}, {
key: "onSelectGroup",
value: function onSelectGroup(group) {
this.selectedgroup = group;
this.groupPickedCb(group);
this.render();
}
}]);
}();
var GroupPicker = exports.GroupPicker = /*#__PURE__*/function () {
function GroupPicker(element, book, onSelectGroup) {
(0, _classCallCheck2["default"])(this, GroupPicker);
this.element = element;
this.book = book;
this.onSelectGroup = onSelectGroup;
}
return (0, _createClass2["default"])(GroupPicker, [{
key: "render",
value: function render() {
var _this2 = this;
this.loadGroups().then(function (groups) {
// render the groups
var container = document.createElement('div');
container.style.display = 'flex';
container.style.flexDirection = 'column';
groups.forEach(function (group) {
var groupButton = document.createElement('button');
groupButton.addEventListener('click', _this2.handleClickGroup(group));
new Group(groupButton, group).render();
container.appendChild(groupButton);
});
_this2.element.innerHTML = '';
_this2.element.appendChild(container);
})["catch"](function (err) {
_this2.element.appendChild((0, _error.errorMessageBlock)(document, (0, _escapeHtml["default"])("There was an error loading your groups. (".concat(err, ")"))));
});
return this;
}
}, {
key: "loadGroups",
value: function loadGroups() {
var _this3 = this;
return new Promise(function (resolve, reject) {
var _indexes2 = indexes(_this3.book),
groupIndex = _indexes2.groupIndex;
kb.fetcher.nowOrWhenFetched(groupIndex, function (ok, err) {
if (!ok) {
return reject(err);
}
var groups = kb.each(_this3.book, ns.vcard('includesGroup'));
return resolve(groups);
});
});
}
}, {
key: "handleClickGroup",
value: function handleClickGroup(group) {
var _this4 = this;
return function (_event) {
_this4.onSelectGroup(group);
};
}
}]);
}();
var Group = exports.Group = /*#__PURE__*/function () {
function Group(element, group) {
(0, _classCallCheck2["default"])(this, Group);
this.element = element;
this.group = group;
}
return (0, _createClass2["default"])(Group, [{
key: "render",
value: function render() {
var container = document.createElement('div');
container.textContent = (0, _escapeHtml["default"])(
// @@@@@ need to escape??
getWithDefault(this.group, ns.vcard('fn'), "[".concat(this.group.value, "]")));
this.element.innerHTML = '';
this.element.appendChild(container);
return this;
}
}]);
}();
var GroupBuilder = exports.GroupBuilder = /*#__PURE__*/function () {
function GroupBuilder(element, book, group, doneBuildingCb, groupChangedCb) {
(0, _classCallCheck2["default"])(this, GroupBuilder);
this.element = element;
this.book = book;
this.group = group;
this.onGroupChanged = function (err, changeType, agent) {
if (groupChangedCb) {
groupChangedCb(err, changeType, agent);
}
};
this.groupChangedCb = groupChangedCb;
this.doneBuildingCb = doneBuildingCb;
}
return (0, _createClass2["default"])(GroupBuilder, [{
key: "refresh",
value: function refresh() {
// TODO: implement
}
}, {
key: "render",
value: function render() {
var _this5 = this;
var dropContainer = document.createElement('div');
dropContainer.style.maxWidth = '350px';
dropContainer.style.minHeight = '200px';
dropContainer.style.outline = '1px solid black';
dropContainer.style.display = 'flex';
dropContainer.style.flexDirection = 'column';
(0, _dragAndDrop.makeDropTarget)(dropContainer, function (uris) {
uris.forEach(function (uri) {
_this5.add(uri)["catch"](function (err) {
_this5.element.appendChild((0, _error.errorMessageBlock)(document, (0, _escapeHtml["default"])("Could not add the given WebId. (".concat(err, ")"))));
});
});
});
var groupNameInput = document.createElement('input');
groupNameInput.type = 'text';
groupNameInput.value = getWithDefault(this.group, ns.vcard('fn'), 'Untitled Group');
groupNameInput.addEventListener('change', function (event) {
_this5.setGroupName(event.target.value)["catch"](function (err) {
_this5.element.appendChild((0, _error.errorMessageBlock)(document, "Error changing group name. (".concat(err, ")")));
});
});
var groupNameLabel = document.createElement('label');
groupNameLabel.textContent = (0, _escapeHtml["default"])('Group Name:');
groupNameLabel.appendChild(groupNameInput);
dropContainer.appendChild(groupNameLabel);
if (kb.any(this.group, ns.vcard('hasMember'))) {
kb.match(this.group, ns.vcard('hasMember')).forEach(function (statement) {
var webIdNode = statement.object;
var personDiv = document.createElement('div');
new Person(personDiv, webIdNode, _this5.handleRemove(webIdNode)).render();
dropContainer.appendChild(personDiv);
});
} else {
var copy = document.createElement('p');
copy.textContent = (0, _escapeHtml["default"])(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2["default"])(["\n To add someone to this group, drag and drop their WebID URL onto the box.\n "])));
dropContainer.appendChild(copy);
}
var doneBuildingButton = document.createElement('button');
doneBuildingButton.textContent = (0, _escapeHtml["default"])('Done');
doneBuildingButton.addEventListener('click', function (_event) {
_this5.doneBuildingCb(_this5.group);
});
dropContainer.appendChild(doneBuildingButton);
this.element.innerHTML = '';
this.element.appendChild(dropContainer);
return this;
}
}, {
key: "add",
value: function add(webId) {
var _this6 = this;
return new Promise(function (resolve, reject) {
kb.fetcher.nowOrWhenFetched(webId, function (ok, err) {
if (!ok) {
_this6.onGroupChanged(err);
return reject(err);
}
// make sure it's a valid person, group, or entity (for now just handle
// webId)
var webIdNode = rdf.namedNode(webId);
var rdfClass = kb.any(webIdNode, ns.rdf('type'));
if (!rdfClass || !rdfClass.equals(ns.foaf('Person'))) {
return reject(new Error("Only people supported right now. (tried to add something of type ".concat(rdfClass.value, ")")));
}
return resolve(webIdNode);
});
}).then(function (webIdNode) {
var statement = rdf.st(_this6.group, ns.vcard('hasMember'), webIdNode);
if (kb.holdsStatement(statement)) {
return webIdNode;
}
return patch(_this6.group.doc().uri, {
toIns: [statement]
}).then(function () {
statement.why = _this6.group.doc();
kb.add(statement);
_this6.onGroupChanged(null, 'added', webIdNode);
_this6.render();
});
});
}
}, {
key: "handleRemove",
value: function handleRemove(webIdNode) {
var _this7 = this;
return function (_event) {
var statement = rdf.st(_this7.group, ns.vcard('hasMember'), webIdNode);
return patch(_this7.group.doc().uri, {
toDel: [statement]
}).then(function () {
kb.remove(statement);
_this7.onGroupChanged(null, 'removed', webIdNode);
_this7.render();
return true;
})["catch"](function (err) {
var name = kb.any(webIdNode, ns.foaf('name'));
var errorMessage = name && name.value ? "Could not remove ".concat(name.value, ". (").concat(err, ")") : "Could not remove ".concat(webIdNode.value, ". (").concat(err, ")");
throw new Error(errorMessage);
});
};
}
}, {
key: "setGroupName",
value: function setGroupName(name) {
var _this8 = this;
var _indexes3 = indexes(this.book),
groupIndex = _indexes3.groupIndex;
var updatePromises = [this.group.doc(), groupIndex].map(function (namedGraph) {
var oldNameStatements = kb.match(_this8.group, ns.vcard('fn'), null, namedGraph);
var newNameStatement = rdf.st(_this8.group, ns.vcard('fn'), rdf.literal(name));
return patch(namedGraph.value, {
toDel: oldNameStatements,
toIns: [newNameStatement]
}).then(function (_solidResponse) {
kb.removeStatements(oldNameStatements);
newNameStatement.why = namedGraph;
kb.add(newNameStatement);
});
});
return Promise.all(updatePromises);
}
}]);
}(); // @ignore exporting this only for the unit test
// @@ TODO maybe I should move this down at end, but for
// now I will leave it where it was
var Person = exports.Person = /*#__PURE__*/function () {
function Person(element, webIdNode, handleRemove) {
(0, _classCallCheck2["default"])(this, Person);
this.webIdNode = webIdNode;
this.element = element;
this.handleRemove = handleRemove;
}
return (0, _createClass2["default"])(Person, [{
key: "render",
value: function render() {
var _this9 = this;
var container = document.createElement('div');
container.style.display = 'flex';
// TODO: take a look at UI.widgets.setName
var imgSrc = getWithDefault(this.webIdNode, ns.foaf('img'), _iconBase.iconBase + 'noun_15059.svg');
var profileImg = document.createElement('img');
profileImg.src = (0, _escapeHtml["default"])(imgSrc);
profileImg.width = '50';
profileImg.height = '50';
profileImg.style.margin = '5px';
// TODO: take a look at UI.widgets.setImage
var name = getWithDefault(this.webIdNode, ns.foaf('name'), "[".concat(this.webIdNode, "]"));
var nameSpan = document.createElement('span');
nameSpan.innerHTML = (0, _escapeHtml["default"])(name);
nameSpan.style.flexGrow = '1';
nameSpan.style.margin = 'auto 0';
var removeButton = document.createElement('button');
removeButton.textContent = 'Remove';
removeButton.addEventListener('click', function (_event) {
return _this9.handleRemove()["catch"](function (err) {
_this9.element.appendChild((0, _error.errorMessageBlock)(document, (0, _escapeHtml["default"])("".concat(err))));
});
});
removeButton.style.margin = '5px';
container.appendChild(profileImg);
container.appendChild(nameSpan);
container.appendChild(removeButton);
this.element.innerHTML = '';
this.element.appendChild(container);
return this;
}
}]);
}();
function getWithDefault(subject, predicate, defaultValue) {
var object = kb.any(subject, predicate);
return object ? object.value : defaultValue;
}
function patch(url, _ref3) {
var toDel = _ref3.toDel,
toIns = _ref3.toIns;
return new Promise(function (resolve, reject) {
kb.updater.update(toDel, toIns, function (uri, success, errorMessage) {
if (!success) {
return reject(new Error("PATCH failed for resource <".concat(uri, ">: ").concat(errorMessage)));
}
resolve();
});
});
// return webClient.patch(url, toDel, toIns)
// .then(solidResponse => {
// const status = solidResponse.xhr.status
// if (status < 200 || status >= 400) {
// const err = new Error(`PATCH failed for resource <${solidResponse.url}>`)
// err.solidResponse = solidResponse
// throw err
// }
// })
}
function indexes(book) {
return {
// bookIndex: book,
groupIndex: kb.any(book, ns.vcard('groupIndex')),
groupContainer: kb.sym(book.dir().uri + 'Group/')
};
}
//# sourceMappingURL=peoplePicker.js.map