= {\n ...SemanticContextTypes,\n ...TemplateContextTypes,\n};\n\nexport type ComponentChildContext = TemplateContext;\n\nexport interface ComponentProps {\n /**\n * Scope with templates explicitely specified in the markup.\n *\n * If present then it means the component had been created from markup;\n * otherwise it has been created as part of another component.\n *\n * This property should be used only as implementation detail to template-related\n * components (like `SemanticIf`).\n */\n readonly markupTemplateScope?: TemplateService.TemplateScope;\n}\n\nconst ComponentPropTypes: { [K in keyof ComponentProps]?: any } = {\n markupTemplateScope: PropTypes.object,\n};\n\n/**\n * @author Alexey Morozov\n */\nexport abstract class PlatformComponent extends Component
{\n static propTypes: any = ComponentPropTypes;\n\n static childContextTypes: any = TemplateContextTypes;\n\n static readonly contextTypes: any = ContextTypes;\n readonly context: ComponentContext;\n\n readonly cancel = new Cancellation();\n\n get appliedTemplateScope(): TemplateService.TemplateScope {\n const { markupTemplateScope } = this.props as ComponentProps;\n if (markupTemplateScope) {\n return markupTemplateScope;\n }\n const inheritedScope = this.context.templateScope;\n return inheritedScope || TemplateService.TemplateScope.default;\n }\n\n constructor(props: P, context: any) {\n super(props, context);\n }\n\n componentWillUnmount() {\n this.cancel.cancelAll();\n }\n\n getChildContext(): ComponentChildContext {\n const { markupTemplateScope } = this.props as ComponentProps;\n // resets template scope to explicitely provided one if\n // component have been created from markup\n return {\n templateScope: markupTemplateScope || this.context.templateScope,\n };\n }\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as Kefir from 'kefir';\n\nimport { EventType } from './EventsApi';\nimport { EventMaker } from './Utils';\n\nexport interface BuiltInEventData {\n /**\n * Event which should be triggered when something should be refreshed.\n */\n 'Component.Refresh': object;\n /**\n * Event which should be triggered when data has been loaded.\n */\n 'Component.Loaded': object;\n /**\n * Event which should be triggered when a template should be updated with new properties.\n */\n 'Component.TemplateUpdate': object;\n}\nconst event: EventMaker = EventMaker;\n\nexport const ComponentRefresh = event('Component.Refresh');\n/**\n * Event which should be triggered when component starts loading data.\n */\nexport const ComponentLoading: EventType> = 'Component.Loading';\nexport const ComponentLoaded = event('Component.Loaded');\nexport const ComponentTemplateUpdate = event('Component.TemplateUpdate');\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as _ from 'lodash';\nimport * as uuid from 'uuid';\nimport * as Kefir from 'kefir';\nimport * as Immutable from 'immutable';\n\nimport { Rdf } from 'platform/api/rdf';\n\nimport { Event, EventFilter, EventType } from './EventsApi';\n\ninterface Subscriber {\n eventFilter: EventFilter;\n emitter: Kefir.Emitter>;\n}\n\n/**\n * exposed only for testing purpose\n */\nexport const _subscribers: { [key: string]: Subscriber } = {};\n\n/**\n * Listen to all events that satisfies given 'eventFilter'.\n */\nexport function listen(eventFilter: EventFilter): Kefir.Stream> {\n return Kefir.stream((emitter) => {\n const key = uuid.v4();\n _subscribers[key] = { eventFilter, emitter };\n\n // Emits the event if it has the current value\n const currentValue = EventSourceStore.getCurrentValue(eventFilter);\n if (currentValue) {\n emitter.emit(currentValue);\n }\n\n return () => {\n delete _subscribers[key]\n };\n });\n}\n\n/**\n * Trigger event.\n */\nexport function trigger(event: Event) {\n _.forEach(_subscribers, subscriber => {\n // in some browsers subscriber can be undefined because\n // `delete _subscriber[key]` keeps the key and makes value undefined\n if (subscriber) {\n const { eventFilter, emitter } = subscriber;\n if (\n (eventFilter.eventType ? eventFilter.eventType === event.eventType : true) &&\n (eventFilter.source ? eventFilter.source === event.source : true) &&\n (eventFilter.target ? _.includes(event.targets || [], eventFilter.target) : true)\n ) {\n emitter.emit(event);\n }\n }\n });\n EventSourceStore.updateCurrentValue(event);\n}\n\nexport function registerEventSource(eventSource: EventSource) {\n EventSourceStore.addEventSource(eventSource);\n}\n\nexport function unregisterEventSource(eventSource: EventSource) {\n EventSourceStore.deleteEventSource(eventSource);\n}\n\ninterface EventSource {\n source: string;\n eventType: EventType;\n currentValue?: Event;\n}\nnamespace EventSourceStore {\n let sources: Immutable.Map = Immutable.Map();\n\n export function getCurrentValue(eventFilter: EventFilter): Event | undefined {\n const key = eventSourceKey(eventFilter.source, eventFilter.eventType);\n const eventSource = sources.get(key);\n if (eventSource) {\n return eventSource.currentValue;\n }\n return undefined;\n }\n\n export function updateCurrentValue(event: Event) {\n const key = eventSourceKey(event.source, event.eventType);\n const eventSource = sources.get(key);\n if (eventSource) {\n sources = sources.set(key, { ...eventSource, currentValue: event });\n }\n }\n\n export function addEventSource(eventSource: EventSource) {\n const key = eventSourceKey(eventSource.source, eventSource.eventType);\n if (!sources.has(key)) {\n sources = sources.set(key, eventSource);\n }\n }\n\n export function deleteEventSource(eventSource: EventSource) {\n const key = eventSourceKey(eventSource.source, eventSource.eventType);\n sources = sources.remove(key);\n }\n\n function eventSourceKey(source: string, eventType: EventType): EventSourceKey {\n return new EventSourceKey(source, eventType);\n }\n}\n\nclass EventSourceKey {\n constructor(private _source: string, private _eventType: EventType) {}\n\n get source() {\n return this._source;\n }\n\n get eventType() {\n return this._eventType;\n }\n\n public equals(other: EventSourceKey) {\n return this.source === other.source && this.eventType === other.eventType;\n }\n\n public hashCode() {\n let hash = 0;\n hash = 31 * hash + Rdf.hashString(this.source);\n hash = 31 * hash + Rdf.hashString(this.eventType);\n return Rdf.smi(hash);\n }\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\nimport * as React from 'react';\nimport { Modal, Button, ButtonGroup } from 'react-bootstrap';\n\nexport interface Props {\n message: string;\n onHide: () => void;\n onConfirm: (confirm: boolean) => void;\n}\n\n/**\n * Dialog that is shown when user need to confirm navigation from the current page.\n */\nexport class NavigationConfirmationDialog extends React.Component {\n render() {\n const { onHide, message, onConfirm } = this.props;\n const dialog = (\n \n \n Do you want to leave the page?\n \n \n {message}
\n \n \n \n \n \n \n \n \n );\n return dialog;\n }\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as Kefir from 'kefir';\nimport * as maybe from 'data.maybe';\nimport * as Immutable from 'immutable';\nimport * as request from 'platform/api/http';\n\nimport { Rdf, vocabularies } from 'platform/api/rdf';\nimport { trigger } from 'platform/api/events';\nimport { getLabel } from 'platform/api/services/resource-label';\nimport { LdpService, LdpServiceContext } from '../ldp';\nimport { Util as SecurityUtil, UserI } from '../security';\nimport { SetManagementEvents } from './SetManagementEvents';\n\nconst { rdf, rdfs, VocabPlatform, ldp, xsd } = vocabularies;\n\nexport class SetService extends LdpService {\n createSet(name: string, slug = maybe.Nothing()): Kefir.Property {\n const generatedIri = Rdf.iri('');\n const set = Rdf.graph([\n Rdf.triple(generatedIri, rdfs.label, Rdf.literal(name)),\n Rdf.triple(generatedIri, rdf.type, ldp.Container),\n Rdf.triple(generatedIri, rdf.type, VocabPlatform.Set),\n ]);\n return this.addResource(set, slug);\n }\n\n addToExistingSet(setIri: Rdf.Iri, itemIris: Rdf.Iri): Kefir.Property {\n const existingSet = new LdpService(setIri.value, this.context);\n return addSetItem(existingSet, itemIris);\n }\n\n createSetAndAddItems(name: string, listOfItemIris: Immutable.List): Kefir.Property {\n return this.createSet(name)\n .flatMap((setLocation: Rdf.Iri) => {\n const newSet = new LdpService(setLocation.value, this.context);\n return Kefir.zip(listOfItemIris.map((iri, index) => addSetItem(newSet, iri, index)).toJS());\n })\n .toProperty();\n }\n\n reorderItems(setIri: Rdf.Iri, holders: Immutable.List): Kefir.Property {\n const set = new LdpService(setIri.value, this.context);\n return Kefir.zip(\n holders\n .map(({ holder, item }, index) =>\n createItemHolderGraph(holder, item, index).flatMap((graph) => set.update(holder, graph))\n )\n .toArray()\n )\n .map(() => {\n /* nothing */\n })\n .toProperty();\n }\n}\n\nfunction addSetItem(set: LdpService, item: Rdf.Iri, index?: number): Kefir.Property {\n return createItemHolderGraph(Rdf.iri(''), item, index)\n .flatMap((graph) => set.addResource(graph))\n .map((holder) => ({ holder, item }))\n .toProperty();\n}\n\nfunction createItemHolderGraph(holderIri: Rdf.Iri, itemIri: Rdf.Iri, index?: number): Kefir.Property {\n return getLabel(itemIri).map((label) => {\n const triples: Rdf.Triple[] = [\n Rdf.triple(holderIri, VocabPlatform.setItem, itemIri),\n Rdf.triple(holderIri, rdf.type, VocabPlatform.SetItem),\n ];\n triples.push(Rdf.triple(holderIri, rdfs.label, Rdf.literal(label)));\n if (typeof index === 'number') {\n triples.push(Rdf.triple(holderIri, VocabPlatform.setItemIndex, Rdf.literal(index.toString(), xsd.integer)));\n }\n return Rdf.graph(triples);\n });\n}\n\ninterface HolderWithItem {\n holder: Rdf.Iri;\n item: Rdf.Iri;\n}\n\nexport function addToDefaultSet(resource: Rdf.Iri, sourceId: string): Kefir.Property {\n return Kefir.combine([getSetServiceForUser(), Kefir.fromPromise(getUserDefaultSetIri())])\n .flatMap(([service, defaultSetIri]) =>\n service.addToExistingSet(defaultSetIri, resource).map(() => {\n trigger({ eventType: SetManagementEvents.ItemAdded, source: sourceId });\n return resource;\n })\n )\n .toProperty();\n}\n\nexport function getUserSetRootContainerIri(username?: string): Promise {\n return new Promise((resolve, reject) => {\n request\n .get('/rest/sets/getUserSetRootContainerIri')\n .query({ username })\n .type('application/json')\n .accept('text/plain')\n .end((err, res) => {\n if (err) {\n reject(err);\n }\n const iri = res.text;\n if (typeof iri !== 'string') {\n throw new Error(`Invalid user set root container IRI: ${iri}`);\n }\n resolve(Rdf.iri(iri));\n });\n });\n}\n\nexport function getUserDefaultSetIri(username?: string): Promise {\n return new Promise((resolve, reject) => {\n request\n .get('/rest/sets/getUserDefaultSetIri')\n .query({ username })\n .type('application/json')\n .accept('text/plain')\n .end((err, res) => {\n if (err) {\n reject(err);\n }\n const iri = res.text;\n if (typeof iri !== 'string') {\n throw new Error(`Invalid user default set IRI: ${iri}`);\n }\n resolve(Rdf.iri(iri));\n });\n });\n}\n\nclass ContainerOfUserSetContainers extends LdpService {\n constructor(context: LdpServiceContext | undefined) {\n super(VocabPlatform.UserSetContainer.value, context);\n }\n\n getOrCreateSetContainer(setContainerIri: Rdf.Iri): Kefir.Property {\n const setService = new SetService(setContainerIri.value, this.context);\n return this.get(setContainerIri)\n .map((graph) => setService)\n .flatMapErrors(() =>\n Kefir.combine([\n Kefir.fromPromise(SecurityUtil.getUser()),\n Kefir.fromPromise(getUserDefaultSetIri()),\n ]).flatMap(([user, defaultSetIri]) =>\n this.createSetContainerForUser(user, setContainerIri).flatMap(() =>\n setService\n .createSet('Uncategorized', maybe.Just(defaultSetIri.value))\n .flatMapErrors(() => Kefir.constant({}))\n )\n )\n )\n .map(() => setService)\n .toProperty();\n }\n\n createSetContainerForUser(user: UserI, setContainerIri: Rdf.Iri): Kefir.Property {\n const generatedIri = Rdf.iri('');\n const containerName = `Set container of user '${user.principal}'`;\n return this.addResource(\n Rdf.graph([\n Rdf.triple(generatedIri, rdfs.label, Rdf.literal(containerName)),\n Rdf.triple(generatedIri, rdf.type, ldp.Container),\n Rdf.triple(generatedIri, rdf.type, VocabPlatform.SetContainer),\n ]),\n maybe.Just(setContainerIri.value)\n ).map(() => {\n /* nothing */\n });\n }\n}\n\nlet setContainerOfCurrentUser: Kefir.Property = undefined;\n\nexport function getSetServiceForUser(context?: LdpServiceContext): Kefir.Property {\n if (setContainerOfCurrentUser) {\n return setContainerOfCurrentUser;\n }\n\n const container = new ContainerOfUserSetContainers(context);\n setContainerOfCurrentUser = Kefir.fromPromise(getUserSetRootContainerIri())\n .flatMap((setContainerIri) => container.getOrCreateSetContainer(setContainerIri))\n .toProperty();\n return setContainerOfCurrentUser;\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\nimport * as React from 'react';\nimport { isEmpty, trim } from 'lodash';\nimport { FormControl, FormGroup, InputGroup, Button, Form, HelpBlock } from 'react-bootstrap';\n\nimport { Rdf } from 'platform/api/rdf';\nimport { navigateToResource } from 'platform/api/navigation';\n\nexport interface NavigateToIRIProps {\n placeholder?: string;\n buttonCaption?: string;\n}\n\ninterface State {\n value: string;\n error?: boolean;\n}\n\n/**\n * Component that can be used to navigate to the specified IRI resource page.\n */\nexport class NavigateToIRI extends React.Component {\n constructor(props, context) {\n super(props, context);\n this.state = {\n value: '',\n };\n }\n\n static defaultProps = {\n placeholder: 'Enter the full IRI to navigate to the resource page, e.g http://example.org/bob#me',\n buttonCaption: 'Navigate',\n };\n\n render() {\n return (\n \n );\n }\n\n private onValueChange = (event: React.ChangeEvent) => this.setState({ value: trim(event.target.value) });\n\n private isExploreDisabled = () => isEmpty(this.state.value);\n\n private onClick = (event: React.SyntheticEvent) => {\n event.preventDefault();\n navigateToResource(Rdf.iri(this.state.value))\n .onValue((v) => {})\n .onError((e) => this.setState({ error: true }));\n };\n}\n\nexport default NavigateToIRI;\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as Kefir from 'kefir';\nimport * as maybe from 'data.maybe';\nimport * as request from 'platform/api/http';\n\nimport { Rdf, turtle, vocabularies } from 'platform/api/rdf';\nimport * as URI from 'urijs';\nimport * as _ from 'lodash';\n\nconst { VocabPlatform } = vocabularies;\n\ninterface SerializedResource {\n data: string;\n format: string;\n}\n\nexport interface LdpServiceContext {\n readonly repository?: string;\n readonly isDefault?: boolean;\n}\n\nexport class LdpService {\n protected readonly BASE_CONTAINER: string;\n protected readonly context: LdpServiceContext;\n\n constructor(container: string, context?: LdpServiceContext) {\n if (!context || context.isDefault) {\n this.context = { repository: 'assets' };\n } else {\n this.context = context;\n }\n\n if (typeof container !== 'string') {\n throw new Error('Container IRI cannot be null or undefined');\n }\n this.BASE_CONTAINER = container;\n }\n\n protected getServiceUrl(urlSuffix = '', queryParams: { [param: string]: string | string[] } = {}) {\n const endpoint = `/container${urlSuffix}`;\n const urlQuery = URI.buildQuery(_.assign({ repository: this.context.repository }, queryParams));\n return urlQuery ? `${endpoint}?${urlQuery}` : endpoint;\n }\n\n /**\n * TODO move to different place\n * @param {Rdf.Iri} setIri\n * @param {Rdf.Iri} visibilityEnum\n * @return {Kefir.Stream}\n */\n public static setVisibility(setIri: Rdf.Iri, visibilityEnum: Rdf.Iri, groups: Rdf.Iri[]): Kefir.Property<{}> {\n const resource = Rdf.graph([\n Rdf.triple(Rdf.iri(''), VocabPlatform.visibilityItem, setIri),\n Rdf.triple(setIri, VocabPlatform.visibility, visibilityEnum),\n ..._.map(groups, (group) => Rdf.triple(setIri, VocabPlatform.visibleToGroups, group)),\n ]);\n\n return new LdpService(VocabPlatform.VisibilityContainer.value).addResource(resource, maybe.Nothing());\n }\n\n public getContainerIRI = (): Rdf.Iri => {\n return Rdf.iri(this.BASE_CONTAINER);\n };\n\n public getContainer(): Kefir.Property {\n return this.fetchResource(Rdf.iri(this.BASE_CONTAINER));\n }\n\n public get(resourceIri: Rdf.Iri): Kefir.Property {\n return this.fetchResource(resourceIri);\n }\n\n public update(resourceIri: Rdf.Iri, resource: Rdf.Graph): Kefir.Property {\n return turtle.serialize\n .serializeGraph(resource)\n .flatMap((turtle) => this.sendUpdateResourceRequest(resourceIri, { data: turtle, format: 'text/turtle' }))\n .toProperty();\n }\n\n public options(resourceIri: Rdf.Iri): Kefir.Property<{}> {\n const req = request.options(this.getServiceUrl()).query({ uri: resourceIri.value });\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res) => cb(err != null ? err.status : null, res.ok ? res.text : null))\n ).toProperty();\n }\n\n public deleteResource(resourceIri: Rdf.Iri) {\n const req = request.del(this.getServiceUrl()).query({ uri: resourceIri.value });\n return Kefir.fromNodeCallback((cb) => req.end((err, res) => cb(err, res ? res.text : null))).toProperty();\n }\n\n public addResource = (resource: Rdf.Graph, name = maybe.Nothing()): Kefir.Property => {\n return turtle.serialize\n .serializeGraph(resource)\n .flatMap((turtle) =>\n this.createResourceRequest(this.getContainerIRI(), { data: turtle, format: 'text/turtle' }, name)\n )\n .map((location) => Rdf.iri(location))\n .toProperty();\n };\n\n renameResource(resourceIri: Rdf.Iri, newName: string): Kefir.Property {\n const req = request.put(this.getServiceUrl('/rename')).query({ uri: resourceIri.value, newName });\n return Kefir.stream((emitter) => {\n req.end((err, res) => {\n if (err) {\n emitter.error(err);\n } else {\n emitter.emit(undefined);\n }\n emitter.end();\n });\n return () => req.abort();\n }).toProperty();\n }\n\n /**\n * Returns created resource URI.\n */\n public createResourceRequest = (\n containerIri: Rdf.Iri,\n resource: SerializedResource,\n name = maybe.Nothing()\n ): Kefir.Property => {\n let req = request\n .post(this.getServiceUrl())\n .query({ uri: containerIri.value })\n .send(resource.data)\n .type(resource.format);\n req = name.map((slug) => req.set('Slug', slug)).getOrElse(req);\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res) => cb(err, res ? res.header['location'] : null))\n ).toProperty();\n };\n\n /**\n * Copy resource into same or specified container and returns new resource URI\n */\n public copyResource(\n resource: Rdf.Iri,\n targetContainer: Data.Maybe,\n name = maybe.Nothing()\n ): Kefir.Property {\n let req = request.get(this.getServiceUrl('/copyResource'));\n req = targetContainer\n .map((target) => req.query({ source: resource.value, target: target.value }))\n .getOrElse(req.query({ source: resource.value }));\n req = name.map((slug) => req.set('Slug', slug)).getOrElse(req);\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res) => cb(err, res ? res.header['location'] : null))\n ).toProperty();\n }\n\n public sendUpdateResourceRequest(resourceUrl: Rdf.Iri, resource: SerializedResource): Kefir.Property {\n const req = request\n .put(this.getServiceUrl())\n .query({ uri: resourceUrl.value })\n .send(resource.data)\n .type(resource.format);\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res) => {\n cb(err, res ? Rdf.iri(res.header['location']) : null);\n })\n ).toProperty();\n }\n\n protected fetchResource(iri: Rdf.Iri): Kefir.Property {\n return this.getResourceTriples(iri.value, 'text/turtle');\n }\n\n protected getResourceTriples(resourceUrl: string, format: string): Kefir.Property {\n return this.getResourceRequest(resourceUrl, format)\n .flatMap((res) => {\n return turtle.deserialize.turtleToGraph(res);\n })\n .toProperty();\n }\n\n public getResourceRequest(resourceUrl: string, format: string): Kefir.Property {\n const req = request.get(this.getServiceUrl()).query({ uri: resourceUrl }).accept(format);\n return Kefir.fromNodeCallback(req.end.bind(req))\n .toProperty()\n .map((res) => res.text);\n }\n\n public getExportURL(iris: string[]): string {\n return this.getServiceUrl('/exportResource', { iris: iris });\n }\n\n public getImportURL(): string {\n return this.getServiceUrl('/importResource');\n }\n\n public importGetTextFromURL(url: string): Kefir.Property {\n const req = request.get(url);\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res) => {\n cb(err, res && res.ok ? res.text : null);\n })\n ).toProperty();\n }\n\n public importFromText(text: string, containerIRI?: string, force?: boolean): Kefir.Property {\n let req = request.post(this.getImportURL()).send(new File([text], 'import.ttl'));\n if (force) {\n req = req.query({ force });\n }\n if (containerIRI) {\n req = req.query({ containerIRI });\n }\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res) => {\n cb(err, res);\n })\n ).toProperty();\n }\n\n public importFromDelayedId(delayedId: string, containerIRI: string): Kefir.Property {\n const req = request\n .post(this.getImportURL())\n // .type('form')\n .query({ force: true, delayedId, containerIRI });\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res) => {\n cb(err, res);\n })\n ).toProperty();\n }\n}\n\nexport function ldpc(baseUrl: string) {\n return new LdpService(baseUrl);\n}\n\nexport function slugFromName(name: String) {\n return name\n .toLowerCase()\n .trim()\n .replace(/[^a-zA-Z0-9~_-]+/g, '-');\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as ModuleRegistry from './Registry';\nimport * as ReactErrorCatcher from './ReactErrorCatcher';\nimport * as ComponentsLoader from './ComponentsLoader';\n\nexport { ComponentClassMetadata } from './ComponentsStore';\nexport { ModuleRegistry, ReactErrorCatcher, ComponentsLoader };\n\nexport { ExtensionPoint } from './ExtensionPoint';\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as request from 'platform/api/http';\nimport * as _ from 'lodash';\nimport * as Kefir from 'kefir';\nimport * as Immutable from 'immutable';\n\nimport { BatchedPool, requestAsProperty } from 'platform/api/async';\nimport { Rdf } from 'platform/api/rdf';\n\nexport interface SecurityUtilI {\n isPermitted(permissionString: string): Kefir.Property;\n getUser(callback?: (user: UserI) => void): Promise;\n isAnonymous(callback: (whetherIsAnonymous: boolean) => void): void;\n}\n\nexport namespace Permissions {\n export const templateSave = 'pages:edit:save';\n\n const sparqlQueryEditor = 'ui:component:view:mp:sparql:query:editor';\n /** Dropdown to select other endpoints for debugging purpose. */\n export const queryEditorSelectEndpoint = `${sparqlQueryEditor}:select:repository`;\n\n /**\n * Permission for page explore actions - Ontodia, Graph View, Table View.\n */\n export const pageToolbarExplore = 'ui:page:view:toolbar:explore';\n\n export const sparqlSelect = 'sparql:query:select';\n export const qaasInfo = 'qaas:info';\n\n /**\n * Constructs permission string to perform specified action on an LDP resource.\n * (Based on Permissions.java)\n */\n export function toLdp(\n base: 'container' | 'type',\n resource: Rdf.Iri,\n action: 'read' | 'create' | 'update' | 'delete' | 'export' | 'import',\n ownership: 'any' | 'owner'\n ) {\n return `api:ldp:${base}:<${resource.value}>:${action}:${ownership}`;\n }\n}\n\nexport interface PermissionDocumentation {\n readonly acl: string;\n readonly description: string;\n readonly example: string;\n}\n\nexport interface UserI {\n principal?: string;\n isAuthenticated: boolean;\n isAnonymous: boolean;\n userURI: string;\n}\n\nexport interface SessionInfoI {\n lastAccessTimestamp: number;\n timout: number;\n idleTime: number;\n}\n\nexport interface Account {\n principal: string;\n password: string;\n roles: string;\n permissions?: string;\n}\n\nexport interface RoleDefinition {\n roleName: string;\n permissions: Array;\n}\n\nexport class NotEnoughPermissionsError extends Error {\n __proto__: Error;\n constructor(message) {\n const trueProto = new.target.prototype;\n super(message);\n this.__proto__ = trueProto;\n }\n}\n\nexport class SecurityUtil implements SecurityUtilI {\n private pool = new BatchedPool({ fetch: (perms) => this.fetchPermitted(perms.toArray()) });\n\n public getUser(cb?: (user: UserI) => void): Promise {\n if (cb) {\n this.getUser().then(cb);\n return;\n }\n\n // TODO cache\n const WINDOW_user = 'cache_user';\n if (!_.isUndefined(window[WINDOW_user])) {\n return Promise.resolve(window[WINDOW_user]);\n }\n\n return new Promise((resolve, reject) => {\n request\n .get('/rest/security/user')\n .type('application/json')\n .accept('application/json')\n .end((err, res) => {\n if (err) {\n reject(err);\n } else {\n const user = JSON.parse(res.text);\n window[WINDOW_user] = user;\n resolve(user);\n }\n });\n });\n }\n\n public isPermitted(permissionString: string): Kefir.Property {\n return this.pool.query(permissionString);\n }\n\n private fetchPermitted(permissionStrings: string[]): Kefir.Property> {\n const req = request\n .post('/rest/security/permissions')\n .send(permissionStrings)\n .type('application/json')\n .accept('application/json');\n return Kefir.fromNodeCallback<{ [permission: string]: boolean }>((cb) => req.end((err, res) => cb(err, res.body)))\n .toProperty()\n .map((batch) => Immutable.Map(batch));\n }\n\n /**\n * Checks with backend whether the current logged-in subject is a anonymous user.\n * Value is cached in current window scope i.e. will be refreshed only if\n * user opens new tab or tab is reloaded e.g. due to login/logout.\n * @param {Function} cb callback\n */\n public isAnonymous(cb): void {\n // TODO cache\n const WINDOW_isAnonymousUser = 'cache_isAnonymousUser';\n if (!_.isUndefined(window[WINDOW_isAnonymousUser])) {\n cb(window[WINDOW_isAnonymousUser]);\n return;\n }\n this.getUser((userObject) => {\n window[WINDOW_isAnonymousUser] = (userObject).isAnonymous;\n cb((userObject).isAnonymous);\n });\n }\n\n public getSessionInfo(cb) {\n return request\n .get('/rest/security/getSessionInfo')\n .type('application/json')\n .accept('application/json')\n .end((err, res) => {\n cb(JSON.parse(res.text));\n });\n }\n\n public touchSession(cb) {\n return request.post('/rest/security/touchSession').end((err, res: request.Response) => {\n cb(res.status);\n });\n }\n\n public getAllAccounts(): Kefir.Property {\n const req = request.get('/rest/security/getAllAccounts').type('application/json').accept('application/json');\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(err != null ? err.message : null, res.ok ? JSON.parse(res.text) : null);\n })\n ).toProperty();\n }\n\n getDocumentationForAllPermissions(): Kefir.Property {\n const req = request.get('/rest/security/getAllPermissionsDoc').type('application/json').accept('application/json');\n return requestAsProperty(req).map((res) => res.body);\n }\n\n public createAccount(account: Account): Kefir.Property {\n const req = request.post('/rest/security/createAccount').send(account).type('application/json');\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(err != null ? err.response.text : null, res.ok ? true : null);\n })\n ).toProperty();\n }\n\n public updateAccount(account: Account): Kefir.Property {\n const req = request.put('/rest/security/updateAccount').send(account).type('application/json');\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(err != null ? err.response.text : null, res.ok ? true : null);\n })\n ).toProperty();\n }\n\n public deleteAccount(account: Account): Kefir.Property {\n const req = request.del('/rest/security/deleteAccount/' + account.principal);\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(err != null ? err.response.text : null, res.ok ? true : null);\n })\n ).toProperty();\n }\n\n public getRoleDefinitions(): Kefir.Property {\n const req = request.get('/rest/security/getAllRoleDefinitions').type('application/json').accept('application/json');\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(err != null ? err : null, res.ok ? JSON.parse(res.text) : null);\n })\n ).toProperty();\n }\n\n isPermissionValid(permission: string): Kefir.Property {\n const req = request\n .put('/rest/security/isPermissionValid')\n .send(permission)\n .type('application/json')\n .accept('application/json');\n\n return requestAsProperty(req).map((res) => res.body);\n }\n\n updateRoleDefinitions(roles: RoleDefinition[]): Kefir.Property {\n const req = request.put('/rest/security/updateRoleDefinitions').send(roles).type('application/json');\n\n return requestAsProperty(req).map((res) => res.ok);\n }\n}\n\nexport const Util = new SecurityUtil();\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport { post, del, Response } from 'superagent';\nimport * as Kefir from 'kefir';\nimport * as URI from 'urijs';\n\nimport { LdpService } from 'platform/api/services/ldp';\nimport { Rdf, vocabularies } from 'platform/api/rdf';\nimport { requestAsProperty } from 'platform/api/async';\nimport { modeLabel } from 'platform/components/documentation/CodeBlock.scss';\n\nexport const FILE_UPLOAD_SERVICE_URL = '/file';\nexport const FILE_LDP_CONTAINER_ID = 'http://www.researchspace.org/resource/system/fileContainer';\n\nexport const FILE_URL = '';\nexport const ADMIN_URL = '/direct';\nexport const TEMPORARY_STORAGE_URL = '/temporary';\nexport const MOVE_URL = '/move';\n\nconst { VocabPlatform } = vocabularies;\n\nexport const RESOURCE_QUERY = `\n CONSTRUCT {\n ?__resourceIri__ a <${VocabPlatform.File.value}>.\n ?__resourceIri__ <${VocabPlatform.fileName.value}> ?__fileName__.\n ?__resourceIri__ <${VocabPlatform.mediaType.value}> ?__mediaType__.\n ?__resourceIri__ <${VocabPlatform.fileContext.value}> ?__contextUri__.\n } WHERE {}\n`;\n\nexport interface FileResource {\n iri: Rdf.Iri;\n fileName: string;\n mediaType: string;\n}\n\nexport class FileManager {\n private readonly repository: string;\n private readonly ldp: LdpService;\n\n constructor(options: { repository: string }) {\n const { repository } = options;\n this.repository = repository;\n this.ldp = new LdpService(FILE_LDP_CONTAINER_ID, { repository });\n }\n\n /**\n * @returns file resource IRI\n */\n uploadFileAsResource(options: {\n file: File;\n storage: string;\n contextUri?: string;\n generateIriQuery?: string;\n resourceQuery?: string;\n onProgress?: (percent: number) => void;\n fileNameHack?: boolean;\n }): Kefir.Property {\n if (!options.storage) {\n return Kefir.constantError(new Error('Storage is undefined!'));\n }\n\n const request = post(FILE_UPLOAD_SERVICE_URL + FILE_URL)\n .attach('file', options.file as any)\n .field('fileSize', `${options.file.size}`)\n .field('storage', options.storage)\n .field('repository', this.repository)\n .field('createResourceQuery', options.resourceQuery || RESOURCE_QUERY)\n .field('generateIriQuery', options.generateIriQuery || '')\n .field('contextUri', options.contextUri || '')\n .field('fileNameHack', options.fileNameHack ? 'true' : '')\n .on('progress', (e) => {\n if (options.onProgress) {\n options.onProgress(e.percent as number);\n }\n });\n\n return requestAsProperty(request).map((response) => {\n const fileIri = Rdf.iri(response.header.location);\n return fileIri;\n });\n }\n\n /**\n * @returns object ID of the uploaded file, including object kind prefix \"file/\"\n */\n uploadFileDirectlyToStorage(options: {\n file: File;\n storage: string;\n folder: string;\n fileName?: string;\n onProgress?: (percent: number) => void;\n }): Kefir.Property {\n if (!options.storage) {\n return Kefir.constantError(new Error('Storage is undefined!'));\n }\n if (!options.folder) {\n return Kefir.constantError(new Error('Path is undefined!'));\n }\n\n const request = post(FILE_UPLOAD_SERVICE_URL + ADMIN_URL)\n .attach('file', options.file as any)\n .field('fileSize', `${options.file.size}`)\n .field('storage', options.storage)\n .field('folder', options.folder)\n .field('fileName', options.fileName || '')\n .on('progress', (e) => {\n if (options.onProgress) {\n options.onProgress(e.percent);\n }\n });\n\n return requestAsProperty(request).map((response) => {\n return response.ok ? response.text : null;\n });\n }\n\n /**\n * Uploads file to temporary storage and returns its description.\n *\n * @returns file resource IRI\n */\n public uploadFileTemporary(options: {\n file: File;\n storage: string;\n onProgress?: (percent: number) => void;\n }): Kefir.Property {\n if (!options.storage) {\n return Kefir.constantError(new Error('Storage is undefined!'));\n }\n\n const request = post(FILE_UPLOAD_SERVICE_URL + TEMPORARY_STORAGE_URL)\n .attach('file', options.file as any)\n .field('fileSize', `${options.file.size}`)\n .field('storage', options.storage)\n .on('progress', (e) => {\n if (options.onProgress) {\n options.onProgress(e.percent);\n }\n });\n\n return requestAsProperty(request).map((response) =>\n TemporaryFileSchema.encodeAsIri(response.text, options.file.type)\n );\n }\n\n /**\n * @returns file resource IRI\n */\n createResourceFromTemporaryFile(options: {\n fileName: string;\n storage: string;\n temporaryStorage: string;\n contextUri?: string;\n mediaType: string;\n generateIriQuery?: string;\n resourceQuery?: string;\n onProgress?: (percent: number) => void;\n }): Kefir.Property {\n const request = post(FILE_UPLOAD_SERVICE_URL + MOVE_URL)\n .field('fileName', options.fileName)\n .field('storage', options.storage)\n .field('temporaryStorage', options.temporaryStorage)\n .field('repository', this.repository)\n .field('createResourceQuery', options.resourceQuery || RESOURCE_QUERY)\n .field('mediaType', options.mediaType)\n .field('generateIriQuery', options.generateIriQuery || '')\n .field('contextUri', options.contextUri || '')\n .on('progress', (e) => {\n if (options.onProgress) {\n options.onProgress(e.percent);\n }\n });\n\n return requestAsProperty(request).map((response) => {\n const fileIri = Rdf.iri(response.header.location);\n return fileIri;\n });\n }\n\n deleteFileResource(\n resourceIri: Rdf.Iri,\n storage: string,\n options?: {\n namePredicateIri?: string;\n mediaTypePredicateIri?: string;\n },\n ): Kefir.Property {\n return this.getFileResource(resourceIri, options)\n .flatMap((resource) => {\n const request = del(FILE_UPLOAD_SERVICE_URL + FILE_URL)\n .field('fileName', resource.fileName)\n .field('storage', storage)\n .field('repository', this.repository)\n .field('resourceIri', resource.iri.value);\n\n return requestAsProperty(request);\n })\n .toProperty();\n }\n\n removeTemporaryResource(resourceIri: Rdf.Iri, storage: string): Kefir.Property {\n const { fileName } = TemporaryFileSchema.decodeFromIri(resourceIri);\n const request = del(FILE_UPLOAD_SERVICE_URL + TEMPORARY_STORAGE_URL)\n .field('fileName', fileName)\n .field('storage', storage);\n\n return requestAsProperty(request);\n }\n\n getFileResourceGraph(resourceIri: Rdf.Iri): Kefir.Property {\n return this.ldp.get(resourceIri);\n }\n\n getFileResource(\n resourceIri: Rdf.Iri,\n options?: {\n namePredicateIri?: string;\n mediaTypePredicateIri?: string;\n }\n ): Kefir.Property {\n if (TemporaryFileSchema.isEncodedIri(resourceIri)) {\n const { fileName, mediaType } = TemporaryFileSchema.decodeFromIri(resourceIri);\n return Kefir.constant({ iri: resourceIri, fileName, mediaType });\n } else {\n options = options || {};\n const namePredicateIri = options.namePredicateIri || VocabPlatform.fileName.value;\n const mediaTypePredicateIri = options.mediaTypePredicateIri || VocabPlatform.mediaType.value;\n \n return this.getFileResourceGraph(resourceIri)\n .flatMap((graph) => {\n const triples = graph.triples;\n const resource: FileResource = {\n iri: resourceIri,\n fileName: triples.find((tripple) => {\n return tripple.p.value === namePredicateIri;\n }).o.value,\n mediaType: triples.find((tripple) => {\n return tripple.p.value === mediaTypePredicateIri;\n }).o.value,\n };\n\n if (resource.fileName && resource.mediaType) {\n return Kefir.constant(resource);\n } else {\n return Kefir.constantError(\n new Error(`Either 'fileName' or 'mediaType' properties not found in the file resource graph`)\n );\n }\n })\n .toProperty();\n }\n }\n\n static getFileUrl(fileName: string, storage: string, mode?: string, mediaType?:string): string {\n return new URI(FILE_UPLOAD_SERVICE_URL)\n .addQuery({\n fileName: fileName,\n storage: storage,\n mode: mode,\n mediaType: mediaType,\n })\n .toString();\n }\n\n static isTemporaryResource(resourceIri: Rdf.Iri): boolean {\n return TemporaryFileSchema.isEncodedIri(resourceIri);\n }\n}\n\n// These functions are used by FileInput and FileVisualizer component\n// to store and visualize temporary state of FileInput component\nfunction createTemporaryResource(fileName: string, mediaType: string): FileResource {\n return {\n iri: TemporaryFileSchema.encodeAsIri(fileName, mediaType),\n fileName,\n mediaType,\n };\n}\n\n/**\n * Represents temporary file description into an IRI with custom schema.\n * This IRIs are natively supported by FileInput and FileVizualizer components,\n * but should not be persisted anywhere and only stored in-memory.\n */\nnamespace TemporaryFileSchema {\n const SCHEMA_PREFIX = 'mp-temporary-file:';\n\n export function isEncodedIri(iri: Rdf.Iri): boolean {\n return iri.value.startsWith(SCHEMA_PREFIX);\n }\n\n export function encodeAsIri(fileName: string, mediaType: string): Rdf.Iri {\n const encodedMediaType = encodeURIComponent(mediaType);\n const encodedName = encodeURIComponent(fileName);\n return Rdf.iri(`${SCHEMA_PREFIX}${encodedMediaType}/${encodedName}`);\n }\n\n export function decodeFromIri(encoded: Rdf.Iri): { mediaType: string; fileName: string } {\n if (!isEncodedIri(encoded)) {\n throw new Error(`IRI is not an encoded temporary file: ${encoded}`);\n }\n const bufferString = encoded.value.substring(SCHEMA_PREFIX.length);\n const [encodedMediaType, encodedName] = bufferString.split('/');\n if (!(encodedMediaType && encodedName)) {\n throw new Error(`Failed to decode temporary file IRI: ${encoded}`);\n }\n return {\n mediaType: decodeURIComponent(encodedMediaType),\n fileName: decodeURIComponent(encodedName),\n };\n }\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nexport * from './ldp-set';\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as maybe from 'data.maybe';\nimport * as Kefir from 'kefir';\nimport { includes } from 'lodash';\nimport * as SparqlJs from 'sparqljs';\n\nimport { Rdf, vocabularies } from 'platform/api/rdf';\nimport { LdpService, LdpServiceContext } from './ldp';\n\nconst { VocabPlatform, rdf, rdfs, sp } = vocabularies;\n\nexport type OperationType = SparqlJs.Query['queryType'] | 'UPDATE';\n\nexport interface Query {\n label: string;\n value: string;\n type: SparqlJs.SparqlQuery['type'] | '';\n queryType: OperationType | '';\n structure?: string;\n}\n\nexport class QueryServiceClass extends LdpService {\n public addItem(query: Query): Kefir.Property {\n const graph = this.createGraph(query);\n const label = query.label;\n\n return this.addResource(graph, maybe.Just(label));\n }\n\n public updateItem(iri: Rdf.Iri, query: Query): Kefir.Property<{}> {\n const graph = this.createGraph(query);\n\n return this.update(iri, graph);\n }\n\n private createGraph(query: Query): Rdf.Graph {\n const { label, value, type, queryType } = query;\n const queryClass = type === 'update' ? sp.Update : sp.Query;\n const triples = [\n Rdf.triple(Rdf.iri(''), rdf.type, queryClass),\n Rdf.triple(Rdf.iri(''), rdf.type, this.getType(type, queryType)),\n Rdf.triple(Rdf.iri(''), rdfs.label, Rdf.literal(label)),\n Rdf.triple(Rdf.iri(''), sp.text, value !== undefined ? Rdf.literal(value) : Rdf.literal('')),\n ];\n\n if (query.structure) {\n triples.push(Rdf.triple(Rdf.iri(''), vocabularies.VocabPlatform.searchState, Rdf.literal(query.structure)));\n }\n\n return Rdf.graph(triples);\n }\n\n private getType(type: string, queryType: string): Rdf.Iri {\n if (type !== 'update') {\n switch (queryType) {\n case 'ASK':\n return sp.Ask;\n case 'SELECT':\n return sp.Select;\n case 'DESCRIBE':\n return sp.Describe;\n case 'CONSTRUCT':\n return sp.Construct;\n default:\n return sp.Select;\n }\n } else {\n return sp.Update;\n }\n }\n\n public getQuery(iri: Rdf.Iri): Kefir.Property {\n return this.get(iri).map((graph) => this.parseGraphToQuery(iri, graph));\n }\n\n private parseGraphToQuery(iri: Rdf.Iri, graph: Rdf.Graph): Query {\n const queryTypes = [sp.Ask, sp.Select, sp.Describe, sp.Construct, sp.Update].map((qt) => qt.value);\n const label = graph.triples.find((t) => t.s.equals(iri) && t.p.equals(rdfs.label)).o.value;\n const value = graph.triples.find((t) => t.s.equals(iri) && t.p.equals(sp.text)).o.value;\n const structure = graph.triples.find((t) => t.s.equals(iri) && t.p.equals(vocabularies.VocabPlatform.searchState));\n const sTypeIRI = graph.triples.find(\n // there are several types, filter only those which are relevant\n (t) => t.s.equals(iri) && t.p.equals(rdf.type) && includes(queryTypes, t.o.value)\n ).o.value;\n\n const queryType = this.extractTypeFromIri(sTypeIRI).toUpperCase() as OperationType;\n\n return {\n label,\n value,\n type: queryType === 'UPDATE' ? 'update' : 'query',\n queryType: queryType,\n structure: structure ? structure.o.value : undefined,\n };\n }\n\n /**\n * Return substring after last '#'\n */\n private extractTypeFromIri(sTypeIRI: string): string {\n return /[^#]*$/.exec(sTypeIRI)[0];\n }\n}\n\nexport const QueryService = function (context?: LdpServiceContext) {\n return new QueryServiceClass(VocabPlatform.QueryContainer.value, context);\n};\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as Kefir from 'kefir';\nimport * as request from 'platform/api/http';\nimport * as Immutable from 'immutable';\nimport * as Maybe from 'data.maybe';\nimport * as _ from 'lodash';\n\nimport { Rdf } from 'platform/api/rdf';\nimport { SparqlClient, SparqlUtil } from 'platform/api/sparql';\nimport { requestAsProperty } from 'platform/api/async';\n\nconst ENDPOINT = '/rest/repositories';\n\nfunction composeErrorMessage(err): string {\n return err.message + '. ' + err.response.text;\n}\n\nexport const SparqlRepositoryType = 'researchspace:SPARQLRepository';\nexport const NeptuneRepositoryType = 'researchspace:NeptuneSPARQLRepository';\nexport const Rdf4jRepositoryType = 'openrdf:SailRepository';\nexport const EphedraRepositoryType = 'researchspace:FederationSailRepository';\nexport type RepositoryType =\n | typeof SparqlRepositoryType\n | typeof NeptuneRepositoryType\n | typeof Rdf4jRepositoryType\n | typeof EphedraRepositoryType;\n\nexport interface RepositoryInfo {\n id: string;\n description: string;\n type: RepositoryType;\n}\n\nexport function getRepositoryConfig(id: string): Kefir.Property {\n const req = request.get(`${ENDPOINT}/config/${id}`).accept('text/turtle');\n return fromRequest(req, (err, res) => res.text);\n}\n\nexport function deleteRepositoryConfig(id: string): Kefir.Property {\n const req = request.delete(`${ENDPOINT}/config/${id}`);\n return fromRequest(req, (err, res) => res.text);\n}\n\nexport function getRepositoryInfo(id: string): Kefir.Property {\n const req = request.get(`${ENDPOINT}/info/${id}`).accept('application/json');\n return fromRequest(req, (err, res) => res.body);\n}\n\nexport function validateDefault(): Kefir.Property {\n const req = request.get(`${ENDPOINT}/validatedefault`).accept('application/json');\n return fromRequest(req, (err, res) => (err ? false : res.body.valid));\n}\n\nexport function getRepositoryConfigTemplate(id: string): Kefir.Property {\n const req = request.get(`${ENDPOINT}/templates/${id}`).accept('text/turtle');\n return fromRequest(req, (err, res) => res.text);\n}\n\nexport function updateOrAddRepositoryConfig(id: string, turtle: string, validate = false): Kefir.Property {\n const req = request.post(`${ENDPOINT}/config/${id}`).send(turtle).query({ validate }).type('text/turtle');\n return fromRequest(req, (err, res) => res.text);\n}\n\nexport function getRepositoryConfigTemplates(): Kefir.Property {\n const req = request.get(`${ENDPOINT}/templates`).type('application/json').accept('application/json');\n return fromRequest(req, (err, res) => res.body);\n}\n\nexport function getRepositoryStatus(): Kefir.Property> {\n const req = request.get(ENDPOINT).type('application/json').accept('application/json');\n return fromRequest(req, (err, res) => res.body).map((status) => Immutable.Map(status));\n}\n\nlet repositories: Kefir.Property>;\nexport function guessResourceRepository(resource: Rdf.Iri): Kefir.Property> {\n return repositories\n .flatMap((rs) =>\n Kefir.combine(rs.map((r) => executeGuessQuery(r, resource).map((resp) => [r, resp] as [string, boolean])))\n )\n .map((responses) => Maybe.fromNullable(_.find(responses, ([_, resp]) => resp)).map(([repo, _]) => repo))\n .toProperty();\n}\n\nconst GUESS_QUERY = SparqlUtil.Sparql`ASK { ?__value__ a ?type }`;\nfunction executeGuessQuery(repository: string, resource: Rdf.Iri): Kefir.Property {\n return SparqlClient.ask(SparqlClient.setBindings(GUESS_QUERY, { __value__: resource }), {\n context: { repository: repository },\n });\n}\n\nfunction fromRequest(\n request: request.SuperAgentRequest,\n getValue: (err: any, res: request.Response) => T\n): Kefir.Property {\n return Kefir.fromNodeCallback((cb) =>\n request.end((err, res) => {\n cb(err ? composeErrorMessage(err) : null, getValue(err, res));\n })\n ).toProperty();\n}\n\nclass DefaultRepositoryInfoClass {\n private isValid: boolean;\n\n public init = () => {\n repositories = getRepositoryStatus().map((rs) => rs.keySeq().toArray());\n return validateDefault().onValue((v) => (this.isValid = v));\n };\n\n public isValidDefault = () => {\n return this.isValid;\n };\n}\n\nexport const DefaultRepositoryInfo = new DefaultRepositoryInfoClass();\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as JsonLd from 'jsonld';\nimport * as Kefir from 'kefir';\nimport * as N3 from 'n3';\n\nimport * as Rdf from '../core/Rdf';\n\nregisterTtlParser();\nregisterGraphParser();\n\nexport interface LoaderOptions {\n /** @default false */\n fetchRemoteContexts?: boolean;\n overrideContexts?: {\n [contextIri: string]: object;\n };\n}\n\nconst NODE_DOCUMENT_LOADER = JsonLd.documentLoaders.node();\n\nexport function makeDocumentLoader(options: LoaderOptions): JsonLd.DocumentLoader {\n return (url, callback) => {\n if (options.overrideContexts && url in options.overrideContexts) {\n return callback(null, {\n // this is for a context via a link header\n contextUrl: null,\n // this is the actual document that was loaded\n document: options.overrideContexts[url],\n // this is the actual context URL after redirects\n documentUrl: url,\n });\n }\n\n if (options.fetchRemoteContexts) {\n return NODE_DOCUMENT_LOADER(url, callback);\n } else {\n callback(new Error(`Fetching remote JSON-LD contexts is not allowed`), null);\n }\n };\n}\n\nexport function compact(\n input: object,\n ctx: object | string,\n options: JsonLd.CompactOptions & { documentLoader: JsonLd.DocumentLoader }\n): Kefir.Property {\n return Kefir.fromNodeCallback((callback) => JsonLd.compact(input, ctx, options, callback)).toProperty();\n}\n\nexport function frame(\n input: object | string,\n frame: object,\n options: JsonLd.FrameOptions & { documentLoader: JsonLd.DocumentLoader }\n): Kefir.Property {\n return Kefir.fromNodeCallback((callback) => JsonLd.frame(input, frame, options, callback)).toProperty();\n}\n\nexport function fromRdf(\n dataset: object | string,\n options: JsonLd.FromRdfOptions & { documentLoader: JsonLd.DocumentLoader }\n): Kefir.Property {\n return Kefir.fromNodeCallback((callback) => JsonLd.fromRDF(dataset, options, callback)).toProperty();\n}\n\nfunction registerTtlParser() {\n JsonLd.registerRDFParser('text/turtle', (input, callback) => {\n const quads: JsonLd.Quad[] = [];\n N3.Parser().parse(input, (error, triple, hash) => {\n if (error) {\n callback(error, quads);\n } else if (triple) {\n const quad = createJsonLdQuad(triple);\n quads.push(quad);\n } else if (callback) {\n callback(undefined, quads);\n }\n });\n });\n}\n\n/**\n * Registers parser from Rdf.Graph to json-ld with 'mph/graph' MIME-type.\n */\nfunction registerGraphParser() {\n JsonLd.registerRDFParser('mph/graph', (input, callback) => {\n const inputGraph = input as Rdf.Graph;\n const quads = inputGraph.triples.forEach((triple) => ({\n subject: getTerm(triple.s),\n predicate: getTerm(triple.p),\n object: getTerm(triple.o),\n graph: {\n termType: 'DefaultGraph',\n value: '',\n },\n }));\n function getTerm(term: Rdf.Node): JsonLd.Term {\n if (term.isLiteral()) {\n return {\n termType: 'Literal',\n value: term.value,\n language: term.language,\n datatype: {\n termType: 'NamedNode',\n value: term.datatype.value,\n },\n };\n } else if (term.isBnode()) {\n return {\n termType: 'BlankNode',\n value: term.value,\n };\n } else if (term.isIri()) {\n return {\n termType: 'NamedNode',\n value: term.value,\n };\n }\n }\n return quads;\n });\n}\n\nfunction createJsonLdQuad(triple: N3.Triple): JsonLd.Quad {\n return {\n subject: getTerm(triple.subject),\n predicate: getTerm(triple.predicate),\n object: getTerm(triple.object),\n graph: {\n termType: 'DefaultGraph',\n value: '',\n },\n };\n\n function getTerm(value: string): JsonLd.Term {\n if (N3.Util.isLiteral(value)) {\n return getLiteralTerm(value);\n } else if (N3.Util.isBlank(value)) {\n return {\n termType: 'BlankNode',\n value: value,\n };\n } else {\n return {\n termType: 'NamedNode',\n value: value,\n };\n }\n }\n\n function getLiteralTerm(literal: string): JsonLd.Term {\n return {\n termType: 'Literal',\n value: N3.Util.getLiteralValue(literal),\n language: N3.Util.getLiteralLanguage(literal),\n datatype: {\n termType: 'NamedNode',\n value: N3.Util.getLiteralType(literal),\n },\n };\n }\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport { Rdf } from 'platform/api/rdf';\nimport { QueryContext } from 'platform/api/sparql';\n\nimport { BaseResourceService } from './BaseResourceService';\n\nconst THUMBNAIL_SERVICE_URL = `/rest/data/rdf/utils/thumbnails/default`;\nconst service = new BaseResourceService(THUMBNAIL_SERVICE_URL);\n\nexport function getThumbnail(iri: Rdf.Iri, options?: { context?: QueryContext }) {\n return service.getResource(iri, options ? options.context : undefined);\n}\n\nexport function getThumbnails(iris: ReadonlyArray, options?: { context?: QueryContext }) {\n return service.getResources(iris, options ? options.context : undefined);\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as _ from 'lodash';\nimport { Just, Nothing } from 'data.maybe';\nimport * as Kefir from 'kefir';\n\nimport { requestAsProperty } from 'platform/api/async';\nimport * as request from 'platform/api/http';\nimport { Rdf, vocabularies } from 'platform/api/rdf';\n\nimport { LdpService } from 'platform/api/services/ldp';\nimport { getLabels } from 'platform/api/services/resource-label';\n\nconst { sp, field, rdfs, VocabPlatform } = vocabularies;\n\nimport {\n FieldDefinition,\n FieldDefinitionProp,\n normalizeFieldDefinition,\n} from 'platform/components/forms/FieldDefinition';\n\nexport function getFieldDefinitionProp(fieldIri: Rdf.Iri): Kefir.Property {\n const ldp = new LdpService(vocabularies.VocabPlatform.FieldDefinitionContainer.value);\n return ldp.get(fieldIri).map((graph) => deserialize(fieldIri, graph));\n}\n\nexport function getFieldDefinition(fieldIri: Rdf.Iri): Kefir.Property {\n return getFieldDefinitionProp(fieldIri).map(normalizeFieldDefinition);\n}\n\nfunction deserialize(fieldIri: Rdf.Iri, graph: Rdf.Graph): FieldDefinitionProp {\n const predicates = {\n description: [rdfs.comment],\n xsdDatatype: [field.xsd_datatype],\n minOccurs: [field.min_occurs],\n maxOccurs: [field.max_occurs],\n order: [field.order],\n selectPattern: [field.select_pattern, sp.text],\n deletePattern: [field.delete_pattern, sp.text],\n askPattern: [field.ask_pattern, sp.text],\n valueSetPattern: [field.valueset_pattern, sp.text],\n autosuggestionPattern: [field.autosuggestion_pattern, sp.text],\n testSubject: [field.testsubject],\n insertPattern: [field.insert_pattern, sp.text],\n };\n\n const pg = Rdf.pg(fieldIri, graph);\n // TODO can we iterate over object values and don't loose type information for keys here?\n // after this transformation we get {[key: string]: string} which is not perfect\n const partialField = _.mapValues(predicates, (propertyPath) =>\n Rdf.getValueFromPropertyPath(propertyPath, pg)\n .map((n) => n.value)\n .getOrElse(undefined)\n );\n\n const label = Rdf.getValuesFromPropertyPath([rdfs.label], pg).map((v) => v as Rdf.Literal);\n const domain = Rdf.getValuesFromPropertyPath([field.domain], pg).map((v) => v.value);\n const range = Rdf.getValuesFromPropertyPath([field.range], pg).map((v) => v.value);\n const defaultValues = Rdf.getValuesFromPropertyPath([field.default_value], pg).map((v) => v.value);\n const categories = Rdf.getValuesFromPropertyPath([field.category], pg);\n const treePatterns = Rdf.getValueFromPropertyPath([field.tree_patterns], pg)\n .chain((config) => {\n if (!(config.isLiteral() && config.datatype.equals(VocabPlatform.SyntheticJsonDatatype))) {\n return Nothing();\n }\n try {\n return Just(JSON.parse(config.value));\n } catch (e) {\n return Nothing();\n }\n })\n .getOrElse(undefined);\n\n return {\n id: fieldIri.value,\n label,\n ...partialField,\n categories,\n domain,\n range,\n defaultValues,\n treePatterns,\n } as FieldDefinitionProp;\n}\n\nconst FIELDS_REST_PATH = '/rest/fields/definitions';\n\nexport function getGeneratedFieldDefinitions(iris: ReadonlyArray): Kefir.Property {\n if (iris.length === 0) {\n return Kefir.constant([]);\n }\n const req = request\n .post(FIELDS_REST_PATH)\n .send({\n fields: iris.map((iri) => iri.value),\n })\n .type('application/json')\n .accept('application/json');\n\n return requestAsProperty(req)\n .map((res) => JSON.parse(res.text) as FieldDefinitionProp[])\n .flatMap((fields) => {\n return getLabels(\n fields.filter((f) => f.label === undefined || f.label === null).map((f) => Rdf.iri(f.iri)),\n { context: {} }\n ).map((labels) => {\n return fields.map((f) => ({\n ...f,\n label: labels.get(Rdf.iri(f.iri)),\n }));\n });\n })\n .toProperty();\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as request from 'platform/api/http';\nimport * as Kefir from 'kefir';\nimport * as fileSaver from 'file-saver';\n\nimport { Rdf, turtle } from 'platform/api/rdf';\nimport { SparqlUtil } from 'platform/api/sparql';\n\nexport const GRAPH_STORE_SERVICEURL = '/rdf-graph-store';\n\nclass GraphStoreService {\n public createGraph({\n targetGraph,\n graphData,\n repository,\n }: {\n targetGraph: Rdf.Iri;\n graphData: Rdf.Graph;\n repository?: string;\n }): Kefir.Property {\n return turtle.serialize\n .serializeGraph(graphData)\n .flatMap((turtleString: string) => this.createGraphRequest({ targetGraph, turtleString, repository }))\n .map((location) => Rdf.iri(location))\n .toProperty();\n }\n\n private createGraphRequest({\n targetGraph,\n turtleString,\n repository,\n }: {\n targetGraph: Rdf.Iri;\n turtleString: string;\n repository?: string;\n }): Kefir.Property {\n const req = request\n .post(GRAPH_STORE_SERVICEURL)\n .query({ graph: targetGraph.value, repository: repository })\n .send(turtleString)\n .type('text/turtle');\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res) => cb(err, res ? res.header['location'] : null))\n ).toProperty();\n }\n\n public updateGraph({\n targetGraph,\n graphData,\n repository,\n }: {\n targetGraph: Rdf.Iri;\n graphData: Rdf.Graph;\n repository?: string;\n }): Kefir.Property {\n return turtle.serialize\n .serializeGraph(graphData)\n .flatMap((turtleString: string) => this.createGraphRequest({ targetGraph, turtleString, repository }))\n .map((location) => Rdf.iri(location))\n .toProperty();\n }\n\n public updateGraphRequest({\n targetGraph,\n turtleString,\n repository,\n }: {\n targetGraph: Rdf.Iri;\n turtleString: string;\n repository?: string;\n }): Kefir.Property {\n const req = request\n .put(GRAPH_STORE_SERVICEURL)\n .query({ uri: targetGraph.value, repository: repository })\n .send(turtleString)\n .type('text/turtle');\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res) => cb(err, res ? res.header['location'] : null))\n ).toProperty();\n }\n\n public createGraphFromFile({\n targetGraph,\n keepSourceGraphs,\n file,\n contentType,\n onProgress,\n repository,\n }: {\n targetGraph: Rdf.Iri;\n keepSourceGraphs: boolean;\n file: File;\n contentType: string;\n onProgress: (percent: number) => void;\n repository?: string;\n }): Kefir.Property {\n const req = request\n .post(GRAPH_STORE_SERVICEURL)\n .query({\n graph: targetGraph.value,\n keepSourceGraphs: keepSourceGraphs,\n repository: repository,\n })\n .type(contentType)\n .send(file)\n .on('progress', (e) => onProgress(e.percent));\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(err, res.ok ? true : null);\n })\n ).toProperty();\n }\n\n public updateGraphFromFile({\n targetGraph,\n file,\n contentType,\n onProgress,\n repository,\n }: {\n targetGraph: Rdf.Iri;\n file: File;\n contentType: string;\n onProgress: (percent: number) => void;\n repository?: string;\n }): Kefir.Property {\n const req = request\n .put(GRAPH_STORE_SERVICEURL)\n .query({ graph: targetGraph.value, repository: repository })\n .type(contentType)\n .send(file)\n .on('progress', (e) => onProgress(e.percent));\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(err, res.ok ? true : null);\n })\n ).toProperty();\n }\n\n getGraph({ targetGraph, repository }: { targetGraph: Rdf.Iri; repository?: string }): Kefir.Property {\n const req = request\n .get(GRAPH_STORE_SERVICEURL)\n .query({ graph: targetGraph.value, repository: repository })\n .accept('text/turtle');\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(this.errorToString(err), res.ok ? turtle.deserialize.turtleToGraph(res.text) : null);\n })\n ).toProperty();\n }\n\n public deleteGraph({\n targetGraph,\n repository,\n }: {\n targetGraph: Rdf.Iri;\n repository?: string;\n }): Kefir.Property {\n const req = request.del(GRAPH_STORE_SERVICEURL).query({ graph: targetGraph.value, repository: repository });\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(this.errorToString(err), res.ok ? true : null);\n })\n ).toProperty();\n }\n\n public downloadGraph({\n targetGraph,\n acceptHeader,\n fileName,\n repository,\n }: {\n targetGraph: Rdf.Iri;\n acceptHeader: SparqlUtil.ResultFormat;\n fileName: string;\n repository?: string;\n }): Kefir.Property {\n const req = request\n .get(GRAPH_STORE_SERVICEURL)\n .query({ graph: targetGraph.value, repository: repository })\n .accept(acceptHeader);\n\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(this.errorToString(err), res.ok ? this.download(res.text, acceptHeader, fileName) : false);\n })\n ).toProperty();\n }\n\n private download(response, header, filename): boolean {\n let blob = new Blob([response], { type: header });\n fileSaver.saveAs(blob, filename);\n return true;\n }\n\n private errorToString(err: any): string {\n if (err !== null) {\n const status = err['status'];\n if (413 === status) {\n return 'File too large. Please contact your administrator.';\n } else {\n return err.response.text;\n }\n }\n\n return null;\n }\n}\n\nexport const RDFGraphStoreService = new GraphStoreService();\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as request from 'platform/api/http';\nimport * as Kefir from 'kefir';\n\nimport { Rdf } from 'platform/api/rdf';\n\nexport const FILEUPLOAD_SERVICEURL = '/file-upload';\n\nclass FileUpload {\n public uploadFile(options: {\n createResourceQuery: string;\n generateIdQuery: string;\n storage: string;\n metadataExtractor: string;\n contextUri: string;\n file: File;\n contentType: string;\n onProgress: (percent: number) => void;\n }): Kefir.Property {\n const req = request\n .post(FILEUPLOAD_SERVICEURL)\n .field('createResourceQuery', options.createResourceQuery)\n .field('generateIdQuery', options.generateIdQuery)\n .field('storage', options.storage)\n .field('metadataExtractor', options.metadataExtractor || '')\n .field('contextUri', options.contextUri)\n // .type(options.contentType)\n .attach('image', options.file as any)\n .on('progress', (e) => options.onProgress(e.percent));\n return Kefir.fromNodeCallback((cb) =>\n req.end((err, res: request.Response) => {\n cb(err != null ? err.message : null, res.ok ? Rdf.iri(res.header['location']) : null);\n })\n ).toProperty();\n }\n\n public getMimeType(file: File): string {\n const fileEnding = file.name.split('.').pop().toLowerCase().trim();\n switch (fileEnding) {\n case 'jpg':\n return 'image/jpeg';\n default:\n return 'application/octet-stream';\n }\n }\n}\n\nexport const FileUploadService = new FileUpload();\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as request from 'platform/api/http';\nimport * as Kefir from 'kefir';\n\nimport { Rdf } from 'platform/api/rdf';\n\nconst POST_INVALIDATE_ALL = '/rest/cache/all/invalidate';\n\n/**\n * Invalidate all caches.\n */\nexport function invalidateAllCaches() {\n return sendRequest(POST_INVALIDATE_ALL);\n}\n\n/**\n * Invalidate all caches for the specific resource.\n */\nexport function invalidateCacheForResource(resource: Rdf.Iri) {\n const url = POST_INVALIDATE_ALL + '/' + encodeURIComponent(resource.value);\n return sendRequest(url);\n}\n\nfunction sendRequest(url: string): Kefir.Property {\n const req = request.post(url);\n return Kefir.fromNodeCallback((cb) => req.end((err, res) => cb(err, res.text))).toProperty();\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as maybe from 'data.maybe';\nimport * as Kefir from 'kefir';\n\nimport { Rdf, vocabularies } from 'platform/api/rdf';\nimport { Template, Argument } from 'platform/components/query-editor';\nimport { LdpService, LdpServiceContext } from './ldp';\nimport { includes } from 'lodash';\n\nconst { VocabPlatform, xsd, rdf, rdfs, spl, spin, dct } = vocabularies;\n\nconst DEFAULT_NAMESPACE = 'http://www.researchspace.org/query/';\nconst CATEGORIES_PREDICATE = dct.subject;\n\nexport class QueryTemplateServiceClass extends LdpService {\n public addItem(template: Template, queryIri: string, namespace: string): Kefir.Property<{}> {\n const graph = this.createGraph(template, queryIri, namespace);\n return this.addResource(graph, maybe.Just(template.identifier));\n }\n\n public updateItem(iri: Rdf.Iri, template: Template, queryIri: string, namespace: string): Kefir.Property<{}> {\n const graph = this.createGraph(template, queryIri, namespace);\n\n return this.update(iri, graph);\n }\n\n private createGraph(template: Template, queryIri: string, namespace = DEFAULT_NAMESPACE): Rdf.Graph {\n const { identifier, label, description, args } = template;\n const subject = Rdf.iri('');\n\n const argsTriples = args.map((arg, index) => {\n const argIri = Rdf.iri(namespace + identifier + '/arg/' + index);\n\n const triples = [\n Rdf.triple(subject, spin.constraintProp, argIri),\n Rdf.triple(argIri, rdf.type, spl.Argument),\n Rdf.triple(argIri, rdfs.label, Rdf.literal(arg.label)),\n Rdf.triple(argIri, spl.predicateProp, Rdf.iri(namespace + identifier + '/predicate/' + arg.variable)),\n Rdf.triple(argIri, spl.valueTypeProp, Rdf.iri(arg.valueType)),\n ];\n\n if (arg.defaultValue) {\n triples.push(Rdf.triple(argIri, spl.defaultValue, arg.defaultValue));\n }\n\n // serialize default to false i.e. by default values should not be optional\n const optional = arg.optional !== undefined ? arg.optional : false;\n triples.push(Rdf.triple(argIri, spl.optionalProp, Rdf.literal(optional, xsd.boolean)));\n\n if (arg.comment !== undefined) {\n triples.push(Rdf.triple(argIri, rdfs.comment, Rdf.literal(arg.comment)));\n }\n return triples;\n });\n\n const mergedArgsTriples: Rdf.Triple[] = [].concat.apply([], argsTriples);\n const categories = template.categories.map((category) => Rdf.triple(subject, CATEGORIES_PREDICATE, category));\n\n return Rdf.graph([\n Rdf.triple(subject, rdf.type, spin.Template),\n Rdf.triple(subject, rdf.type, template.templateType),\n Rdf.triple(subject, rdfs.label, Rdf.literal(label)),\n Rdf.triple(subject, rdfs.comment, Rdf.literal(description)),\n Rdf.triple(subject, spin.bodyProp, Rdf.iri(queryIri)),\n ...mergedArgsTriples,\n ...categories,\n ]);\n }\n\n public getQueryTemplate(iri: Rdf.Iri): Kefir.Property<{ template: Template; queryIri: string }> {\n return this.get(iri).map((graph) => this.parseGraphToQueryTemplate(iri, graph));\n }\n\n private parseGraphToQueryTemplate(iri: Rdf.Iri, graph: Rdf.Graph): { template: Template; queryIri: string } {\n const templateTypes = [spin.AskTemplate, spin.SelectTemplate, spin.ConstructTemplate, spin.UpdateTemplate].map(\n (qt) => qt.value\n );\n const templateType = graph.triples.find((t) => t.p.equals(rdf.type) && includes(templateTypes, t.o.value))\n .o as Rdf.Iri;\n\n const argsIris = graph.triples\n .filter((t) => t.s.equals(iri) && t.p.equals(spin.constraintProp))\n .toArray()\n .map((item) => item.o);\n\n const args = argsIris.map(\n (item): Argument => {\n const label = graph.triples.find((t) => t.s.equals(item) && t.p.equals(rdfs.label)).o.value;\n const variable = graph.triples.find((t) => t.s.equals(item) && t.p.equals(spl.predicateProp)).o.value;\n const comment = graph.triples.find((t) => t.s.equals(item) && t.p.equals(rdfs.comment)).o.value;\n const optional = graph.triples.find((t) => t.s.equals(item) && t.p.equals(spl.optionalProp));\n const valueType = graph.triples.find((t) => t.s.equals(item) && t.p.equals(spl.valueTypeProp)).o.value;\n const defaultValue = graph.triples.find((t) => t.s.equals(item) && t.p.equals(spl.defaultValue));\n\n return {\n label: label,\n variable: this.extractValueFromIri(variable),\n comment: comment,\n valueType: valueType,\n defaultValue: defaultValue ? defaultValue.o : undefined,\n optional: optional ? optional.o.value === 'true' : false,\n };\n }\n );\n\n const template: Template = {\n templateType: templateType,\n identifier: this.extractValueFromIri(iri.value),\n label: graph.triples.find((t) => t.s.equals(iri) && t.p.equals(rdfs.label)).o.value,\n description: graph.triples.find((t) => t.s.equals(iri) && t.p.equals(rdfs.comment)).o.value,\n categories: graph.triples\n .filter((t) => t.s.equals(iri) && t.p.equals(CATEGORIES_PREDICATE) && t.o.isIri())\n .map((t) => t.o as Rdf.Iri)\n .toArray(),\n args: args,\n };\n\n const queryIri = graph.triples.find((t) => t.s.equals(iri) && t.p.equals(spin.bodyProp)).o.value;\n\n return { template, queryIri };\n }\n\n /**\n * Return substring after last '/'\n */\n private extractValueFromIri(iri: string): string {\n return /[^/]*$/.exec(iri)[0];\n }\n}\n\nexport const QueryTemplateService = function (context: LdpServiceContext) {\n return new QueryTemplateServiceClass(VocabPlatform.QueryTemplateContainer.value, context);\n};\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as Kefir from 'kefir';\nimport * as uuid from 'uuid';\nimport * as Maybe from 'data.maybe';\nimport * as moment from 'moment';\n\nimport { Rdf } from 'platform/api/rdf';\nimport { VocabPlatform, workflow as VocabWorkflow, rdf, xsd, sp } from 'platform/api/rdf/vocabularies';\nimport { LdpService } from 'platform/api/services/ldp';\nimport { SparqlClient } from 'platform/api/sparql';\nimport { getLabels } from 'platform/api/services/resource-label';\nimport { Util as SecurityUtil } from 'platform/api/services/security';\nimport { parseQuerySync } from 'platform/api/sparql/SparqlUtil';\n\nconst ASSIGNEE_VARIABLE = 'assignee';\nconst NEW_STEP_VARIABLE = '__newStep__';\nconst WORKFLOW_INSTANTIATION_VARIABLE = '__workflowInstantiation__';\n\nexport interface WorkflowState {\n step: Rdf.Iri | undefined;\n assignee: Rdf.Iri | undefined;\n}\nexport namespace WorkflowState {\n export const empty: WorkflowState = { step: undefined, assignee: undefined };\n\n export function isEqual(a: WorkflowState, b: WorkflowState): boolean {\n return (\n ((a.step && a.step.equals(b.step)) || (!a.step && !b.step)) &&\n ((a.assignee && a.assignee.equals(b.assignee)) || (!a.assignee && !b.assignee))\n );\n }\n}\n\nexport interface WorkflowStep {\n iri: Rdf.Iri;\n label: string;\n /**\n * SPARQL Select query returns possible assignees.\n * Expected projection variables:\n * * '?assignee' - possible assignees\n * Injected variables:\n * * '?__newStep__' - new step IRI\n * * '?__workflowInstantiation__' - workflow instantiation IRI\n */\n assigneeQuery?: string;\n}\n\nexport interface WorkflowAssignee {\n iri: Rdf.Iri;\n label: string;\n}\n\nexport interface WorkflowData {\n subject: Rdf.Iri;\n definition: Rdf.Iri;\n firstStep: Rdf.Iri;\n metadataQuery?: string;\n assignee?: Rdf.Iri;\n newWorkflowIriTemplate?: string;\n}\n\nexport class WorkflowService {\n private ldpService: LdpService;\n constructor() {\n this.ldpService = new LdpService(VocabPlatform.WorkflowContainer.value, { repository: 'default' });\n }\n\n queryWorkflowInstantiation(workflow: string): Kefir.Property {\n return this.ldpService.get(Rdf.iri(workflow));\n }\n\n queryWorkflowSteps({\n definition,\n currentStep,\n }: {\n definition: string;\n currentStep?: Rdf.Iri;\n }): Kefir.Property> {\n const ldpAssetsService = new LdpService(VocabPlatform.WorkflowContainer.value, { repository: 'assets' });\n const definitionIri = Rdf.iri(definition);\n return ldpAssetsService\n .get(definitionIri)\n .flatMap((g) => {\n let steps: Array;\n if (currentStep) {\n steps = Rdf.getValuesFromPropertyPath([VocabWorkflow.nextStep], Rdf.pg(currentStep, g));\n steps.push(currentStep);\n } else {\n steps = Rdf.getValuesFromPropertyPath([VocabWorkflow.hasStep], Rdf.pg(definitionIri, g));\n }\n\n return getLabels(steps, { context: { repository: 'assets' } }).map((labels) =>\n steps.map((step) => {\n const label = labels.get(step);\n const assigneeQuery = Rdf.getValueFromPropertyPath(\n [VocabWorkflow.assigneeQuery, sp.text],\n Rdf.pg(step, g)\n )\n .map((v) => v.value)\n .getOrElse(undefined);\n return { iri: step, assigneeQuery, label } as WorkflowStep;\n })\n );\n })\n .toProperty();\n }\n\n queryWorkflowAssignees({\n query,\n newStep,\n workflowInstantiations,\n }: {\n query: string;\n newStep: Rdf.Iri;\n workflowInstantiations: Array;\n }): Kefir.Property> {\n const values = workflowInstantiations.map((iri) => ({\n [WORKFLOW_INSTANTIATION_VARIABLE]: iri,\n }));\n return SparqlClient.prepareQuery(query, values)\n .map((parsedQuery) => SparqlClient.setBindings(parsedQuery, { [NEW_STEP_VARIABLE]: newStep }))\n .flatMap((parsedQuery) => SparqlClient.select(parsedQuery))\n .flatMap(({ results }) => {\n const assignees = results.bindings.map((binding) => {\n return binding[ASSIGNEE_VARIABLE] as Rdf.Iri;\n });\n return getLabels(assignees);\n })\n .map((labels) => {\n const assignees: Array = [];\n labels.forEach((label, iri) => assignees.push({ iri, label } as WorkflowAssignee));\n return assignees;\n })\n .toProperty();\n }\n\n private generateSubjectByTemplate(template: string | undefined): string {\n const iriTemplate = template || '{{UUID}}';\n const subject = iriTemplate.replace(/{{([^{}]+)}}/g, (match, placeholder) => {\n if (placeholder === 'UUID') {\n return uuid.v4();\n } else {\n return '';\n }\n });\n return subject;\n }\n\n createWorkflowInstantiation(workflowData: WorkflowData): Kefir.Property {\n const ldpAssetsService = new LdpService(VocabPlatform.WorkflowContainer.value, { repository: 'assets' });\n return ldpAssetsService\n .get(workflowData.definition)\n .flatMap((graph) => {\n try {\n const steps = Rdf.getValuesFromPropertyPath(\n [VocabWorkflow.hasStep],\n Rdf.pg(workflowData.definition, graph)\n );\n const firstStep = steps.find((step) => step.value === workflowData.firstStep.value);\n if (!firstStep) {\n throw new Error(`Unknown step ${workflowData.firstStep}, no equals with definition's steps`);\n }\n const subjectIri = this.generateSubjectByTemplate(workflowData.newWorkflowIriTemplate);\n const subject = Rdf.iri('');\n const workflowStateIri = Rdf.iri(`${VocabWorkflow.hasState.value}-${uuid.v4()}`);\n const workflowMetadataIri = Rdf.iri(`${VocabWorkflow.metadata.value}-${uuid.v4()}`);\n const timeLiteral = Rdf.literal(moment().toISOString(), xsd.dateTime);\n const triples: Array = [\n Rdf.triple(subject, rdf.type, VocabWorkflow.WorkflowInstantiation),\n Rdf.triple(subject, VocabWorkflow.subject, workflowData.subject),\n Rdf.triple(subject, VocabWorkflow.hasState, workflowStateIri),\n Rdf.triple(subject, VocabWorkflow.currentState, workflowStateIri),\n Rdf.triple(workflowStateIri, rdf.type, VocabWorkflow.WorkflowState),\n Rdf.triple(workflowStateIri, VocabWorkflow.step, firstStep),\n Rdf.triple(workflowStateIri, VocabWorkflow.startTime, timeLiteral),\n ];\n if (workflowData.assignee.value) {\n triples.push(Rdf.triple(workflowStateIri, VocabWorkflow.assignee, workflowData.assignee));\n }\n return this.createMetadata(workflowData.metadataQuery, workflowMetadataIri).flatMap((metadataGraph) => {\n if (metadataGraph.length !== 0) {\n triples.push(Rdf.triple(subject, VocabWorkflow.metadata, workflowMetadataIri));\n metadataGraph.forEach((item) => {\n triples.push(Rdf.triple(item.s, item.p, item.o));\n });\n }\n const workflowGraph = Rdf.graph(triples);\n return this.ldpService.addResource(workflowGraph, Maybe.Just(subjectIri));\n });\n } catch (error) {\n return Kefir.constantError(error);\n }\n })\n .toProperty();\n }\n\n createMetadata(metadataQuery: string, metadataIri: Rdf.Iri): Kefir.Property {\n if (!metadataQuery) {\n return Kefir.constant([]);\n }\n let query = parseQuerySync(metadataQuery);\n query = SparqlClient.setBindings(query, {\n metadataIri,\n });\n return SparqlClient.construct(query);\n }\n\n updateWorkflowInstantiation({\n workflowIri,\n originalGraph,\n workflowState,\n }: {\n workflowIri: Rdf.Iri;\n originalGraph: Rdf.Graph;\n workflowState: WorkflowState;\n }): Kefir.Property {\n return Kefir.fromPromise(SecurityUtil.getUser())\n .flatMap((user) => {\n const workflowStateIri = Rdf.iri(`${workflowIri.value}/${uuid.v4()}`);\n const timeLiteral = Rdf.literal(moment().toISOString(), xsd.dateTime);\n const triples: Array = [];\n Rdf.getValueFromPropertyPath(\n [VocabWorkflow.currentState],\n Rdf.pg(workflowIri, originalGraph)\n ).map((currentState) =>\n triples.push(\n Rdf.triple(currentState, VocabWorkflow.endTime, timeLiteral),\n Rdf.triple(currentState, VocabWorkflow.advancedBy, Rdf.iri(user.userURI))\n )\n );\n triples.push(\n Rdf.triple(workflowIri, VocabWorkflow.hasState, workflowStateIri),\n Rdf.triple(workflowIri, VocabWorkflow.currentState, workflowStateIri),\n Rdf.triple(workflowStateIri, rdf.type, VocabWorkflow.WorkflowState),\n Rdf.triple(workflowStateIri, VocabWorkflow.step, workflowState.step),\n Rdf.triple(workflowStateIri, VocabWorkflow.startTime, timeLiteral)\n );\n if (workflowState.assignee) {\n triples.push(Rdf.triple(workflowStateIri, VocabWorkflow.assignee, workflowState.assignee));\n }\n originalGraph.triples.forEach((t) => {\n if (!t.p.equals(VocabWorkflow.currentState)) {\n triples.push(t);\n }\n });\n const workflowGraph = Rdf.graph(triples);\n return this.ldpService.update(workflowIri, workflowGraph);\n })\n .toProperty();\n }\n\n deserializeWorkflowState(workflowIri: Rdf.Iri, workflowGraph: Rdf.Graph): WorkflowState {\n return Rdf.getValueFromPropertyPath([VocabWorkflow.currentState], Rdf.pg(workflowIri, workflowGraph))\n .map((currentState) => {\n const pg = Rdf.pg(currentState, workflowGraph);\n const step = Rdf.getValueFromPropertyPath([VocabWorkflow.step], pg).getOrElse(undefined);\n const assignee = Rdf.getValueFromPropertyPath([VocabWorkflow.assignee], pg).getOrElse(undefined);\n return { step, assignee };\n })\n .getOrElse(WorkflowState.empty);\n }\n\n isWorkflowExist(resourceIri: Rdf.Iri): Kefir.Property {\n const queryStr = `ASK {\n ?workflow a ?type .\n ?workflow ?predicate ?subject .\n }`;\n let query = parseQuerySync(queryStr);\n query = SparqlClient.setBindings(query, {\n subject: resourceIri,\n type: VocabWorkflow.WorkflowInstantiation,\n predicate: VocabWorkflow.subject,\n });\n return SparqlClient.ask(query);\n }\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport { Rdf } from 'platform/api/rdf';\nimport { QueryContext } from 'platform/api/sparql';\n\nimport { getPreferredUserLanguage } from 'platform/api/services/language';\n\nimport { BaseResourceService } from './BaseResourceService';\n\nconst LABELS_SERVICE_URL = '/rest/data/rdf/utils/getLabelsForRdfValue';\nconst service = new (class extends BaseResourceService {\n constructor() {\n super(LABELS_SERVICE_URL);\n }\n\n createRequest(resources: string[], repository: string) {\n const request = super.createRequest(resources, repository);\n const preferredLanguage = getPreferredUserLanguage();\n request.query({ preferredLanguage });\n return request;\n }\n})();\n\nexport function getLabel(iri: Rdf.Iri, options?: { context?: QueryContext }) {\n return service.getResource(iri, options ? options.context : undefined);\n}\n\nexport function getLabels(iris: ReadonlyArray, options?: { context?: QueryContext }) {\n return service.getResources(iris, options ? options.context : undefined);\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as Kefir from 'kefir';\nimport * as SparqlJs from 'sparqljs';\n\nimport { SparqlUtil } from 'platform/api/sparql';\n\nimport * as ConfigService from './config';\nimport { NotEnoughPermissionsError } from './security';\n\ninterface RawConfig {\n environment: EnvironmentConfig;\n ui: RawUIConfig;\n global: GlobalConfig;\n}\n\n/**\n * This is static holder of configuration. It's initalized in MainApp, everything is rendered after\n * this component is ready. To use, call getEnvironmentConfig, it will get you either config or\n * throw an error if it's not initialized yet\n */\nexport class ConfigHolderClass {\n private isLoading: boolean;\n\n private environmentConfig: EnvironmentConfig;\n private uiConfig: UIConfig;\n private globalConfig: GlobalConfig;\n\n constructor() {\n this.isLoading = true;\n }\n\n /**\n * Get environment config in runtime. Values will be available when rendering.\n * @returns EnvironmentConfig\n */\n public getEnvironmentConfig(): EnvironmentConfig {\n if (this.isLoading) {\n throw Error('Config has not been initialized yet');\n }\n return this.environmentConfig;\n }\n\n /**\n * Get environment config in runtime. Values will be available when rendering.\n * @returns EnvironmentConfig\n */\n public getUIConfig(): UIConfig {\n if (this.isLoading) {\n throw Error('Config has not been initialized yet');\n }\n return this.uiConfig;\n }\n\n /**\n * Get global config in runtime. Values will be available when rendering.\n * @returns GlobalConfig\n */\n public getGlobalConfig(): GlobalConfig {\n if (this.isLoading) {\n throw Error('Config has not been initialized yet');\n }\n return this.globalConfig;\n }\n\n fetchConfig(): Kefir.Property {\n return Kefir.combine({\n environment: ConfigService.getConfigsInGroup('environment'),\n ui: ConfigService.getConfigsInGroup('ui'),\n global: ConfigService.getConfigsInGroup('global'),\n }).toProperty();\n }\n\n /**\n * This method is to be called by MainApp to trigger config initialization.\n */\n initializeConfig(rawConfig: RawConfig) {\n this.setEnvironmentConfig(rawConfig.environment);\n this.setUIConfig(rawConfig.ui);\n this.setGlobalConfig(rawConfig.global);\n this.isLoading = false;\n }\n\n private setEnvironmentConfig(config: EnvironmentConfig) {\n if (!config.resourceUrlMapping) {\n throw new NotEnoughPermissionsError(\n 'Configuration property \"resourceUrlMapping\" is undefined. ' +\n 'Most likely permissions for reading the configuration properties are not set correctly.'\n );\n }\n this.environmentConfig = config;\n }\n\n private setUIConfig(config: RawUIConfig) {\n const {\n preferredLanguages,\n preferredLabels,\n preferredThumbnails,\n templateIncludeQuery,\n enableUiComponentBasedSecurity,\n } = config;\n\n const labelPaths = preferredLabels ? preferredLabels.value : [];\n const thumbnailPaths = preferredThumbnails ? preferredThumbnails.value : [];\n this.uiConfig = {\n preferredLanguages: preferredLanguages ? preferredLanguages.value : [],\n labelPropertyPattern: makePropertyPattern(labelPaths),\n labelPropertyPath: makePropertyPath(labelPaths),\n thumbnailPropertyPattern: makePropertyPattern(thumbnailPaths),\n thumbnailPropertyPath: makePropertyPath(thumbnailPaths),\n templateIncludeQuery: templateIncludeQuery ? templateIncludeQuery.value : undefined,\n enableUiComponentBasedSecurity: enableUiComponentBasedSecurity\n ? Boolean(enableUiComponentBasedSecurity.value)\n : false,\n };\n }\n\n private setGlobalConfig(config: GlobalConfig) {\n this.globalConfig = config;\n }\n}\n\nexport interface EnvironmentConfig {\n readonly resourceUrlMapping?: StringValue;\n}\n\ninterface RawUIConfig {\n preferredLanguages?: StringArray;\n preferredLabels?: StringArray;\n preferredThumbnails?: StringArray;\n templateIncludeQuery?: StringValue;\n enableUiComponentBasedSecurity?: BooleanValue;\n supportedBrowsers?: StringArray;\n unsupportedBrowserMessage?: StringValue;\n}\n\nexport interface UIConfig {\n readonly preferredLanguages: ReadonlyArray;\n readonly labelPropertyPattern: string;\n readonly labelPropertyPath: SparqlJs.PropertyPath;\n readonly thumbnailPropertyPattern: string;\n readonly thumbnailPropertyPath: SparqlJs.PropertyPath;\n readonly templateIncludeQuery: string | undefined;\n readonly enableUiComponentBasedSecurity: boolean;\n readonly supportedBrowsers?: ReadonlyArray;\n readonly unsupportedBrowserMessage?: string | undefined;\n}\n\nexport interface GlobalConfig {\n readonly homePage?: StringValue;\n}\n\nexport interface StringValue {\n value: string;\n shadowed: boolean;\n}\n\nexport interface StringArray {\n value: string[];\n shadowed: boolean;\n}\n\nexport interface BooleanValue {\n value: boolean;\n shadowed: boolean;\n}\n\nfunction makePropertyPattern(paths: ReadonlyArray): string {\n return keepOnlyPropertyPaths(paths).join('|');\n}\n\nfunction makePropertyPath(paths: ReadonlyArray): SparqlJs.PropertyPath {\n const alternatives: Array = [];\n for (const path of keepOnlyPropertyPaths(paths)) {\n try {\n const alternative = SparqlUtil.parsePropertyPath(path);\n alternatives.push(alternative);\n } catch (err) {\n console.warn('Invalid label property path', err);\n }\n }\n\n if (alternatives.length === 0) {\n throw new Error('Failed to construct property path for labels (path is empty)');\n }\n\n return {\n type: 'path',\n pathType: '|',\n items: alternatives,\n };\n}\n\nfunction keepOnlyPropertyPaths(paths: ReadonlyArray): string[] {\n return paths.filter((path) => !(path.startsWith('{') || path.endsWith('}')));\n}\n\nexport const ConfigHolder = new ConfigHolderClass();\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as request from 'platform/api/http';\n\nimport { parseTemplate } from './RemoteTemplateFetcher';\n\nconst TEMPLATE_SERVICE_URL = '/rest/template/';\n\nexport function getHeader(cb: (html: string) => void): void {\n request\n .get(TEMPLATE_SERVICE_URL + 'header')\n .accept('text/html')\n .end((err, res) => {\n cb(res.text);\n });\n}\n\nexport function getFooter(cb: (html: string) => void): void {\n request\n .get(TEMPLATE_SERVICE_URL + 'footer')\n .accept('text/html')\n .end((err, res) => {\n cb(res.text);\n });\n}\n\nexport function getNoPermissionsPage(cb: (html: string) => void): void {\n request\n .get(TEMPLATE_SERVICE_URL + 'noPermissionsPage')\n .accept('text/html')\n .end((err, res) => {\n cb(res.text);\n });\n}\n\nexport { ContextCapturer, CapturedContext } from './functions';\nimport * as TemplateParser from './TemplateParser';\nexport { TemplateParser, parseTemplate };\nexport * from './TemplateScope';\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as Kefir from 'kefir';\nimport { get } from 'platform/api/http';\n\nimport { Rdf } from 'platform/api/rdf';\nimport { constructUrlForResource } from 'platform/api/navigation';\n\nmodule URLMinifierService {\n const URL_MINIFIER_SERVICE_URL = '/rest/url-minify/getShort';\n\n export function getShortKey(url: string): Kefir.Property {\n const request = get(URL_MINIFIER_SERVICE_URL).query({ url }).accept('text/plain');\n return Kefir.fromNodeCallback((cb) => request.end((err, res) => cb(err, res.text))).toProperty();\n }\n\n export function getShortURLForResource(iri: Rdf.Iri, repository?: string): Kefir.Property {\n return constructUrlForResource(iri, {}, repository)\n .map((url) => url.absoluteTo(location.origin).valueOf())\n .flatMap(makeShortURL)\n .toProperty();\n }\n\n export function makeShortURL(fullUrl: string): Kefir.Property {\n return URLMinifierService.getShortKey(fullUrl).map((key) => location.origin + '/l/' + key);\n }\n}\n\nexport = URLMinifierService;\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as request from 'platform/api/http';\nimport * as Kefir from 'kefir';\nimport { requestAsProperty } from 'platform/api/async';\n\nexport module GenericRestService {\n export function getJson(path: string): Kefir.Property {\n const req = request.get(path).type('application/json').accept('application/json');\n\n return requestAsProperty(req).map((res) => JSON.parse(res.text));\n }\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nexport { EventsApi, Event, EventType } from './EventsApi';\nimport * as BuiltInEvents from './BuiltInEvents';\nexport { BuiltInEvents };\nexport * from './EventsStore';\nexport * from './Utils';\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\n/**\n * This module can be used to serialize/deserialize JS instance to/from JSON.\n * E.g. it can be used to save state of the react component.\n */\n\nimport * as _ from 'lodash';\nimport { Iterable } from 'immutable';\n\nexport interface Deserializer {\n /**\n * Name which is used to determine appropriate deserializer function.\n * Should be unique across all possible deserializers.\n */\n name: string;\n\n /**\n * Deserializer function. Converts JSON object to JS instance.\n */\n deserializer: (obj: {}) => T;\n}\n\nexport interface Serializer {\n /**\n * Name which is used to mark serialized obeject.\n * Deserializer with corresponding name will be used for deserialization.\n * Should be unique across all possible serializers.\n */\n name: string;\n\n predicate: (x: any) => boolean;\n\n /**\n * Serializer function. Converts JS instance to JSON.\n */\n serializer: (x: T) => {};\n}\n\nconst TYPE_VARIABLE_NAME = '#type';\nconst VALUE_VARIABLE_NAME = '#value';\n\n/**\n * All registered desrializers.\n */\nconst deserializers: Array> = [];\n\n/**\n * All registered srializers.\n */\nconst serializers: Array> = [];\n\n/**\n * ES6 decorator function to register method as deserializer for class instance.\n *\n * @example\n * class MyClass {\n * @deserializer\n * public static fromJson(obj: {}) {return new MyClass();}\n * }\n */\nexport function deserializer(target: any, key: string, descriptor: any) {\n deserializers.push({\n name: target.prototype.constructor.name,\n deserializer: descriptor.value,\n });\n return descriptor;\n}\n\n/**\n * ES6 decorator function to register method as serializer for class instance.\n *\n * @example\n * class MyClass {\n * @serializer\n * toJson() {return {}}\n * }\n */\nexport function serializer(target: any, key: string, descriptor: any) {\n serializers.push({\n name: target.constructor.name,\n predicate: (obj) => obj instanceof target.constructor,\n serializer: descriptor.value,\n });\n return descriptor;\n}\n\n/**\n * Register serializer for class.\n * Useful when serializer need to be registered for class that is not under control.\n * Otherwise decorator approach is more concise.\n *\n * @example\n * class MyClassA {\n * constructor(x: MyClassB) {\n * this._x = x;\n * }\n * }\n *\n * serializerFor({\n * name: MyClassA.prototype.constructor.name,\n * predicate: function(obj) {\n * return obj instanceof MyClassA\n * },\n * serializer: function(obj: MyClassA) {\n * return {\n * x: obj._x\n * };\n * }\n * });\n *\n * If serializer for MyClassB has been already defined, it will be properly serialized as well.\n */\nexport function serializerFor(serializer: Serializer) {\n serializers.push(serializer);\n}\n/**\n * Register deserializer for class.\n * Useful when deserializer need to be registered for class that is not under control.\n * Otherwise decorator approach is more concise.\n *\n * @example\n * class MyClassA {\n * constructor(x: MyClassB) {\n * this._x = x;\n * }\n * }\n *\n * derializerFor({\n * name: MyClassA.prototype.constructor.name,\n * deserializer: function(obj: any) {\n * return new MyClassA(obj.x);\n * }\n * });\n *\n * If there is deserializer for MyClassB, obj.x will deserialized before invocation of the deserializer for MyClassA.\n * As result obj.x will be instanceof MyClassB.\n */\nexport function deserializerFor(deserializer: Deserializer) {\n return deserializers.push(deserializer);\n}\n\n/**\n * Serialize JS class instance to JSON object.\n * Serialization rules:\n * serialize(null) === null\n * serialize(1) === 1\n * serialize('string') === 'string'\n * serialize([1, myClassInstance]) == [1, {#type: MyClass, #value: {...}}]\n * serialize({x: 1, y: myClassInstance}) == {x: 1, y: {#type: MyClass, #value: {...}}}\n */\nexport function serialize(object: any): {} {\n if (_.isUndefined(object) || _.isNull(object) || _.isNumber(object) || _.isString(object)) {\n return object;\n } else if (_.isArray(object)) {\n return _.map(object, serialize);\n // need to have !(object instanceof Iterable) check to avoid warnings from immutablejs\n } else if (!(object instanceof Iterable) && _.isPlainObject(object)) {\n return _.transform(\n object,\n (res, val, key) => {\n res[key] = serialize(val);\n return res;\n },\n {}\n );\n } else {\n var serializerObj = _.find(serializers, (serializer) => serializer.predicate(object));\n if (serializerObj) {\n return addTypeDiscriminator(serializerObj.serializer, serializerObj.name)(object);\n } else {\n return object;\n }\n }\n}\n\n/**\n * Deserialize JSON as some JS class instance.\n */\nexport function deserialize(object: any): T {\n if (_.isUndefined(object) || _.isNull(object) || _.isNumber(object) || _.isString(object)) {\n return object as any;\n } else if (_.isArray(object)) {\n return _.map(object, deserialize);\n } else {\n var deserializerObj = _.find(deserializers, (deserializer) => object[TYPE_VARIABLE_NAME] === deserializer.name);\n if (deserializerObj) {\n return deserializerObj.deserializer(deserialize(object[VALUE_VARIABLE_NAME]));\n } else if (_.isPlainObject(object)) {\n return _.transform(\n object,\n (res, val, key) => {\n res[key] = deserialize(val);\n return res;\n },\n {}\n );\n } else {\n return object;\n }\n }\n}\n\nfunction addTypeDiscriminator(originalFn: Function, serializedObjectType) {\n return function (obj) {\n var json = {};\n json[TYPE_VARIABLE_NAME] = serializedObjectType;\n json[VALUE_VARIABLE_NAME] = serialize(originalFn.apply(obj, [obj]));\n return json;\n };\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as SparqlJs from 'sparqljs';\n\nexport function isQuery(node: any): node is SparqlJs.Query {\n return typeof node === 'object' && node.type === 'query';\n}\n\nexport function isSelectQuery(query: SparqlJs.Query): query is SparqlJs.SelectQuery {\n return query.queryType === 'SELECT';\n}\n\nexport function isConstructQuery(query: SparqlJs.Query): query is SparqlJs.ConstructQuery {\n return query.queryType === 'CONSTRUCT';\n}\n\nexport function isAskQuery(query: SparqlJs.Query): query is SparqlJs.AskQuery {\n return query.queryType === 'ASK';\n}\n\nexport function isDescribeQuery(query: SparqlJs.Query): query is SparqlJs.DescribeQuery {\n return query.queryType === 'DESCRIBE';\n}\n\nexport function isStarProjection(variables: any): variables is ['*'] {\n return Array.isArray(variables) && variables.length === 1 && variables[0] === '*';\n}\n\nexport function isPattern(node: any): node is SparqlJs.Pattern {\n if (typeof node === 'object') {\n switch (node.type) {\n case 'bgp':\n case 'optional':\n case 'union':\n case 'group':\n case 'minus':\n case 'graph':\n case 'service':\n case 'filter':\n case 'values':\n return true;\n }\n }\n return false;\n}\n\nexport function isGroupPattern(pattern: SparqlJs.Pattern): pattern is SparqlJs.GroupPattern {\n return pattern.type === 'group';\n}\n\nexport function isBlockPattern(pattern: SparqlJs.Pattern): pattern is SparqlJs.BlockPattern {\n switch (pattern.type) {\n case 'optional':\n case 'union':\n case 'group':\n case 'minus':\n case 'graph':\n case 'service':\n return true;\n default:\n return false;\n }\n}\n\nexport function isExpression(node: any): node is SparqlJs.Expression {\n if (typeof node === 'string') {\n return true;\n } else if (Array.isArray(node)) {\n return true;\n } else if (typeof node === 'object') {\n switch (node.type) {\n // expressions\n case 'operation':\n case 'functionCall':\n case 'aggregate':\n // expression-like patterns\n case 'bgp':\n case 'group':\n return true;\n }\n }\n return false;\n}\n\nexport function isQuads(node: any): node is SparqlJs.Quads {\n return (node.type === 'bgp' || node.type === 'graph') && 'triples' in node;\n}\n\nexport function isTerm(\n node: SparqlJs.Expression | SparqlJs.PropertyPath | SparqlJs.VariableExpression | SparqlJs.Term\n): node is SparqlJs.Term {\n return typeof node === 'string';\n}\n\nexport function isVariable(term: any): term is SparqlJs.Term {\n return typeof term === 'string' && term.length > 0 && (term[0] === '?' || term[0] === '$');\n}\n\nexport function isLiteral(term: any): term is SparqlJs.Term {\n return typeof term === 'string' && term.length > 0 && term[0] === '\"';\n}\n\nexport function isBlank(term: any): term is SparqlJs.Term {\n return typeof term === 'string' && term.length > 1 && term[0] === '_';\n}\n\nexport function isIri(term: any): term is SparqlJs.Term {\n if (typeof term !== 'string' || term.length === 0) {\n return false;\n }\n const first = term[0];\n return first !== '?' && first !== '$' && first !== '\"' && first !== '_';\n}\n\nexport function isUpdateOperation(update: any) {\n return isInsertDeleteOperation(update) || isManagementOperation(update);\n}\n\nexport function isInsertDeleteOperation(update: SparqlJs.UpdateOperation): update is SparqlJs.InsertDeleteOperation {\n if (typeof update !== 'object') {\n return false;\n }\n const updateType = (update as SparqlJs.InsertDeleteOperation).updateType;\n return (\n updateType &&\n (updateType === 'insert' ||\n updateType === 'delete' ||\n updateType === 'deletewhere' ||\n updateType === 'insertdelete')\n );\n}\n\nexport function isManagementOperation(update: SparqlJs.UpdateOperation): update is SparqlJs.ManagementOperation {\n if (typeof update !== 'object') {\n return false;\n }\n const type = (update as SparqlJs.ManagementOperation).type;\n return type && (type === 'load' || type === 'copy' || type === 'move' || type === 'add');\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nexport * from './vocabularies';\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nexport { default as rdfs } from './rdfs';\nexport { default as rdf } from './rdf';\nexport { default as xsd } from './xsd';\nexport { default as dc } from './dc';\nexport { default as dct } from './dct';\n\nexport { default as sp } from './sp';\nexport { default as spl } from './spl';\nexport { default as spin } from './spin';\nexport { default as ldp } from './ldp';\nexport { default as oa } from './oa';\nexport { default as field } from './field';\n\nexport { default as prov } from './prov';\n\nexport { default as persist } from './persist';\nexport { default as VocabPlatform } from './platform';\n\nexport { default as workflow } from './workflow';\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport { Props as ReactProps, ReactNode, CSSProperties, createElement } from 'react';\nimport * as _ from 'lodash';\nimport * as maybe from 'data.maybe';\nimport * as Kefir from 'kefir';\n\nimport { Rdf } from 'platform/api/rdf';\nimport { getLabel } from 'platform/api/services/resource-label';\nimport { Cancellation } from 'platform/api/async';\nimport { Component } from 'platform/api/components';\nimport { getRepositoryStatus } from 'platform/api/services/repository';\nimport { SparqlClient, SparqlUtil } from 'platform/api/sparql';\nimport { ErrorNotification } from 'platform/components/ui/notification';\n\nimport { ResourceLink as InternalResourceLink } from './ResourceLink';\nimport { extractParams } from '../NavigationUtils';\n\nexport interface ResourceLinkProps extends ReactProps {\n iri?: string;\n /**\n * @deprecated\n */\n uri?: string;\n getlabel?: boolean;\n className?: string;\n style?: CSSProperties;\n title?: string;\n\n /**\n * Specify if link should be draggable, e.g. into sets.\n *\n * @default true\n */\n draggable?: boolean;\n\n guessRepository?: boolean;\n\n /**\n * Equivalent to the `target` attribute of the `` DOM element.\n * Can be set to `_blank` to open the link in a new tab/window.\n *\n * @default '_self'\n */\n target?: '_self' | '_blank';\n\n /**\n * Fragment identifier\n */\n fragment?: string;\n\n // catcher for query params\n [index: string]: any;\n}\n\ninterface State {\n label?: Data.Maybe;\n repository: Data.Maybe;\n}\n\ninterface ParamMap {\n [index: string]: string;\n}\n\n/**\n * Component which can be used in handlebars templates to generate a routed\n * link for the resource. If no childs are given (elements or text), the\n * component will automatically try to fetch a label and render a sensible and\n * human readable default link (unless getlabel=true).\n *\n * 'uri' attribute specifies destination resource uri.\n *\n * 'urlqueryparam-*' attribute specify additional url query parameter,\n * last part of attribute name corresponds to the url query parameter name.\n * For example 'urlqueryparam-example=\"test\"' attribute will result into\n * '?example=test' query parameter.\n *\n * 'getlabel' boolean attribute to specify whether label for the given resource\n * should be fetched automatically. Default: true\n *\n * @example\n * \n * \n * \n *\n * @example\n * \t // fetching label automatically\n * \n * \n *\n * @example\n * \t // fetching no label, will render plain link\n * \n * \n */\nexport class ResourceLinkComponent extends Component {\n private cancellation = new Cancellation();\n\n constructor(props: ResourceLinkProps, context) {\n super(props, context);\n this.checkDeprecated(props);\n this.state = {\n label: maybe.Nothing(),\n repository: maybe.Nothing(),\n };\n }\n\n private checkDeprecated(props: ResourceLinkProps) {\n if (props.uri) {\n console.warn(\n 'The \"uri\" property of \"ResourceLinkComponent\" is deprecated,',\n 'please use the \"iri\" property instead'\n );\n }\n }\n\n private getIri = () => {\n const { iri, uri } = this.props;\n return iri || uri;\n };\n\n public componentDidMount() {\n const iri = this.getIri();\n\n if (!iri) {\n return;\n }\n\n this.getRepository().onValue((repository) => {\n this.fetchLabel(Rdf.iri(iri), this.props.children, repository).onValue((label) =>\n this.setState({\n label: maybe.Just(label),\n repository: maybe.Just(repository),\n })\n );\n });\n }\n\n public componentWillReceiveProps(nextProps: ResourceLinkProps) {\n if (this.props.uri !== nextProps.uri) {\n this.checkDeprecated(nextProps);\n }\n }\n\n public componentWillUnmount() {\n this.cancellation.cancelAll();\n }\n\n public render() {\n const iri = this.getIri();\n\n if (!iri) {\n return createElement(ErrorNotification, {\n errorMessage: `The component doesn't have the \"iri\" property`,\n });\n }\n\n return this.state.label.map(this.renderLink).getOrElse(null);\n }\n\n private renderLink = (label: string) => {\n const iri = this.getIri();\n let props = _.clone(this.props) as any;\n props.title = label;\n return createElement(\n InternalResourceLink,\n _.assign(\n {\n resource: Rdf.iri(iri),\n params: extractParams(this.props),\n className: this.props.className,\n style: this.props.style,\n title: this.props.title,\n repository: this.state.repository.getOrElse(undefined),\n fragment: this.props.fragment,\n },\n props\n ),\n this.getChildren(this.props.children, this.state.label, iri)\n );\n };\n\n /**\n * Returns child nodes for the resource link component.\n * If the child is a plain text node equal to the resource Iri\n * or no child nodes are present a simple label string will be returned\n * (if present).\n *\n * Otherwise the unmodified array of children will be returned.\n *\n * @param {children} Children of the resource link component.\n * @param {label} Label string.\n * @param {iri} Iri of the resource link.\n */\n private getChildren = (children: ReactNode, label: Data.Maybe, iri: string) => {\n if ((_.isEmpty(children) || children === iri) && label.isJust) {\n children = label.get();\n } else if (_.isEmpty(children)) {\n children = '';\n }\n return children;\n };\n\n private fetchLabel = (resource: Rdf.Iri, children: ReactNode, repository: string): Kefir.Property => {\n if (this.props.getlabel !== false && (_.isEmpty(children) || children === resource.value)) {\n return this.cancellation.map(getLabel(resource, { context: { repository } }));\n } else {\n return Kefir.constant(resource.value);\n }\n };\n\n private static repositories = getRepositoryStatus();\n private getRepository = (): Kefir.Property => {\n if (this.props.guessRepository) {\n return ResourceLinkComponent.repositories\n .map((repositories) =>\n repositories.filter((running) => running).map((_, repository) => this.executeGuessQuery(repository))\n )\n .flatMap((responses) => Kefir.combine(responses.toKeyedSeq().toObject()))\n .map((responses) => _.findKey(responses))\n .toProperty();\n } else {\n return Kefir.constant(this.context.semanticContext ? this.context.semanticContext.repository : undefined);\n }\n };\n\n private static GUESS_QUERY = SparqlUtil.Sparql`ASK { ?subject a ?type }`;\n private executeGuessQuery = (repository: string) => {\n return SparqlClient.ask(\n SparqlClient.setBindings(ResourceLinkComponent.GUESS_QUERY, { subject: Rdf.iri(this.getIri()) }),\n { context: { repository: repository } }\n );\n };\n}\nexport default ResourceLinkComponent;\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as Maybe from 'data.maybe';\nimport * as Kefir from 'kefir';\nimport * as request from 'platform/api/http';\nimport * as _ from 'lodash';\nimport * as I from 'immutable';\n\nimport { Rdf } from 'platform/api/rdf';\nimport { BatchedPool, requestAsProperty } from 'platform/api/async';\n\nexport interface NamespaceRecord {\n prefix: string;\n iri: string;\n appId: string;\n}\n\nconst GET_FULL_URIS_URL = '/rest/data/rdf/namespace/getFullUris';\nconst GET_PREFIXED_URIS_URL = '/rest/data/rdf/namespace/getPrefixedUris';\nexport const GET_REGISTERED_PREFIXES = '/rest/data/rdf/namespace/getRegisteredPrefixes';\nconst GET_RECORDS = '/rest/data/rdf/namespace/getRecords';\nconst PUT_PREFIX = '/rest/data/rdf/namespace/setPrefix';\nconst DELETE_PREFIX = '/rest/data/rdf/namespace/deletePrefix';\n\nconst pool = new BatchedPool>({\n fetch: (iris) => getPrefixedIris(iris.toArray()),\n});\n\nexport function getPrefixedUri(iri: Rdf.Iri): Kefir.Property> {\n return pool.query(iri);\n}\n\nexport function getPrefixedIris(iris: Rdf.Iri[]): Kefir.Property>> {\n return resolveIri(\n GET_PREFIXED_URIS_URL,\n _.map(iris, (iri) => iri.value)\n ).map((res) =>\n I.Map(res)\n .mapEntries>((entry) => [Rdf.iri(entry[0]), Maybe.fromNullable(entry[1])])\n .toMap()\n );\n}\n\nexport function getFullIri(prefixedIri: string): Kefir.Property> {\n return getFullIris([prefixedIri]).map((res) => res.get(prefixedIri));\n}\n\nexport function getFullIris(iris: string[]): Kefir.Property>> {\n return resolveIri(GET_FULL_URIS_URL, iris).map((res) =>\n I.Map(res)\n .map((iri) => Maybe.fromNullable(iri).map(Rdf.iri))\n .toMap()\n );\n}\n\nexport function getRegisteredPrefixes(): Kefir.Property<{ [key: string]: string }> {\n const req = request.get(GET_REGISTERED_PREFIXES).type('application/json').accept('application/json');\n return Kefir.fromNodeCallback>((cb) => req.end((err, res) => cb(err, res.body))).toProperty();\n}\n\nexport function getNamespaceRecords(): Kefir.Property {\n const req = request.get(GET_RECORDS).type('application/json').accept('application/json');\n return requestAsProperty(req).map((res) => res.body);\n}\n\nexport function setPrefix(prefix: string, ns: string, targetAppId: string): Kefir.Property {\n const req = request\n .put(PUT_PREFIX + '/' + prefix)\n .query({ targetAppId })\n .type('text/plain')\n .send(ns);\n return requestAsProperty(req).map((res) => {});\n}\n\nexport function deletePrefix(prefix: string, targetAppId: string): Kefir.Property {\n const req = request.del(DELETE_PREFIX + '/' + prefix).query({ targetAppId });\n return requestAsProperty(req).map((res) => {});\n}\n\nfunction resolveIri(url: string, iris: string[]): Kefir.Property<{ [key: string]: string }> {\n const req = request.post(url).send(iris).type('application/json').accept('application/json');\n return Kefir.fromNodeCallback>((cb) =>\n req.end((err, res) => cb(err, res ? res.body : null))\n ).toProperty();\n}\n\nexport function isSystemNamespacePrefix(prefix: string) {\n // either empty or starts with uppercase letter\n return prefix.length === 0 || prefix[0] === prefix[0].toUpperCase();\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport * as Kefir from 'kefir';\nimport * as _ from 'lodash';\nimport * as SparqlJs from 'sparqljs';\n\nimport { Rdf } from 'platform/api/rdf';\nimport { getCurrentResource } from '../navigation/CurrentResource';\n\nimport { isQuery, isTerm, isIri } from './TypeGuards';\n\n// by default we initialized parser without prefixes so we don't need\n// to initialize it explicitly in all tests, but the expectation is that\n// in production run init is called on the system startup\nlet Parser: SparqlJs.SparqlParser = new SparqlJs.Parser();\nexport let RegisteredPrefixes: {\n Default: string; Admin: string; Help: string;\n} & { [key: string]: string };\nexport function init(registeredPrefixes: { [key: string]: string }) {\n RegisteredPrefixes = registeredPrefixes as typeof RegisteredPrefixes;\n Parser = new SparqlJs.Parser(registeredPrefixes);\n}\n\nconst Generator = new SparqlJs.Generator();\n\nexport type RDFResultFormat =\n | 'application/ld+json'\n | 'application/n-quads'\n | 'application/n-triples'\n | 'application/rdf+json'\n | 'application/rdf+xml'\n | 'application/trig'\n | 'application/trix'\n | 'application/x-binary-rdf'\n | 'application/x-trig'\n | 'application/x-turtle'\n | 'application/xml'\n | 'text/n3'\n | 'text/nquads'\n | 'text/plain'\n | 'text/rdf+n3'\n | 'text/turtle'\n | 'text/x-nquads';\n\nexport type TupleResultFormat =\n | 'application/json'\n | 'application/sparql-results+json'\n | 'application/sparql-results+xml'\n | 'application/x-binary-rdf-results-table'\n | 'text/csv'\n | 'text/tab-separated-values';\n\nexport type BooleanResultFormat = 'text/boolean';\nexport type ResultFormat = RDFResultFormat | TupleResultFormat | BooleanResultFormat;\n\nexport function guessFileEnding(resultFormat: RDFResultFormat) {\n switch (resultFormat) {\n case 'application/rdf+xml':\n return 'rdf';\n case 'text/turtle':\n return 'ttl';\n case 'application/x-trig':\n return 'trig';\n case 'application/trix':\n return 'trix';\n case 'application/ld+json':\n return 'jsonld';\n case 'text/n3':\n return 'n3';\n case 'text/x-nquads':\n return 'nq';\n case 'application/n-triples':\n return 'nt';\n default:\n return 'application/rdf+xml';\n }\n}\n\nexport function getFileEnding(file: File): string {\n return file.name.split('.').pop().toLowerCase().trim();\n}\n\nexport function getMimeType(fileEnding: String): RDFResultFormat {\n switch (fileEnding) {\n case 'owl':\n return 'application/rdf+xml';\n case 'rdf':\n return 'application/rdf+xml';\n case 'ttl':\n return 'text/turtle';\n case 'trig':\n return 'application/x-trig';\n case 'trix':\n return 'application/trix';\n case 'jsonld':\n return 'application/ld+json';\n case 'n3':\n return 'text/n3';\n case 'nq':\n return 'text/x-nquads';\n case 'nt':\n return 'text/plain';\n case 'ntriples':\n return 'text/plain';\n default:\n return 'application/rdf+xml';\n }\n}\n\nexport function addOrChangeLimit(query: SparqlJs.SelectQuery, limit: number): SparqlJs.SparqlQuery {\n query.limit = limit;\n return query;\n}\n\n/**\n * TODO deprecated, please use parseQuerySync\n */\nexport function parseQueryAsync(query: string): Kefir.Property {\n try {\n return Kefir.constant(parseQuery(query));\n } catch (e) {\n console.error('Error while parsing the query: ' + e);\n return Kefir.constantError(e);\n }\n}\n\n/**\n * Parses SPARQL query from string representation to Query Algebra using SPARQL.js\n * Resolving all namespaces with platform namespace service.\n */\nexport function parseQuery(query: string): T {\n return Parser.parse(encodeLegacyVars(replaceQueryParams(query))) as T;\n}\n\nexport function parseQuerySync(query: string): T {\n return parseQuery(query);\n}\n\nexport function serializeQuery(query: SparqlJs.SparqlQuery): string {\n return decodeLegacyVars(Generator.stringify(query));\n}\n\nexport function validateSelectQuery(query: SparqlJs.Query): Kefir.Property {\n if (isQuery(query) && query.queryType === 'SELECT') {\n return Kefir.constant(query);\n } else {\n return Kefir.constantError(new Error(`Invalid SELECT query: ${serializeQuery(query)}`));\n }\n}\n\nexport function Sparql(strings: TemplateStringsArray, ...values: any[]): SparqlJs.SparqlQuery {\n return parseQuerySync(strings.raw[0]);\n}\n\nfunction replaceQueryParams(query: string): string {\n // TODO, for legacy purpose only. Bind ?? to current resource\n if (typeof getCurrentResource() === 'undefined') {\n return query;\n } else {\n // replace special Template: prefix which is not substitued by the NS service\n const contextResource = getCurrentResource().value.startsWith('Template:')\n ? '<' + getCurrentResource().value.substr('Template:'.length) + '>'\n : getCurrentResource().toString();\n return query.replace(/\\?\\?/g, contextResource).replace(/\\$_this/g, contextResource);\n }\n}\n\nfunction decodeLegacyVars(query: string): string {\n return query.replace(/\\?____/g, '?:');\n}\n\nfunction encodeLegacyVars(query: string): string {\n return query.replace(/\\?:/g, '?____');\n}\n\nexport function randomVariableName(): string {\n return '_' + Math.random().toString(36).substring(7);\n}\n\n/**\n * @see https://lucene.apache.org/core/2_9_4/queryparsersyntax.html\n */\nconst LUCENE_ESCAPE_REGEX = /([+\\-&|!(){}\\[\\]^\"~*?:\\\\])/g;\n\n/**\n * Create a Lucene full text search query from a user input by\n * splitting it on whitespaces and escaping any special characters.\n */\nexport function makeLuceneQuery(inputText: string, escape = true, tokenize = true): Rdf.Literal {\n const words = inputText\n .split(' ')\n .map((w) => w.trim())\n .filter((w) => w.length > 0)\n .map((w) => {\n if (escape) {\n w = w.replace(LUCENE_ESCAPE_REGEX, '\\\\$1');\n }\n if (tokenize) {\n w += '*';\n }\n return w;\n })\n .join(' ');\n return Rdf.literal(words);\n}\n\nexport function parsePatterns(patterns: string, prefixes?: { [prefix: string]: string }): SparqlJs.Pattern[] {\n const wrappedPattern = `SELECT * WHERE { ${patterns} }`;\n const parser = prefixes ? new SparqlJs.Parser(prefixes) : Parser;\n const query = parser.parse(encodeLegacyVars(replaceQueryParams(wrappedPattern))) as SparqlJs.SelectQuery;\n return query.where;\n}\n\nexport function parsePropertyPath(propertyPath: string): SparqlJs.Term | SparqlJs.PropertyPath {\n const query = Parser.parse(`SELECT * WHERE { ?s ${propertyPath} ?o }`);\n if (query.type === 'query' && query.where.length === 1) {\n const pattern = query.where[0];\n if (pattern.type === 'bgp' && pattern.triples.length === 1) {\n const iriOrPath = pattern.triples[0].predicate;\n if (!isTerm(iriOrPath) || isIri(iriOrPath)) {\n return iriOrPath;\n }\n }\n }\n throw new Error(`Invalid Sparql property path: '${propertyPath}'`);\n}\n\n/**\n * Checks if SPARQL SELECT result is empty. With workaround for blazegraph bug\n * when empty result have one empty binding.\n * e.g for query like 'SELECT ?s ?p ?o WHERE { }'\n */\nexport function isSelectResultEmpty(result: { results: { bindings: {}[] } }): boolean {\n const bindings = result.results.bindings;\n return _.isEmpty(bindings) || (bindings.length === 1 && _.isEmpty(bindings[0]));\n}\n\n/**\n * Resolves prefixed IRIs to full ones using platform-wide registered prefixes.\n * If an IRI is already in a full form, it would be returned as is.\n */\nexport function resolveIris(iris: string[]): Rdf.Iri[] {\n if (iris.length === 0) {\n return [];\n }\n const serializedIris = iris.map((iri) => `(${iri})`).join(' ');\n // using initialized Sparql.js parser to resolve IRIs\n const parsed = parseQuery(`SELECT * WHERE {} VALUES (?iri) { ${serializedIris} }`);\n return parsed.values.map((row) => Rdf.iri(row['?iri']));\n}\n\n// see SPARQL 1.1 grammar for all allowed characters:\n// https://www.w3.org/TR/sparql11-query/#rPN_LOCAL\nconst IRI_LOCAL_PART = /^[a-zA-Z][-_a-zA-Z0-9]*$/;\n\n// TODO: move to NamespaceService\nexport function compactIriUsingPrefix(iri: Rdf.Iri): string {\n const iriValue = iri.value;\n for (const prefix in RegisteredPrefixes) {\n if (!RegisteredPrefixes.hasOwnProperty(prefix)) {\n continue;\n }\n const expandedPrefix = RegisteredPrefixes[prefix];\n if (iriValue.startsWith(expandedPrefix)) {\n const localPart = iriValue.substring(expandedPrefix.length, iriValue.length);\n if (IRI_LOCAL_PART.test(localPart)) {\n return prefix + ':' + localPart;\n }\n }\n }\n return `<${iriValue}>`;\n}\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport { registerSerializersAndDeserializers as forImmutable } from './3rd-party/immutable';\nimport { registerSerializersAndDeserializers as forDataMaybe } from './3rd-party/data.maybe';\nimport { registerSerializersAndDeserializers as forMoment } from './3rd-party/moment';\n\nexport * from './JsonCore';\nexport { recordSerializer } from './3rd-party/immutable';\n\nforImmutable();\nforDataMaybe();\nforMoment();\n","/**\n * ResearchSpace\n * Copyright (C) 2020, © Trustees of the British Museum\n * Copyright (C) 2015-2019, metaphacts GmbH\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n */\n\nimport { ReactElement, createElement, Props, MouseEvent, CSSProperties } from 'react';\nimport * as D from 'react-dom-factories';\nimport { assign } from 'lodash';\nimport * as classNames from 'classnames';\nimport * as Maybe from 'data.maybe';\nimport * as _ from 'lodash';\n\nimport { Cancellation } from 'platform/api/async';\nimport { Component } from 'platform/api/components';\nimport { Rdf } from 'platform/api/rdf';\nimport { Draggable } from 'platform/components/dnd';\nimport {\n getCurrentResource,\n navigateToResource,\n getCurrentUrl,\n constructUrlForResource,\n construcUrlForResourceSync,\n} from '../Navigation';\n\nexport enum ResourceLinkAction {\n edit,\n}\n\ninterface ResourceLinkProps extends Props {\n resource: Rdf.Iri;\n title?: string;\n draggable?: boolean;\n action?: ResourceLinkAction;\n params?: {};\n className?: string;\n activeClassName?: string;\n style?: CSSProperties;\n repository?: string;\n target?: '_self' | '_blank';\n fragment?: string;\n}\n\ninterface State {\n readonly url?: uri.URI;\n}\n\nexport class ResourceLink extends Component {\n private readonly cancellation = new Cancellation();\n\n constructor(props: ResourceLinkProps, context: any) {\n super(props, context);\n this.state = {\n url: construcUrlForResourceSync(\n this.props.resource,\n this.props.params,\n this.getRepository(),\n this.props.fragment\n ),\n };\n }\n\n static defaultProps = {\n target: '_self' as '_self',\n };\n\n componentDidMount() {\n this.cancellation\n .map(constructUrlForResource(this.props.resource, this.props.params, this.getRepository(), this.props.fragment))\n .observe({\n value: (url) => this.setState({ url }),\n error: (error) => console.error(error),\n });\n }\n\n componentWillUnmount() {\n this.cancellation.cancelAll();\n }\n\n public render() {\n const { title, className, activeClassName, style, resource, draggable, target } = this.props;\n const { url } = this.state;\n const props = {\n href: url.toString(),\n title: title,\n className: classNames(className, {\n [activeClassName]: this.isLinkActive(),\n }),\n style: style,\n onClick: this.onClick,\n target: target,\n };\n\n // by default all links are draggable, but sometimes we want to disable this behavior\n if (draggable === false) {\n return D.a(props, this.props.children);\n } else {\n return createElement(Draggable, { iri: resource.value }, D.a(props, this.props.children));\n }\n }\n\n private onClick = (e: MouseEvent) => {\n if (isSimpleClick(e) && this.props.target === '_self') {\n e.preventDefault();\n e.stopPropagation();\n\n const query = { action: ResourceLinkAction[this.props.action], ...this.props.params };\n navigateToResource(this.props.resource, query, this.getRepository(), this.props.fragment).onValue(() => {\n /**/\n });\n }\n // otherwise we just let default link action trigger, and for example if\n // target='_blank' is set it will just open the page in a new window\n };\n\n private getRepository = () =>\n this.props.repository\n ? this.props.repository\n : this.context.semanticContext\n ? this.context.semanticContext.repository\n : undefined;\n\n private isLinkActive = () => {\n const { resource, params } = this.props;\n const urlParams = assign({}, params);\n if (!_.isUndefined(this.props.action)) {\n urlParams['action'] = ResourceLinkAction[this.props.action];\n }\n\n // extract params from current url and drop ?uri param\n // for comparison i.e. in case of dealing with full uris\n const currentUrlParms = assign({}, getCurrentUrl().search(true));\n delete currentUrlParms.uri;\n return getCurrentResource().equals(resource) && _.isEqual(currentUrlParms, urlParams);\n };\n}\n\n/**\n * make sure that we don't hijack Ctrl+Click, Meta+Click, middle mouse click default actions\n */\nexport function isSimpleClick(e: MouseEvent