From 32d6b1ca8e4dcebf05a1348493c827c5e8804157 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 13 Jun 2019 10:43:51 +0200 Subject: [PATCH 1/2] Convert the TrustedApplications pane to React --- index.js | 2 +- ...ions.test.ts.snap => service.test.ts.snap} | 4 +- trustedApplications/container.tsx | 81 +++++ trustedApplications/index.tsx | 64 ++++ trustedApplications/model.ts | 7 + ...edApplications.test.ts => service.test.ts} | 13 +- trustedApplications/service.ts | 78 +++++ .../trustedApplicationsPane.ts | 284 ------------------ .../trustedApplicationsUtils.ts | 39 --- trustedApplications/view.tsx | 215 +++++++++++++ types.ts | 1 + 11 files changed, 460 insertions(+), 328 deletions(-) rename trustedApplications/__snapshots__/{trustedApplications.test.ts.snap => service.test.ts.snap} (96%) create mode 100644 trustedApplications/container.tsx create mode 100644 trustedApplications/index.tsx create mode 100644 trustedApplications/model.ts rename trustedApplications/{trustedApplications.test.ts => service.test.ts} (80%) create mode 100644 trustedApplications/service.ts delete mode 100644 trustedApplications/trustedApplicationsPane.ts delete mode 100644 trustedApplications/trustedApplicationsUtils.ts create mode 100644 trustedApplications/view.tsx diff --git a/index.js b/index.js index bd54dfbc..219edd0f 100644 --- a/index.js +++ b/index.js @@ -131,7 +131,7 @@ register(require('./internalPane.js')) // The home pane is a 2016 experiment. Always there. register(require('./profile/profilePane').default) // edit your public profile -register(require('./trustedApplications/trustedApplicationsPane').default) // manage your trusted applications +register(require('./trustedApplications/index').default) // manage your trusted applications register(require('./home/homePane').default) // ENDS diff --git a/trustedApplications/__snapshots__/trustedApplications.test.ts.snap b/trustedApplications/__snapshots__/service.test.ts.snap similarity index 96% rename from trustedApplications/__snapshots__/trustedApplications.test.ts.snap rename to trustedApplications/__snapshots__/service.test.ts.snap index 9a8bdfc4..c0f2673c 100644 --- a/trustedApplications/__snapshots__/trustedApplications.test.ts.snap +++ b/trustedApplications/__snapshots__/service.test.ts.snap @@ -43,7 +43,7 @@ Array [ Statement { "object": NamedNode { "termType": "NamedNode", - "value": "http://www.w3.org/ns/auth/acl#Read", + "value": "http://www.w3.org/ns/auth/acl#read", }, "predicate": NamedNode { "termType": "NamedNode", @@ -62,7 +62,7 @@ Array [ Statement { "object": NamedNode { "termType": "NamedNode", - "value": "http://www.w3.org/ns/auth/acl#Write", + "value": "http://www.w3.org/ns/auth/acl#write", }, "predicate": NamedNode { "termType": "NamedNode", diff --git a/trustedApplications/container.tsx b/trustedApplications/container.tsx new file mode 100644 index 00000000..29a22569 --- /dev/null +++ b/trustedApplications/container.tsx @@ -0,0 +1,81 @@ +import * as React from 'react' +import $rdf from 'rdflib' +import vocab from 'solid-namespace' +import { View } from './view' +import { ContainerProps } from '../types' +import { TrustedApplication, Mode } from './model' +import { getStatementsToAdd, getStatementsToDelete, fetchTrustedApps } from './service' + +const ns = vocab($rdf) + +export const Container: React.FC = (props) => { + if (!props.session) { + return
You are not logged in
+ } + + const isEditable: boolean = (props.store as any).updater.editable(props.subject.doc().uri, props.store) + if (!isEditable) { + return
Your profile {props.subject.doc().uri} is not editable, so we cannot do much here.
+ } + + const fetchedTrustedApps: TrustedApplication[] = fetchTrustedApps(props.store, props.subject, ns) + + const [trustedApps, setTrustedApps] = React.useState(fetchedTrustedApps) + + const addOrEditApp = (origin: string, modes: Mode[]) => { + const result = new Promise((resolve) => { + const deletions = getStatementsToDelete($rdf.sym(origin), props.subject, props.store, ns) + const additions = getStatementsToAdd($rdf.sym(origin), generateRandomString(), modes, props.subject, ns) + props.store.updater!.update(deletions, additions, () => { + const newApp: TrustedApplication = { subject: props.subject.value, origin, modes } + setTrustedApps(insertTrustedApp(newApp, trustedApps)) + resolve() + }) + }) + + return result + } + + const deleteApp = (origin: string) => { + const result = new Promise((resolve) => { + const deletions = getStatementsToDelete($rdf.sym(origin), props.subject, props.store, ns) + props.store.updater!.update(deletions, [], () => { + setTrustedApps(removeTrustedApp(origin, trustedApps)) + resolve() + }) + }) + + return result + } + + return ( +
+ +
+ ) +} + +function insertTrustedApp (app: TrustedApplication, into: TrustedApplication[]): TrustedApplication[] { + const index = into.findIndex(found => found.origin === app.origin) + if (index === -1) { + return into.concat(app) + } + + return into.slice(0, index) + .concat(app) + .concat(into.slice(index + 1)) +} +function removeTrustedApp (origin: string, from: TrustedApplication[]): TrustedApplication[] { + const index = from.findIndex(found => found.origin === origin) + return (index === -1) + ? from + : from.slice(0, index).concat(from.slice(index + 1)) +} + +function generateRandomString (): string { + return Math.random().toString(36).substring(7) +} diff --git a/trustedApplications/index.tsx b/trustedApplications/index.tsx new file mode 100644 index 00000000..9ad93080 --- /dev/null +++ b/trustedApplications/index.tsx @@ -0,0 +1,64 @@ +/* Profile Editing Pane +** +** Unlike most panes, this is available any place whatever the real subject, +** and allows the user to edit their own profile. +** +** Usage: paneRegistry.register('profile/profilePane') +** or standalone script adding onto existing mashlib. +*/ + +import * as React from 'react' +import * as ReactDOM from 'react-dom' +import solidUi, { SolidUi } from 'solid-ui' +import { IndexedFormula } from 'rdflib' +import paneRegistry from 'pane-registry' + +import { PaneDefinition } from '../types' +import { Container } from './container' + +const nodeMode = (typeof module !== 'undefined') + +let panes +let UI: SolidUi + +if (nodeMode) { + UI = solidUi + panes = paneRegistry +} else { // Add to existing mashlib + panes = (window as any).panes + UI = panes.UI +} + +const kb: IndexedFormula = UI.store + +const thisPane: PaneDefinition = { + icon: UI.icons.iconBase + 'noun_15177.svg', // Looks like an A - could say it's for Applications? + + name: 'trustedApplications', + + label: function (subject) { + var types = kb.findTypeURIs(subject) + if (types[UI.ns.foaf('Person').uri] || types[UI.ns.vcard('Individual').uri]) { + return 'Manage your trusted applications' + } + return null + }, + + render: function (subject, _dom) { + const container = document.createElement('div') + UI.authn.solidAuthClient.currentSession().then((session: any) => { + ReactDOM.render( + , + container + ) + }) + + return container + } +} + +export default thisPane +if (!nodeMode) { + console.log('*** patching in live pane: ' + thisPane.name) + panes.register(thisPane) +} diff --git a/trustedApplications/model.ts b/trustedApplications/model.ts new file mode 100644 index 00000000..792870f8 --- /dev/null +++ b/trustedApplications/model.ts @@ -0,0 +1,7 @@ +export type Mode = 'read' | 'append' | 'write' | 'control' + +export interface TrustedApplication { + origin: string + subject: string + modes: Mode[] +} diff --git a/trustedApplications/trustedApplications.test.ts b/trustedApplications/service.test.ts similarity index 80% rename from trustedApplications/trustedApplications.test.ts rename to trustedApplications/service.test.ts index 2bb48c05..54cb1157 100644 --- a/trustedApplications/trustedApplications.test.ts +++ b/trustedApplications/service.test.ts @@ -1,7 +1,7 @@ /* eslint-env jest */ const $rdf = require('rdflib') const ns = require('solid-namespace')($rdf) -const { getStatementsToDelete, getStatementsToAdd } = require('./trustedApplicationsUtils') +const { getStatementsToDelete, getStatementsToAdd, deserialiseMode } = require('./service') describe('getStatementsToDelete', () => { it('should return an empty array when there are no statements', () => { @@ -43,10 +43,19 @@ describe('getStatementsToAdd', () => { it('should return all required statements to add the given permissions for a given origin', () => { const mockOrigin = $rdf.sym('https://fake.origin') const mockProfile = $rdf.sym('https://fake.profile#me') - const modes = [ns.acl('Read'), ns.acl('Write')] + const modes = ['read', 'write'] const statementsToAdd = getStatementsToAdd(mockOrigin, 'mock_app_id', modes, mockProfile, ns) expect(statementsToAdd.length).toBe(4) expect(statementsToAdd).toMatchSnapshot() }) }) + +describe('deserialiseMode', () => { + it('should convert a full namespaced ACL to a plaintext string', () => { + expect(deserialiseMode($rdf.sym(ns.acl('read')), ns)).toBe('read') + expect(deserialiseMode($rdf.sym(ns.acl('append')), ns)).toBe('append') + expect(deserialiseMode($rdf.sym(ns.acl('write')), ns)).toBe('write') + expect(deserialiseMode($rdf.sym(ns.acl('control')), ns)).toBe('control') + }) +}) diff --git a/trustedApplications/service.ts b/trustedApplications/service.ts new file mode 100644 index 00000000..816d0567 --- /dev/null +++ b/trustedApplications/service.ts @@ -0,0 +1,78 @@ +import $rdf, { NamedNode, IndexedFormula, Statement } from 'rdflib' +import { Namespaces } from 'solid-namespace' +import { Mode, TrustedApplication } from './model' + +export function getStatementsToDelete ( + origin: NamedNode, + person: NamedNode, + kb: IndexedFormula, + ns: Namespaces +) { + // `as any` is used because the rdflib typings incorrectly require a Node to be passed, + // even though null is also valid: + const applicationStatements = kb.statementsMatching(null as any, ns.acl('origin'), origin, null as any, null as any) + const statementsToDelete = applicationStatements.reduce( + (memo, st) => { + return memo + .concat(kb.statementsMatching(person, ns.acl('trustedApp'), st.subject, null as any, false)) + .concat(kb.statementsMatching(st.subject, null as any, null as any, null as any, false)) + }, + [] as Statement[] + ) + return statementsToDelete +} + +export function getStatementsToAdd ( + origin: NamedNode, + nodeName: string, + modes: Mode[], + person: NamedNode, + ns: Namespaces +) { + var application = new $rdf.BlankNode(`bn_${nodeName}`) + return [ + $rdf.st(person, ns.acl('trustedApp'), application, person.doc()), + $rdf.st(application, ns.acl('origin'), origin, person.doc()), + ...modes + .map(mode => { + return ns.acl(mode) + }) + .map(mode => $rdf.st(application, ns.acl('mode'), mode, person.doc())) + ] +} + +/* istanbul ignore next [This executes the actual HTTP requests, which is too much effort to test.] */ +export function fetchTrustedApps ( + store: $rdf.IndexedFormula, + subject: $rdf.NamedNode, + ns: Namespaces +): TrustedApplication[] { + return (store.each(subject, ns.acl('trustedApp'), undefined, undefined) as any) + .flatMap((app: $rdf.NamedNode) => { + return store.each(app, ns.acl('origin'), undefined, undefined) + .map((origin) => { + const modes = store.each(app, ns.acl('mode'), undefined, undefined) + const trustedApp: TrustedApplication = { + origin: origin.value, + subject: subject.value, + modes: modes.map((mode) => deserialiseMode(mode as $rdf.NamedNode, ns)) + } + return trustedApp + }) + }) + .sort((appA: TrustedApplication, appB: TrustedApplication) => (appA.origin > appB.origin) ? 1 : -1) +} + +/** + * @param serialisedMode The full IRI of a mode + * @returns A plain text string representing that mode, i.e. 'read', 'append', 'write' or 'control' + */ +export function deserialiseMode (serialisedMode: $rdf.NamedNode, ns: Namespaces): Mode { + const deserialisedMode = serialisedMode.value + .replace(ns.acl('read').value, 'read') + .replace(ns.acl('append').value, 'append') + .replace(ns.acl('write').value, 'write') + .replace(ns.acl('control').value, 'control') + + return deserialisedMode as Mode +} diff --git a/trustedApplications/trustedApplicationsPane.ts b/trustedApplications/trustedApplicationsPane.ts deleted file mode 100644 index 137c4154..00000000 --- a/trustedApplications/trustedApplicationsPane.ts +++ /dev/null @@ -1,284 +0,0 @@ -/* Profile Editing Pane -** -** Unlike most panes, this is available any place whatever the real subject, -** and allows the user to edit their own profile. -** -** Usage: paneRegistry.register('profile/profilePane') -** or standalone script adding onto existing mashlib. -*/ - -import solidUi, { SolidUi } from 'solid-ui' -import $rdf, { NamedNode, IndexedFormula } from 'rdflib' -import { Namespaces } from 'solid-namespace' -import paneRegistry from 'pane-registry' - -import { getStatementsToAdd, getStatementsToDelete } from './trustedApplicationsUtils' -import { PaneDefinition } from '../types' - -const nodeMode = (typeof module !== 'undefined') - -let panes -let UI: SolidUi - -if (nodeMode) { - UI = solidUi - panes = paneRegistry -} else { // Add to existing mashlib - panes = (window as any).panes - UI = panes.UI -} - -const kb: IndexedFormula = UI.store -const ns: Namespaces = UI.ns - -const thisColor = '#418d99' - -interface FormElements { - modes: HTMLInputElement[]; - // This appears to be used to store either a node from the store, - // or a reference to the input (checkbox) element for a particular mode. - // These typings were created post-hoc, so I'm not sure if that was intentional. - // Thus, this union type should be considered as descriptive rather than prescriptive. - origin: (undefined | NamedNode | HTMLInputElement); -}; - -const thisPane: PaneDefinition = { - icon: UI.icons.iconBase + 'noun_15177.svg', // Looks like an A - could say it's for Applications? - - name: 'trustedApplications', - - label: function (subject) { - var types = kb.findTypeURIs(subject) - if (types[UI.ns.foaf('Person').uri] || types[UI.ns.vcard('Individual').uri]) { - return 'Manage your trusted applications' - } - return null - }, - - render: function (subject, dom) { - var div = dom.createElement('div') - div.classList.add('trusted-applications-pane') - div.setAttribute('style', 'border: 0.3em solid ' + thisColor + '; border-radius: 0.5em; padding: 0.7em; margin-top:0.7em;') - var table = div.appendChild(dom.createElement('table')) - var main = table.appendChild(dom.createElement('tr')) - var bottom = table.appendChild(dom.createElement('tr')) - var statusArea = bottom.appendChild(dom.createElement('div')) - statusArea.setAttribute('style', 'padding: 0.7em;') - - var context = { dom: dom, div: main, statusArea: statusArea, me: null } - UI.authn.logInLoadProfile(context).then((context: any) => { - let subject: NamedNode = context.me - - var profile = subject.doc() - var editable = UI.store.updater.editable(profile.uri, kb) - - main.appendChild(createText('h3', 'Manage your trusted applications')) - - if (!editable) { - main.appendChild(UI.widgets.errorMessageBlock(dom, `Your profile ${subject.doc().uri} is not editable, so we cannot do much here.`)) - return - } - - main.appendChild(createText('p', 'Here you can manage the applications you trust.')) - - const applicationsTable = createApplicationTable(subject) - main.appendChild(applicationsTable) - - main.appendChild(createText('h4', 'Notes')) - main.appendChild(createContainer('ol', [ - main.appendChild(createText('li', 'Trusted applications will get access to all resources that you have access to.')), - main.appendChild(createText('li', 'You can limit which modes they have by default.')), - main.appendChild(createText('li', 'They will not gain more access than you have.')) - ])) - main.appendChild(createText('p', 'Application URLs must be valid URL. Examples are http://localhost:3000, https://trusted.app, and https://sub.trusted.app.')) - }, (err: any) => { - statusArea.appendChild(UI.widgets.errorMessageBlock(dom, err)) - }) - return div - } // render() -} // - -function createApplicationTable (subject: NamedNode) { - var applicationsTable = createElement('table', { - 'class': 'results' - }) - - // creating headers - var header = createContainer('tr', [ - createText('th', 'Application URL'), - createText('th', 'Access modes'), - createText('th', 'Actions') - ]) - applicationsTable.appendChild(header); - - // creating rows - (kb.each(subject, ns.acl('trustedApp'), undefined, undefined) as any) - .flatMap((app: any) => { - return kb.each(app, ns.acl('origin'), undefined, undefined) - .map(origin => ({ appModes: kb.each(app, ns.acl('mode'), undefined, undefined), origin })) - }) - .sort((a: any, b: any) => a.origin.value < b.origin.value ? -1 : 1) - .forEach(({ appModes, origin }: {appModes: NamedNode[], origin: NamedNode}) => applicationsTable.appendChild(createApplicationEntry(subject, origin, appModes, updateTable))) - - // adding a row for new applications - applicationsTable.appendChild(createApplicationEntry(subject, null, [ns.acl('Read')], updateTable)) - - return applicationsTable - - function updateTable () { - applicationsTable.parentElement!.replaceChild(createApplicationTable(subject), applicationsTable) - } -} - -function createApplicationEntry ( - subject: NamedNode, - origin: NamedNode | null, - appModes: NamedNode[], - updateTable: () => void -): HTMLTableRowElement { - var trustedApplicationState = { - origin, - appModes, - formElements: { - modes: [], - origin: undefined - } as FormElements - } - return createContainer('tr', [ - createContainer('td', [ - createElement('input', { - 'class': 'textinput', - placeholder: 'Write new URL here', - value: origin ? origin.value : '' - }, {}, (element) => { trustedApplicationState.formElements.origin = element }) - ]), - createContainer('td', createModesInput(trustedApplicationState)), - createContainer('td', origin - ? [ - createText('button', 'Update', { - 'class': 'controlButton', - style: 'background: LightGreen;' - }, { - click: () => addOrEditApplication() - }), - createText('button', 'Delete', { - 'class': 'controlButton', - style: 'background: LightCoral;' - }, { - click: () => removeApplication() - }) - ] - : [ - createText('button', 'Add', { - 'class': 'controlButton', - style: 'background: LightGreen;' - }, { - click: () => addOrEditApplication() - }) - ]) - ]) - - function addOrEditApplication () { - var origin - try { - origin = $rdf.sym(trustedApplicationState.formElements.origin!.value) - } catch (err) { - return alert('Please provide an application URL you want to trust') - } - - var modes = trustedApplicationState.formElements.modes - .filter(checkbox => checkbox.checked) - .map(checkbox => checkbox.value) - - var deletions = getStatementsToDelete(origin, subject, kb, ns) - var additions = getStatementsToAdd(origin, generateRandomString(), modes, subject, ns); - (kb as any).updater.update(deletions, additions, handleUpdateResponse) - } - - function removeApplication () { - var origin - try { - origin = $rdf.sym(trustedApplicationState.formElements.origin!.value) - } catch (err) { - return alert('Please provide an application URL you want to remove trust from') - } - - var deletions = getStatementsToDelete(origin, subject, kb, ns); - (kb as any).updater.update(deletions, null, handleUpdateResponse) - } - - function handleUpdateResponse (uri: any, success: boolean, errorBody: any) { - if (success) { - return updateTable() - } - console.error(uri, errorBody) - } -} - -function createElement ( - elementName: K, - attributes: {[name: string]: string} = {}, - eventListeners: {[eventName: string]: EventListener} = {}, - onCreated: (null | ((createdElement: HTMLElementTagNameMap[K]) => void)) = null -) { - var element = document.createElement(elementName) - if (onCreated) { - onCreated(element) - } - Object.keys(attributes).forEach(attName => { - element.setAttribute(attName, attributes[attName]) - }) - Object.keys(eventListeners).forEach(eventName => { - element.addEventListener(eventName, eventListeners[eventName]) - }) - return element -} - -function createContainer ( - elementName: K, - children: HTMLElement[], - attributes = {}, - eventListeners = {}, - onCreated = null -) { - var element = createElement(elementName, attributes, eventListeners, onCreated) - children.forEach(child => element.appendChild(child)) - return element -} - -function createText ( - elementName: K, - textContent: string | null, - attributes = {}, - eventListeners = {}, - onCreated = null -) { - var element = createElement(elementName, attributes, eventListeners, onCreated) - element.textContent = textContent - return element -} - -function createModesInput ({ appModes, formElements }: { appModes: NamedNode[], formElements: FormElements}) { - return ['Read', 'Write', 'Append', 'Control'].map(mode => { - var isChecked = appModes.some(appMode => appMode.value === ns.acl(mode).value) - return createContainer('label', [ - createElement('input', { - type: 'checkbox', - ...(isChecked ? { checked: '' } : {}), - value: ns.acl(mode).uri - }, {}, (element) => formElements.modes.push(element)), - createText('span', mode) - ]) - }) -} - -function generateRandomString () { - return Math.random().toString(36).substring(7) -} - -export default thisPane -if (!nodeMode) { - console.log('*** patching in live pane: ' + thisPane.name) - panes.register(thisPane) -} -// ENDS diff --git a/trustedApplications/trustedApplicationsUtils.ts b/trustedApplications/trustedApplicationsUtils.ts deleted file mode 100644 index d108d1a4..00000000 --- a/trustedApplications/trustedApplicationsUtils.ts +++ /dev/null @@ -1,39 +0,0 @@ -import $rdf, { NamedNode, IndexedFormula, Statement } from 'rdflib' -import { Namespaces } from 'solid-namespace' - -export function getStatementsToDelete ( - origin: NamedNode, - person: NamedNode, - kb: IndexedFormula, - ns: Namespaces -) { - // `as any` is used because the rdflib typings incorrectly require a Node to be passed, - // even though null is also valid: - const applicationStatements = kb.statementsMatching(null as any, ns.acl('origin'), origin, null as any, null as any) - const statementsToDelete = applicationStatements.reduce( - (memo, st) => { - return memo - .concat(kb.statementsMatching(person, ns.acl('trustedApp'), st.subject, null as any, false)) - .concat(kb.statementsMatching(st.subject, null as any, null as any, null as any, false)) - }, - [] as Statement[] - ) - return statementsToDelete -} - -export function getStatementsToAdd ( - origin: NamedNode, - nodeName: string, - modes: string[], - person: NamedNode, - ns: Namespaces -) { - var application = new $rdf.BlankNode(`bn_${nodeName}`) - return [ - $rdf.st(person, ns.acl('trustedApp'), application, person.doc()), - $rdf.st(application, ns.acl('origin'), origin, person.doc()), - ...modes - .map(mode => $rdf.sym(mode)) - .map(mode => $rdf.st(application, ns.acl('mode'), mode, person.doc())) - ] -} diff --git a/trustedApplications/view.tsx b/trustedApplications/view.tsx new file mode 100644 index 00000000..bed4a562 --- /dev/null +++ b/trustedApplications/view.tsx @@ -0,0 +1,215 @@ +import * as React from 'react' +import $rdf, { NamedNode } from 'rdflib' +import vocab from 'solid-namespace' +import { TrustedApplication, Mode } from './model' + +const ns = vocab($rdf) + +type AddOrUpdate = (origin: string, modes: Mode[]) => Promise +interface Props { + apps: Array>; + onSaveApp: AddOrUpdate; + onDeleteApp: (origin: string) => Promise; +} + +export const View: React.FC = (props) => { + return ( + <> +

Here you can manage the applications you trust.

, + + + + + + + + + + {props.apps.map((app) => ( + + ))} + +
Application URLAccess modesActions
+ +

Notes

+
    +
  1. Trusted applications will get access to all resources that you have access to.
  2. +
  3. You can limit which modes they have by default.
  4. +
  5. They will not gain more access than you have.
  6. +
+

+ Application URLs must be valid URL. + Examples are http://localhost:3000, https://trusted.app, and https://sub.trusted.app. +

+ + ) +} + +const ApplicationRow: React.FC<{ + app: TrustedApplication, + onSave: AddOrUpdate, + onDelete: (origin: string) => Promise +}> = (props) => { + const initialModes = { + read: props.app.modes.indexOf('read') !== -1, + append: props.app.modes.indexOf('append') !== -1, + write: props.app.modes.indexOf('write') !== -1, + control: props.app.modes.indexOf('control') !== -1 + } + const [modes, setModes] = React.useState<{[ key: string]: boolean}>(initialModes) + + const setMode = (mode: Mode, checked: boolean) => { + setModes({ ...modes, [mode]: checked }) + } + const getCheckboxHandler = (mode: Mode) => { + return (event: React.ChangeEvent) => setMode(mode, event.target.checked) + } + + const submitHandler = (event: React.FormEvent) => { + event.preventDefault() + + const newModes = Object.keys(modes).filter(mode => modes[mode]) as Mode[] + props.onSave(props.app.origin, newModes) + } + + return ( + + +

{props.app.origin}

+ + +
+
+ + + + +
+ +
+ + + + + + ) +} + +const NewApplication: React.FC<{ onSave: AddOrUpdate }> = (props) => { + const [origin, setOrigin] = React.useState() + const [modes, setModes] = React.useState<{[ key: string]: boolean}>({ + read: false, + append: false, + write: false, + control: false + }) + + const setMode = (mode: Mode, checked: boolean) => { + setModes({ ...modes, [mode]: checked }) + } + const getCheckboxHandler = (mode: Mode) => { + return (event: React.ChangeEvent) => setMode(mode, event.target.checked) + } + + const submitHandler = (event: React.FormEvent) => { + event.preventDefault() + + const newModes = Object.keys(modes).filter(mode => modes[mode]) as Mode[] + props.onSave(origin, newModes) + } + + return ( +
+
+ + setOrigin(e.target.value)} + name="origin" + id="origin" + placeholder="https://example.com" + /> +
+
+ + + + +
+ +
+ ) +} diff --git a/types.ts b/types.ts index 0c090262..afc10b11 100644 --- a/types.ts +++ b/types.ts @@ -43,4 +43,5 @@ export interface RevampPaneDefinition { export interface ContainerProps { store: IndexedFormula; subject: NamedNode; + session?: any; } From 4c8f9fdb42e3178a603b3ebbf1bd69e1630bc7ef Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 13 Jun 2019 11:08:36 +0200 Subject: [PATCH 2/2] Remove redundant casts The rdflib typings have been updated, so these are no longer necessary. --- trustedApplications/service.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/trustedApplications/service.ts b/trustedApplications/service.ts index 816d0567..b8f4d522 100644 --- a/trustedApplications/service.ts +++ b/trustedApplications/service.ts @@ -8,14 +8,12 @@ export function getStatementsToDelete ( kb: IndexedFormula, ns: Namespaces ) { - // `as any` is used because the rdflib typings incorrectly require a Node to be passed, - // even though null is also valid: - const applicationStatements = kb.statementsMatching(null as any, ns.acl('origin'), origin, null as any, null as any) + const applicationStatements = kb.statementsMatching(null, ns.acl('origin'), origin, null) const statementsToDelete = applicationStatements.reduce( (memo, st) => { return memo - .concat(kb.statementsMatching(person, ns.acl('trustedApp'), st.subject, null as any, false)) - .concat(kb.statementsMatching(st.subject, null as any, null as any, null as any, false)) + .concat(kb.statementsMatching(person, ns.acl('trustedApp'), st.subject, null, false)) + .concat(kb.statementsMatching(st.subject, null, null, null, false)) }, [] as Statement[] )