diff --git a/src/config/ToastData.ts b/src/config/ToastData.ts index dacff385..3b0c2a8f 100644 --- a/src/config/ToastData.ts +++ b/src/config/ToastData.ts @@ -5,7 +5,8 @@ import { AppSettings } from "./Variables"; export const ToastData: { [Key in keyof typeof enToast]: ToastType } = { lookingForRelationshipsOrProperties: { repeatable: false }, diagramsClosedByDefault: { repeatable: false }, - newFunctionEditTrope: { repeatable: false } + newFunctionEditTrope: { repeatable: false }, + newFunctionDescriptionAndSources: { repeatable: false } }; export type ToastType = { diff --git a/src/config/Variables.ts b/src/config/Variables.ts index 325fee54..bbd3a0ea 100644 --- a/src/config/Variables.ts +++ b/src/config/Variables.ts @@ -3,7 +3,7 @@ import { Restriction } from "../datatypes/Restriction"; import { Representation } from "./Enum"; import * as joint from "jointjs"; import { Environment } from "./Environment"; -import { Languages } from "./Languages"; +import { LanguageObject, Languages } from "./Languages"; export type AlternativeLabel = { label: string; language: string }; @@ -88,6 +88,8 @@ export var WorkspaceTerms: { subClassOf: string[]; restrictions: Restriction[]; topConcept: string | undefined; + descriptions: LanguageObject; + source: string; }; } = {}; diff --git a/src/function/FunctionCreateVars.ts b/src/function/FunctionCreateVars.ts index 0e294d2f..e1a33e97 100644 --- a/src/function/FunctionCreateVars.ts +++ b/src/function/FunctionCreateVars.ts @@ -125,6 +125,8 @@ export function addVocabularyElement( subClassOf: [], restrictions: [], topConcept: scheme, + source: "", + descriptions: initLanguageObject("") }; } @@ -158,7 +160,7 @@ export function addDiagram( index = Object.keys(Diagrams).length > 0 ? Object.values(Diagrams).reduce((a, b) => (a.index > b.index ? a : b)) - .index + 1 + .index + 1 : 0; Diagrams[diagramID] = { name: name, diff --git a/src/function/FunctionElem.ts b/src/function/FunctionElem.ts index 88c07066..582ee53c 100644 --- a/src/function/FunctionElem.ts +++ b/src/function/FunctionElem.ts @@ -317,7 +317,6 @@ export async function putElementsOnCanvas( } const iris: string[] = data.iri; const ids: string[] = data.id.filter((id: string) => !graph.getCell(id)); - debugger; if (iris.length === 0 && ids.length === 0) { console.warn(`Expected to receive valid IRI data, got ${dataToParse} diff --git a/src/locale/cs.ts b/src/locale/cs.ts index 6509afbf..57e12f69 100644 --- a/src/locale/cs.ts +++ b/src/locale/cs.ts @@ -84,6 +84,8 @@ export const cs: { [Property in keyof typeof en]: string } = { detailPanelPrefLabel: "Jméno", detailPanelAltLabel: "Synonyma", detailPanelInScheme: "Ve slovníku", + detailPanelSource: "Zdroj", + detailPanelDescription: "Popis", detailPanelDefinition: "Definice", detailPanelStereotype: "Stereotyp", setCanvasLanguage: "Zobrazovaný jazyk slovníků", diff --git a/src/locale/cstoast.ts b/src/locale/cstoast.ts index 99d8670a..56533f5c 100644 --- a/src/locale/cstoast.ts +++ b/src/locale/cstoast.ts @@ -20,5 +20,9 @@ export const csToast: { newFunctionEditTrope: { header: "Nyní dostupné: úprava vlastností", content: "V nové verzi OntoGrapheru můžete upravovat vlastnosti (jejich definice a synonyma) pojmů v detailu daného pojmu. Stačí v seznamu vlastností pojmu kliknout na Info ikonku, která se zobrazí při najetí myší na danou vlastnost." + }, + newFunctionDescriptionAndSources: { + header: "Nyní dostupné: úprava zdrojů a popisů", + content: "V nové verzi OntoGrapheru nyní lze upravovat zdroje (dct:source) a popisy (skos:scopeNote) v detailu pojmů a vybraných vztahů." } }; diff --git a/src/locale/en.ts b/src/locale/en.ts index 001bc8f9..31919037 100644 --- a/src/locale/en.ts +++ b/src/locale/en.ts @@ -77,6 +77,8 @@ export const en = { detailPanelPrefLabel: "Label", detailPanelAltLabel: "Synonyms", detailPanelInScheme: "In vocabulary", + detailPanelSource: "Source", + detailPanelDescription: "Description", detailPanelDefinition: "Definition", detailPanelStereotype: "Stereotype", setCanvasLanguage: "Displayed vocabulary language", diff --git a/src/locale/entoast.ts b/src/locale/entoast.ts index a9cd45d4..435dadad 100644 --- a/src/locale/entoast.ts +++ b/src/locale/entoast.ts @@ -14,5 +14,9 @@ export const enToast: { newFunctionEditTrope: { header: "Trope editing now available", content: "In the new version of OntoGrapher, you can edit tropes (their definitions and synonyms) within the detail panel of the parent term. In the list of tropes, click the info icon that shows when you point the mouse at a trope." + }, + newFunctionDescriptionAndSources: { + header: "Description and source editing now available", + content: "In the new version of OntoGrapher, you can edit descriptions (skos:scopeNote) and sources (dct:source) within the detail panel of terms and connections." } }; diff --git a/src/main/App.tsx b/src/main/App.tsx index 1f68809d..e0839ffe 100644 --- a/src/main/App.tsx +++ b/src/main/App.tsx @@ -161,6 +161,7 @@ export default class App extends React.Component< }); this.handleWorkspaceReady(); callToast("newFunctionEditTrope"); + callToast("newFunctionDescriptionAndSources"); }; if (Environment.debug && loadDebugData()) finishUp(); else diff --git a/src/panels/detail/components/LinkControls.tsx b/src/panels/detail/components/LinkControls.tsx index 2e1693d4..6cdbb094 100644 --- a/src/panels/detail/components/LinkControls.tsx +++ b/src/panels/detail/components/LinkControls.tsx @@ -1,12 +1,9 @@ -import _ from "lodash"; import React, { useRef, useState } from "react"; import { CloseButton } from "react-bootstrap"; import { LanguageSelector } from "../../../components/LanguageSelector"; import { LinkType, Representation } from "../../../config/Enum"; -import { LanguageObject } from "../../../config/Languages"; import { Locale } from "../../../config/Locale"; import { - AlternativeLabel, AppSettings, CardinalityPool, WorkspaceElements, @@ -15,11 +12,7 @@ import { WorkspaceVocabularies, } from "../../../config/Variables"; import { Cardinality } from "../../../datatypes/Cardinality"; -import { - getDisplayLabel, - getSelectedLabels, -} from "../../../function/FunctionDraw"; -import { initLanguageObject } from "../../../function/FunctionEditVars"; +import { getDisplayLabel } from "../../../function/FunctionDraw"; import { getLabelOrBlank, getLinkOrVocabElem, @@ -32,9 +25,8 @@ import { graph } from "../../../graph/Graph"; import { updateTermConnections } from "../../../queries/update/UpdateConnectionQueries"; import { updateProjectElement } from "../../../queries/update/UpdateElementQueries"; import { updateProjectLink } from "../../../queries/update/UpdateLinkQueries"; -import { IntrinsicTropeControls } from "./IntrinsicTropeControls"; -import { DetailPanelAltLabels } from "./description/DetailPanelAltLabels"; import { DetailPanelCardinalities } from "./description/DetailPanelCardinalities"; +import { DetailElementDescription } from "./element/DetailElementDescription"; interface Props { id: string; @@ -47,10 +39,6 @@ interface Props { export const LinkControls: React.FC = (props: Props) => { const [sourceCardinality, setSourceCardinality] = useState("0"); const [targetCardinality, setTargetCardinality] = useState("0"); - const [inputAltLabels, setInputAltLabels] = useState([]); - const [selectedLabel, setSelectedLabel] = useState( - initLanguageObject("") - ); const [readOnly, setReadOnly] = useState(false); const [selectedLanguage, setSelectedLanguage] = useState( AppSettings.canvasLanguage @@ -116,7 +104,6 @@ export const LinkControls: React.FC = (props: Props) => { prevPropsID.current !== props.id ) { prevPropsID.current = props.id; - const iri = WorkspaceLinks[props.id].iri; const sourceCardinality = CardinalityPool.findIndex( (card) => card.getString() === @@ -133,14 +120,6 @@ export const LinkControls: React.FC = (props: Props) => { setTargetCardinality( targetCardinality === -1 ? "0" : targetCardinality.toString(10) ); - setInputAltLabels( - iri in WorkspaceTerms ? WorkspaceTerms[iri].altLabels : [] - ); - setSelectedLabel( - iri in WorkspaceElements - ? getSelectedLabels(iri, AppSettings.canvasLanguage) - : initLanguageObject("") - ); setReadOnly(isReadOnly(props.id)); } @@ -206,62 +185,14 @@ export const LinkControls: React.FC = (props: Props) => { }} /> {AppSettings.representation === Representation.COMPACT && - WorkspaceLinks[props.id].type === LinkType.DEFAULT && ( - <> -
{Locale[AppSettings.interfaceLanguage].detailPanelAltLabel}
- { - const newAL = [...inputAltLabels, alt]; - setInputAltLabels(newAL); - WorkspaceTerms[WorkspaceLinks[props.id].iri].altLabels = newAL; - save(); - }} - id={WorkspaceLinks[props.id].iri} - selectDisplayLabel={(name, language) => { - const newSL = { - ...selectedLabel, - [language]: name, - }; - WorkspaceElements[WorkspaceLinks[props.id].iri].selectedLabel = - newSL; - setSelectedLabel(newSL); - save(); - }} - deleteAltLabel={(alt: AlternativeLabel) => { - if (selectedLabel[selectedLanguage] === alt.label) { - const newSL = { - ...selectedLabel, - [selectedLanguage]: - WorkspaceTerms[WorkspaceLinks[props.id].iri].labels[ - selectedLanguage - ], - }; - WorkspaceElements[ - WorkspaceLinks[props.id].iri - ].selectedLabel = newSL; - setSelectedLabel(newSL); - } - const newAL = _.without(inputAltLabels, alt); - setInputAltLabels(newAL); - WorkspaceTerms[WorkspaceLinks[props.id].iri].altLabels = newAL; - save(); - }} - /> - {WorkspaceLinks[props.id].iri in WorkspaceTerms && ( - - )} - + WorkspaceLinks[props.id].type === LinkType.DEFAULT && + props.id && ( + )} ); diff --git a/src/panels/detail/components/element/DetailElementDescription.tsx b/src/panels/detail/components/element/DetailElementDescription.tsx new file mode 100644 index 00000000..7d3bcfd8 --- /dev/null +++ b/src/panels/detail/components/element/DetailElementDescription.tsx @@ -0,0 +1,400 @@ +import * as _ from "lodash"; +import React from "react"; +import { Form } from "react-bootstrap"; +import { Representation } from "../../../../config/Enum"; +import { LanguageObject } from "../../../../config/Languages"; +import { Locale } from "../../../../config/Locale"; +import { + AlternativeLabel, + AppSettings, + Stereotypes, + WorkspaceElements, + WorkspaceTerms, + WorkspaceVocabularies, +} from "../../../../config/Variables"; +import { Shapes } from "../../../../config/visual/Shapes"; +import { + drawGraphElement, + getSelectedLabels, +} from "../../../../function/FunctionDraw"; +import { parsePrefix } from "../../../../function/FunctionEditVars"; +import { + isElementVisible, + resizeElem, +} from "../../../../function/FunctionElem"; +import { + filterEquivalent, + getEquivalents, + isEquivalent, +} from "../../../../function/FunctionEquivalents"; +import { + getParentOfIntrinsicTropeType, + getVocabularyFromScheme, +} from "../../../../function/FunctionGetVars"; +import { graph } from "../../../../graph/Graph"; +import { updateProjectElement } from "../../../../queries/update/UpdateElementQueries"; +import { IntrinsicTropeControls } from "../IntrinsicTropeControls"; +import { DetailPanelAltLabels } from "../description/DetailPanelAltLabels"; + +type Props = { + id: string; + performTransaction: (...queries: string[]) => void; + selectedLanguage: string; + save: (id: string) => void; + infoFunction?: (trope: string) => void; +}; + +type State = { + inputTypeType: string; + inputTypeData: string; + inputAltLabels: AlternativeLabel[]; + inputDefinitions: { [key: string]: string }; + inputDescriptions: LanguageObject; + inputSource: string; + selectedLabel: { [key: string]: string }; + readOnly: boolean; + changes: boolean; + modalTropes: boolean; + hoveredTrope: number; +}; + +export class DetailElementDescription extends React.Component { + constructor(props: Props) { + super(props); + this.state = { + inputTypeType: "", + inputTypeData: "", + inputAltLabels: [], + inputDefinitions: {}, + inputDescriptions: {}, + inputSource: "", + selectedLabel: {}, + readOnly: true, + changes: false, + modalTropes: false, + hoveredTrope: -1, + }; + } + + componentDidMount() { + this.prepareDetails(this.props.id); + } + + getName(element: string, language: string): string { + if (element in Stereotypes) { + return Stereotypes[element].labels[language]; + } else { + return WorkspaceTerms[element].labels[language]; + } + } + + getStereotypes(type: "type" | "data") { + let input: string = ""; + let stereotypes: string[] = []; + // object-type, relator-type, trope-type, event-type + if (type === "type") { + input = this.state.inputTypeType; + stereotypes = Object.keys(Stereotypes).filter((stereotype) => + // filter for types within representation/view + isElementVisible([stereotype], AppSettings.representation, true) + ); + } + // kind, subkind, mixin... + if (type === "data") { + input = this.state.inputTypeData; + stereotypes = Object.keys(Stereotypes).filter( + // filter for non-types + (stereotype) => !filterEquivalent(Object.keys(Shapes), stereotype) + ); + } + // filter for uniques + stereotypes = _.uniqWith(stereotypes, (a, b) => isEquivalent(a, b)); + if (input && !stereotypes.includes(input)) { + // if there is a input set, filter duplicates + stereotypes = stereotypes + .concat(input) + .filter((s) => s === input || (s !== input && !isEquivalent(s, input))); + } + return stereotypes.sort(); + } + + prepareDetails(id?: string) { + if (id) + this.setState({ + selectedLabel: getSelectedLabels(id, this.props.selectedLanguage), + inputTypeType: + WorkspaceTerms[id].types.find( + (type) => + type in Stereotypes && filterEquivalent(Object.keys(Shapes), type) + ) || "", + inputTypeData: + WorkspaceTerms[id].types.find( + (type) => + type in Stereotypes && + !filterEquivalent(Object.keys(Shapes), type) + ) || "", + inputAltLabels: WorkspaceTerms[id].altLabels, + inputDefinitions: WorkspaceTerms[id].definitions, + inputDescriptions: WorkspaceTerms[id].descriptions, + inputSource: WorkspaceTerms[id].source, + changes: false, + readOnly: + WorkspaceVocabularies[ + getVocabularyFromScheme(WorkspaceTerms[id].inScheme) + ].readOnly, + }); + } + + componentWillUnmount() { + if (!this.state.readOnly) this.save(); + } + + componentDidUpdate(prevProps: Readonly, prevState: Readonly) { + if (prevState.changes !== this.state.changes && this.state.changes) { + this.save(); + } + if (prevProps.id !== this.props.id && this.props.id) { + this.prepareDetails(this.props.id); + } + } + + updateStereotype(newStereotype: string) { + const stereotypes = WorkspaceTerms[this.props.id].types.filter( + (stereotype) => !(stereotype in Stereotypes) + ); + if (newStereotype !== "") stereotypes.push(newStereotype); + stereotypes.unshift(this.state.inputTypeType); + WorkspaceTerms[this.props.id].types = stereotypes; + this.setState({ + changes: true, + inputTypeData: newStereotype, + }); + } + + updateType(newType: string) { + const stereotypes = WorkspaceTerms[this.props.id].types.filter( + (stereotype) => !(stereotype in Stereotypes) + ); + if (newType !== "") stereotypes.push(newType); + const dataStereotype = this.isObjectType(stereotypes) + ? this.state.inputTypeData + : ""; + if (dataStereotype !== "") stereotypes.push(dataStereotype); + WorkspaceTerms[this.props.id].types = stereotypes; + this.setState({ + changes: true, + inputTypeType: newType, + inputTypeData: dataStereotype, + }); + } + + isObjectType = (types: string[]) => + types.find((t) => + isEquivalent(t, parsePrefix("z-sgov-pojem", "typ-objektu")) + ); + + save() { + const elem = graph.getElements().find((elem) => elem.id === this.props.id); + if (this.props.id in WorkspaceElements && !this.state.readOnly) { + WorkspaceTerms[this.props.id].altLabels = this.state.inputAltLabels; + WorkspaceTerms[this.props.id].definitions = this.state.inputDefinitions; + WorkspaceTerms[this.props.id].descriptions = this.state.inputDescriptions; + WorkspaceTerms[this.props.id].source = this.state.inputSource; + WorkspaceElements[this.props.id].selectedLabel = this.state.selectedLabel; + if (elem) { + drawGraphElement( + elem, + this.props.selectedLanguage, + AppSettings.representation + ); + } + if ( + getEquivalents(parsePrefix("z-sgov-pojem", "typ-vlastnosti")).includes( + this.state.inputTypeType + ) + ) { + getParentOfIntrinsicTropeType(this.props.id).forEach((id) => { + const elem = graph.getElements().find((elem) => elem.id === id); + if (elem) + drawGraphElement( + elem, + this.props.selectedLanguage, + AppSettings.representation + ); + }); + } + if (elem && AppSettings.selectedElements.includes(this.props.id)) + resizeElem(this.props.id); + this.props.save(this.props.id); + this.setState({ changes: false }); + this.props.performTransaction(updateProjectElement(true, this.props.id)); + } else { + throw new Error("Attempted write to a read-only term."); + } + } + + render() { + return ( + <> +
{Locale[AppSettings.interfaceLanguage].detailPanelAltLabel}
+ + this.setState((prev) => ({ + ...prev, + inputAltLabels: [...prev.inputAltLabels, alt], + changes: true, + })) + } + id={this.props.id} + selectDisplayLabel={(name, language) => + this.setState((prev) => ({ + changes: true, + selectedLabel: { ...prev.selectedLabel, [language]: name }, + })) + } + deleteAltLabel={(alt: AlternativeLabel) => { + this.setState((prev) => ({ + changes: true, + inputAltLabels: _.without(prev.inputAltLabels, alt), + selectedLabel: + prev.selectedLabel[this.props.selectedLanguage] === alt.label + ? { + ...prev.selectedLabel, + [this.props.selectedLanguage]: + WorkspaceTerms[this.props.id].labels[ + this.props.selectedLanguage + ], + } + : prev.selectedLabel, + })); + }} + /> + {isElementVisible( + [this.state.inputTypeType], + AppSettings.representation + ) && ( + <> +
+ {Locale[AppSettings.interfaceLanguage].detailPanelStereotype} +
+ this.updateType(event.target.value)} + > + + {this.getStereotypes("type").map((stereotype) => ( + + ))} + + this.updateStereotype(event.target.value)} + > + + {this.getStereotypes("data").map((stereotype) => ( + + ))} + + + )} + +
{Locale[AppSettings.interfaceLanguage].detailPanelSource}
+ { + if (!this.state.readOnly) + this.setState({ inputSource: event.target.value }); + }} + onBlur={() => { + if (!this.state.readOnly) this.setState({ changes: true }); + }} + /> +
{Locale[AppSettings.interfaceLanguage].detailPanelDefinition}
+ { + if (!this.state.readOnly) + this.setState((prev) => ({ + ...prev, + inputDefinitions: { + ...prev.inputDefinitions, + [this.props.selectedLanguage]: event.target.value, + }, + })); + }} + onBlur={() => { + if (!this.state.readOnly) this.setState({ changes: true }); + }} + /> +
{Locale[AppSettings.interfaceLanguage].detailPanelDescription}
+ { + if (!this.state.readOnly) + this.setState((prev) => ({ + ...prev, + inputDescriptions: { + ...prev.inputDescriptions, + [this.props.selectedLanguage]: event.target.value, + }, + })); + }} + onBlur={() => { + if (!this.state.readOnly) this.setState({ changes: true }); + }} + /> + {AppSettings.representation === Representation.COMPACT && + this.props.infoFunction && ( + this.props.infoFunction!(trope)} + /> + )} + + ); + } +} diff --git a/src/panels/detail/components/element/DetailElementDescriptionCard.tsx b/src/panels/detail/components/element/DetailElementDescriptionCard.tsx index 61bcbe9a..87bb9099 100644 --- a/src/panels/detail/components/element/DetailElementDescriptionCard.tsx +++ b/src/panels/detail/components/element/DetailElementDescriptionCard.tsx @@ -1,39 +1,8 @@ -import * as _ from "lodash"; import React from "react"; -import { Accordion, Form } from "react-bootstrap"; -import { Representation } from "../../../../config/Enum"; +import { Accordion } from "react-bootstrap"; import { Locale } from "../../../../config/Locale"; -import { - AlternativeLabel, - AppSettings, - Stereotypes, - WorkspaceElements, - WorkspaceTerms, - WorkspaceVocabularies, -} from "../../../../config/Variables"; -import { Shapes } from "../../../../config/visual/Shapes"; -import { - drawGraphElement, - getSelectedLabels, -} from "../../../../function/FunctionDraw"; -import { parsePrefix } from "../../../../function/FunctionEditVars"; -import { - isElementVisible, - resizeElem, -} from "../../../../function/FunctionElem"; -import { - filterEquivalent, - getEquivalents, - isEquivalent, -} from "../../../../function/FunctionEquivalents"; -import { - getParentOfIntrinsicTropeType, - getVocabularyFromScheme, -} from "../../../../function/FunctionGetVars"; -import { graph } from "../../../../graph/Graph"; -import { updateProjectElement } from "../../../../queries/update/UpdateElementQueries"; -import { IntrinsicTropeControls } from "../IntrinsicTropeControls"; -import { DetailPanelAltLabels } from "../description/DetailPanelAltLabels"; +import { AppSettings } from "../../../../config/Variables"; +import { DetailElementDescription } from "./DetailElementDescription"; type Props = { id: string; @@ -43,190 +12,7 @@ type Props = { infoFunction?: (trope: string) => void; }; -type State = { - inputTypeType: string; - inputTypeData: string; - inputAltLabels: AlternativeLabel[]; - inputDefinitions: { [key: string]: string }; - selectedLabel: { [key: string]: string }; - readOnly: boolean; - changes: boolean; - modalTropes: boolean; - hoveredTrope: number; -}; - -export class DetailElementDescriptionCard extends React.Component< - Props, - State -> { - constructor(props: Props) { - super(props); - this.state = { - inputTypeType: "", - inputTypeData: "", - inputAltLabels: [], - inputDefinitions: {}, - selectedLabel: {}, - readOnly: true, - changes: false, - modalTropes: false, - hoveredTrope: -1, - }; - } - - componentDidMount() { - this.prepareDetails(this.props.id); - } - - getName(element: string, language: string): string { - if (element in Stereotypes) { - return Stereotypes[element].labels[language]; - } else { - return WorkspaceTerms[element].labels[language]; - } - } - - getStereotypes(type: "type" | "data") { - let input: string = ""; - let stereotypes: string[] = []; - // object-type, relator-type, trope-type, event-type - if (type === "type") { - input = this.state.inputTypeType; - stereotypes = Object.keys(Stereotypes).filter((stereotype) => - // filter for types within representation/view - isElementVisible([stereotype], AppSettings.representation, true) - ); - } - // kind, subkind, mixin... - if (type === "data") { - input = this.state.inputTypeData; - stereotypes = Object.keys(Stereotypes).filter( - // filter for non-types - (stereotype) => !filterEquivalent(Object.keys(Shapes), stereotype) - ); - } - // filter for uniques - stereotypes = _.uniqWith(stereotypes, (a, b) => isEquivalent(a, b)); - if (input && !stereotypes.includes(input)) { - // if there is a input set, filter duplicates - stereotypes = stereotypes - .concat(input) - .filter((s) => s === input || (s !== input && !isEquivalent(s, input))); - } - return stereotypes.sort(); - } - - prepareDetails(id?: string) { - if (id) - this.setState({ - selectedLabel: getSelectedLabels(id, this.props.selectedLanguage), - inputTypeType: - WorkspaceTerms[id].types.find( - (type) => - type in Stereotypes && filterEquivalent(Object.keys(Shapes), type) - ) || "", - inputTypeData: - WorkspaceTerms[id].types.find( - (type) => - type in Stereotypes && - !filterEquivalent(Object.keys(Shapes), type) - ) || "", - inputAltLabels: WorkspaceTerms[id].altLabels, - inputDefinitions: WorkspaceTerms[id].definitions, - changes: false, - readOnly: - WorkspaceVocabularies[ - getVocabularyFromScheme(WorkspaceTerms[id].inScheme) - ].readOnly, - }); - } - - componentWillUnmount() { - if (!this.state.readOnly) this.save(); - } - - componentDidUpdate(prevProps: Readonly, prevState: Readonly) { - if (prevState.changes !== this.state.changes && this.state.changes) { - this.save(); - } - if (prevProps.id !== this.props.id && this.props.id) { - this.prepareDetails(this.props.id); - } - } - - updateStereotype(newStereotype: string) { - const stereotypes = WorkspaceTerms[this.props.id].types.filter( - (stereotype) => !(stereotype in Stereotypes) - ); - if (newStereotype !== "") stereotypes.push(newStereotype); - stereotypes.unshift(this.state.inputTypeType); - WorkspaceTerms[this.props.id].types = stereotypes; - this.setState({ - changes: true, - inputTypeData: newStereotype, - }); - } - - updateType(newType: string) { - const stereotypes = WorkspaceTerms[this.props.id].types.filter( - (stereotype) => !(stereotype in Stereotypes) - ); - if (newType !== "") stereotypes.push(newType); - const dataStereotype = this.isObjectType(stereotypes) - ? this.state.inputTypeData - : ""; - if (dataStereotype !== "") stereotypes.push(dataStereotype); - WorkspaceTerms[this.props.id].types = stereotypes; - this.setState({ - changes: true, - inputTypeType: newType, - inputTypeData: dataStereotype, - }); - } - - isObjectType = (types: string[]) => - types.find((t) => - isEquivalent(t, parsePrefix("z-sgov-pojem", "typ-objektu")) - ); - - save() { - const elem = graph.getElements().find((elem) => elem.id === this.props.id); - if (this.props.id in WorkspaceElements && !this.state.readOnly) { - WorkspaceTerms[this.props.id].altLabels = this.state.inputAltLabels; - WorkspaceTerms[this.props.id].definitions = this.state.inputDefinitions; - WorkspaceElements[this.props.id].selectedLabel = this.state.selectedLabel; - if (elem) { - drawGraphElement( - elem, - this.props.selectedLanguage, - AppSettings.representation - ); - } - if ( - getEquivalents(parsePrefix("z-sgov-pojem", "typ-vlastnosti")).includes( - this.state.inputTypeType - ) - ) { - getParentOfIntrinsicTropeType(this.props.id).forEach((id) => { - const elem = graph.getElements().find((elem) => elem.id === id); - if (elem) - drawGraphElement( - elem, - this.props.selectedLanguage, - AppSettings.representation - ); - }); - } - if (elem && AppSettings.selectedElements.includes(this.props.id)) - resizeElem(this.props.id); - this.props.save(this.props.id); - this.setState({ changes: false }); - this.props.performTransaction(updateProjectElement(true, this.props.id)); - } else { - throw new Error("Attempted write to a read-only term."); - } - } - +export class DetailElementDescriptionCard extends React.Component { render() { return ( @@ -234,128 +20,13 @@ export class DetailElementDescriptionCard extends React.Component< {Locale[AppSettings.interfaceLanguage].description} - {this.props.infoFunction && ( - <> -
- {Locale[AppSettings.interfaceLanguage].detailPanelAltLabel} -
- - this.setState((prev) => ({ - ...prev, - inputAltLabels: [...prev.inputAltLabels, alt], - changes: true, - })) - } - id={this.props.id} - selectDisplayLabel={(name, language) => - this.setState((prev) => ({ - changes: true, - selectedLabel: { ...prev.selectedLabel, [language]: name }, - })) - } - deleteAltLabel={(alt: AlternativeLabel) => { - this.setState((prev) => ({ - changes: true, - inputAltLabels: _.without(prev.inputAltLabels, alt), - selectedLabel: - prev.selectedLabel[this.props.selectedLanguage] === - alt.label - ? { - ...prev.selectedLabel, - [this.props.selectedLanguage]: - WorkspaceTerms[this.props.id].labels[ - this.props.selectedLanguage - ], - } - : prev.selectedLabel, - })); - }} - /> -
- {Locale[AppSettings.interfaceLanguage].detailPanelStereotype} -
- this.updateType(event.target.value)} - > - - {this.getStereotypes("type").map((stereotype) => ( - - ))} - - this.updateStereotype(event.target.value)} - > - - {this.getStereotypes("data").map((stereotype) => ( - - ))} - - - )} -
{Locale[AppSettings.interfaceLanguage].detailPanelDefinition}
- { - if (!this.state.readOnly) - this.setState((prev) => ({ - ...prev, - inputDefinitions: { - ...prev.inputDefinitions, - [this.props.selectedLanguage]: event.target.value, - }, - })); - }} - onBlur={() => { - if (!this.state.readOnly) this.setState({ changes: true }); - }} + - {AppSettings.representation === Representation.COMPACT && - this.props.infoFunction && ( - - this.props.infoFunction!(trope) - } - /> - )}
); diff --git a/src/panels/detail/components/element/TropeOverlay.tsx b/src/panels/detail/components/element/TropeOverlay.tsx index 22b78e44..af1d4ccf 100644 --- a/src/panels/detail/components/element/TropeOverlay.tsx +++ b/src/panels/detail/components/element/TropeOverlay.tsx @@ -1,33 +1,22 @@ import classNames from "classnames"; -import _ from "lodash"; -import React, { useEffect, useRef, useState } from "react"; -import { CloseButton, Form } from "react-bootstrap"; +import React, { useState } from "react"; +import { CloseButton } from "react-bootstrap"; import IRILink from "../../../../components/IRILink"; import { LanguageSelector } from "../../../../components/LanguageSelector"; -import { LanguageObject } from "../../../../config/Languages"; -import { Locale } from "../../../../config/Locale"; import { - AlternativeLabel, AppSettings, WorkspaceElements, WorkspaceTerms, - WorkspaceVocabularies, } from "../../../../config/Variables"; -import { - drawGraphElement, - getSelectedLabels, -} from "../../../../function/FunctionDraw"; -import { initLanguageObject } from "../../../../function/FunctionEditVars"; +import { drawGraphElement } from "../../../../function/FunctionDraw"; import { resizeElem } from "../../../../function/FunctionElem"; import { getLabelOrBlank, getLinkOrVocabElem, getParentOfIntrinsicTropeType, - getVocabularyFromScheme, } from "../../../../function/FunctionGetVars"; import { graph } from "../../../../graph/Graph"; -import { updateProjectElement } from "../../../../queries/update/UpdateElementQueries"; -import { DetailPanelAltLabels } from "../description/DetailPanelAltLabels"; +import { DetailElementDescription } from "./DetailElementDescription"; interface Props { projectLanguage: string; @@ -39,23 +28,12 @@ interface Props { } export const TropeOverlay: React.FC = (props: Props) => { - const [inputAltLabels, setInputAltLabels] = useState([]); - const [inputDefinitions, setInputDefinitions] = useState( - initLanguageObject("") - ); - const [selectedLabel, setSelectedLabel] = useState( - initLanguageObject("") - ); - const [readOnly, setReadOnly] = useState(false); const [selectedLanguage, setSelectedLanguage] = useState( AppSettings.canvasLanguage ); - const prevPropsID = useRef(""); - - useEffect(() => { - if (props.id && props.id in WorkspaceTerms && !readOnly) { - WorkspaceElements[props.id].selectedLabel = selectedLabel; + const save = () => { + if (props.id && props.id in WorkspaceTerms) { getParentOfIntrinsicTropeType(props.id).forEach((id) => { const elem = graph.getElements().find((elem) => elem.id === id); if (elem) { @@ -63,44 +41,10 @@ export const TropeOverlay: React.FC = (props: Props) => { resizeElem(id); } }); - } - }, [props.id, readOnly, selectedLabel, selectedLanguage]); - - const save = () => { - if (props.id && props.id in WorkspaceTerms && !readOnly) { - WorkspaceTerms[props.id].altLabels = inputAltLabels; - WorkspaceTerms[props.id].definitions = inputDefinitions; props.save(props.id); - props.performTransaction(updateProjectElement(true, props.id)); } }; - if ( - props.id && - props.id in WorkspaceTerms && - prevPropsID.current !== props.id - ) { - prevPropsID.current = props.id; - setInputDefinitions( - props.id in WorkspaceTerms - ? WorkspaceTerms[props.id].definitions - : initLanguageObject("") - ); - setInputAltLabels( - props.id in WorkspaceTerms ? WorkspaceTerms[props.id].altLabels : [] - ); - setSelectedLabel( - props.id in WorkspaceElements - ? getSelectedLabels(props.id, AppSettings.canvasLanguage) - : initLanguageObject("") - ); - setReadOnly( - WorkspaceVocabularies[ - getVocabularyFromScheme(WorkspaceTerms[props.id].inScheme) - ].readOnly - ); - } - return (
{props.id in WorkspaceElements && ( @@ -132,63 +76,11 @@ export const TropeOverlay: React.FC = (props: Props) => {

-
{Locale[AppSettings.interfaceLanguage].detailPanelAltLabel}
- { - const newAL = [...inputAltLabels, alt]; - setInputAltLabels(newAL); - WorkspaceTerms[props.id].altLabels = newAL; - save(); - }} + { - setSelectedLabel((prev) => ({ - ...prev, - [language]: name, - })); - save(); - }} - deleteAltLabel={(alt: AlternativeLabel) => { - if (selectedLabel[selectedLanguage] === alt.label) { - setSelectedLabel((prev) => ({ - ...prev, - [selectedLanguage]: - WorkspaceTerms[props.id].labels[selectedLanguage], - })); - } - const newAL = _.without(inputAltLabels, alt); - setInputAltLabels(newAL); - WorkspaceTerms[props.id].altLabels = newAL; - save(); - }} - /> -
{Locale[AppSettings.interfaceLanguage].detailPanelDefinition}
- { - if (!readOnly) - setInputDefinitions((prev) => ({ - ...prev, - [selectedLanguage]: event.target.value, - })); - }} - onBlur={(event) => { - if (!readOnly) - setInputDefinitions((prev) => ({ - ...prev, - [selectedLanguage]: event.target.value, - })); - save(); - }} + performTransaction={props.performTransaction} + selectedLanguage={selectedLanguage} + save={save} />
diff --git a/src/panels/menu/modal/export/FunctionExportTerms.ts b/src/panels/menu/modal/export/FunctionExportTerms.ts index 50f460ee..fa47dfa9 100644 --- a/src/panels/menu/modal/export/FunctionExportTerms.ts +++ b/src/panels/menu/modal/export/FunctionExportTerms.ts @@ -1,20 +1,19 @@ import _ from "lodash"; +import { Representation } from "../../../../config/Enum"; import { AppSettings, WorkspaceElements, WorkspaceLinks, WorkspaceTerms, } from "../../../../config/Variables"; -import { processQuery } from "../../../../interface/TransactionInterface"; -import { Representation } from "../../../../config/Enum"; import { parsePrefix } from "../../../../function/FunctionEditVars"; import { isElementHidden, isElementVisible, } from "../../../../function/FunctionElem"; import { - getIntrinsicTropeTypeIDs, getActiveSourceConnections, + getIntrinsicTropeTypeIDs, } from "../../../../function/FunctionGetVars"; type exportTermObject = { [key: string]: string[] }; @@ -23,31 +22,9 @@ export const exportFunctions: { getSuperClassAttributes: (terms: exportTermObject, term: string) => string[]; constructExportTerms: () => exportTermObject; } = { - getSources: async (terms) => { - const query = [ - "PREFIX dct: ", - "select ?term ?source where {", - "?term dct:source ?source.", - `values ?term {<${_.uniq( - Object.keys(terms).concat(_.flatten(Object.values(terms))) - ).join("> <")}>}`, - "}", - ].join(` -`); - return await processQuery(AppSettings.contextEndpoint, query) - .then((response) => response.json()) - .then((data) => { - const r: { [key: string]: string } = {}; - for (const row of data.results.bindings) { - r[row.term.value] = row.source.value; - } - return r; - }) - .catch((e) => { - console.error(e); - return { error: e }; - }); - }, + getSources: async (terms) => + Object.fromEntries(_.intersection(Object.keys(terms), Object.keys(WorkspaceTerms)).map(term => [term, WorkspaceTerms[term].source])) + , getSuperClassAttributes: (terms, term) => { const stack = _.clone(WorkspaceTerms[term].subClassOf); const attributes: string[] = []; diff --git a/src/queries/get/CacheQueries.ts b/src/queries/get/CacheQueries.ts index c41681f2..850f6d27 100644 --- a/src/queries/get/CacheQueries.ts +++ b/src/queries/get/CacheQueries.ts @@ -288,7 +288,7 @@ export async function fetchReadOnlyTerms( "PREFIX skos: ", "PREFIX rdfs: ", "PREFIX dct: ", - "SELECT DISTINCT ?term ?termLabel ?termAltLabel ?termType ?termDefinition ?topConcept ?inverseOnProperty ?restriction ?restrictionPred ?onProperty ?onClass ?target ?subClassOf ?scheme", + "SELECT DISTINCT ?term ?termLabel ?termAltLabel ?termType ?termDefinition ?termDescription ?termSource ?topConcept ?inverseOnProperty ?restriction ?restrictionPred ?onProperty ?onClass ?target ?subClassOf ?scheme", "WHERE {", "GRAPH ?graph {", "?term skos:inScheme ?scheme.", @@ -297,6 +297,8 @@ export async function fetchReadOnlyTerms( "OPTIONAL {?term skos:prefLabel ?termLabel.}", "OPTIONAL {?term skos:altLabel ?termAltLabel.}", "OPTIONAL {?term skos:definition ?termDefinition.}", + "OPTIONAL {?term skos:scopeNote ?termDescription.}", + "OPTIONAL {?term dct:source ?termSource.}", "OPTIONAL {?term rdfs:subClassOf ?subClassOf. ", "filter (!isBlank(?subClassOf)) }", "OPTIONAL {?topConcept skos:hasTopConcept ?term. }", @@ -330,6 +332,8 @@ export async function fetchReadOnlyTerms( inScheme: row.scheme.value, subClassOf: [], restrictions: [], + descriptions: initLanguageObject(""), + source: "" }; } if ( @@ -370,6 +374,21 @@ export async function fetchReadOnlyTerms( result[row.term.value].definitions[row.termDefinition["xml:lang"]] = row.termDefinition.value; } + if (row.termDescription) { + if ( + !( + row.termDescription["xml:lang"] in + result[row.term.value].descriptions + ) + ) + result[row.term.value].descriptions[ + row.termDescription["xml:lang"] + ] = ""; + result[row.term.value].descriptions[row.termDescription["xml:lang"]] = + row.termDescription.value; + } + if (row.termSource) + result[row.term.value].source = row.termSource.value; if (row.topConcept) result[row.term.value].topConcept = row.topConcept.value; if ( diff --git a/src/queries/get/FetchQueries.ts b/src/queries/get/FetchQueries.ts index 8a7e427a..7f45f0f0 100644 --- a/src/queries/get/FetchQueries.ts +++ b/src/queries/get/FetchQueries.ts @@ -227,11 +227,11 @@ export async function fetchRestrictions( graph && "GRAPH <" + graph + "> {", vocabulary ? [ - "?term skos:inScheme ?scheme.", - "<" + - vocabulary + - "> ?scheme.", - ].join(" ") + "?term skos:inScheme ?scheme.", + "<" + + vocabulary + + "> ?scheme.", + ].join(" ") : "?term skos:inScheme <" + scheme + ">.", "?term rdfs:subClassOf ?restriction. ", "?restriction a owl:Restriction .", @@ -244,8 +244,8 @@ export async function fetchRestrictions( targets ? "values ?target {<" + targets.join("> <") + ">}" : "", "FILTER (!isBlank(?target))", "values ?restrictionPred {<" + - Object.keys(RestrictionConfig).join("> <") + - ">}", + Object.keys(RestrictionConfig).join("> <") + + ">}", "}", graph && "}", ].join(` @@ -294,16 +294,17 @@ export async function fetchTerms( "PREFIX skos: ", "PREFIX rdfs: ", "PREFIX z-sgov-pojem: ", - "SELECT ?term ?termLabel ?termAltLabel ?termType ?termDefinition ?topConcept ?subClassOf", + "PREFIX dct: ", + "SELECT ?term ?termLabel ?termAltLabel ?termType ?termDefinition ?termDescription ?termSource ?topConcept ?subClassOf", "WHERE {", graph && "GRAPH <" + graph + "> {", vocabulary ? [ - "?term skos:inScheme ?scheme.", - "<" + - vocabulary + - "> ?scheme.", - ].join(" ") + "?term skos:inScheme ?scheme.", + "<" + + vocabulary + + "> ?scheme.", + ].join(" ") : "?term skos:inScheme <" + scheme + ">.", "?term a ?termType.", terms ? "values ?term {<" + terms.join("> <") + ">}" : "", @@ -311,6 +312,8 @@ export async function fetchTerms( scheme && "?term skos:inScheme ?scheme", "OPTIONAL {?term skos:altLabel ?termAltLabel.}", "OPTIONAL {?term skos:definition ?termDefinition.}", + "OPTIONAL {?term skos:scopeNote ?termDescription.}", + "OPTIONAL {?term dct:source ?termSource.}", "OPTIONAL {?term rdfs:subClassOf ?subClassOf. ", "filter (!isBlank(?subClassOf)) }", "OPTIONAL {?topConcept skos:hasTopConcept ?term. }", @@ -332,6 +335,8 @@ export async function fetchTerms( inScheme: scheme ? scheme : row.scheme.value, subClassOf: [], restrictions: [], + descriptions: initLanguageObject(""), + source: "" }; } if ( @@ -370,6 +375,21 @@ export async function fetchTerms( result[row.term.value].definitions[row.termDefinition["xml:lang"]] = row.termDefinition.value; } + if (row.termDescription) { + if ( + !( + row.termDescription["xml:lang"] in + result[row.term.value].descriptions + ) + ) + result[row.term.value].descriptions[ + row.termDescription["xml:lang"] + ] = ""; + result[row.term.value].descriptions[row.termDescription["xml:lang"]] = + row.termDescription.value; + } + if (row.termSource) + result[row.term.value].source = row.termSource.value; if (row.topConcept) result[row.term.value].topConcept = row.topConcept.value; if ( diff --git a/src/queries/update/UpdateElementQueries.ts b/src/queries/update/UpdateElementQueries.ts index c2a5492e..a9d44406 100644 --- a/src/queries/update/UpdateElementQueries.ts +++ b/src/queries/update/UpdateElementQueries.ts @@ -41,6 +41,10 @@ export function updateProjectElement(del: boolean, ...iris: string[]): string { const definitions = Object.keys(vocabElem.definitions) .filter((lang) => vocabElem.definitions[lang]) .map((lang) => qb.ll(vocabElem.definitions[lang], lang)); + const descriptions = Object.keys(vocabElem.descriptions) + .filter((lang) => vocabElem.descriptions[lang]) + .map((lang) => qb.ll(vocabElem.descriptions[lang], lang)); + const source = vocabElem.source; const ogStatements: string[] = [ qb.s(qb.i(iri), "rdf:type", "og:element"), @@ -63,12 +67,19 @@ export function updateProjectElement(del: boolean, ...iris: string[]): string { qb.s(qb.i(iri), "skos:prefLabel", qb.a(labels), labels.length > 0), qb.s(qb.i(iri), "skos:altLabel", qb.a(altLabels), altLabels.length > 0), qb.s(qb.i(iri), "dc:title", qb.a(names), names.length > 0), + qb.s(qb.i(iri), "dc:source", qb.ll(source), !!source), qb.s( qb.i(iri), "skos:definition", qb.a(definitions), definitions.length > 0 ), + qb.s( + qb.i(iri), + "skos:scopeNote", + qb.a(descriptions), + descriptions.length > 0 + ), qb.s(qb.i(iri), "skos:inScheme", qb.i(scheme)), qb.s( qb.i(scheme), @@ -95,6 +106,8 @@ export function updateProjectElement(del: boolean, ...iris: string[]): string { qb.s(qb.i(iri), "skos:prefLabel", "?labels"), qb.s(qb.i(iri), "skos:altLabel", "?alt"), qb.s(qb.i(iri), "skos:definition", "?definition"), + qb.s(qb.i(iri), "skos:scopeNote", "?description"), + qb.s(qb.i(iri), "dc:source", "?source"), qb.s(qb.i(iri), "dc:title", "?title"), ].map((stmt) => DELETE`${qb.g(graph, [stmt])}`.WHERE`${qb.g(graph, [stmt])}`.build()