diff --git a/package-lock.json b/package-lock.json index cf7872e..82988bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "bootstrap-css": "^4.0.0-alpha.5", "formik": "^2.4.5", "json-schema-to-typescript": "^13.1.2", + "jsonpath": "^1.1.1", "lodash": "^4.17.21", "markdown-it": "^14.1.0", "moment": "^2.30.1", @@ -36,6 +37,7 @@ "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", + "@types/jsonpath": "^0.2.4", "@types/markdown-it": "^14.1.2", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", @@ -1164,6 +1166,13 @@ "version": "7.0.15", "license": "MIT" }, + "node_modules/@types/jsonpath": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.4.tgz", + "integrity": "sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "dev": true, @@ -2131,7 +2140,6 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -2298,6 +2306,110 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "license": "MIT", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/eslint": { "version": "9.11.1", "dev": true, @@ -2433,6 +2545,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/esquery": { "version": "1.6.0", "dev": true, @@ -2473,7 +2597,6 @@ }, "node_modules/esutils": { "version": "2.0.3", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -2554,7 +2677,6 @@ }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "dev": true, "license": "MIT" }, "node_modules/fastq": { @@ -3188,6 +3310,17 @@ "node": ">=6" } }, + "node_modules/jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "license": "MIT", + "dependencies": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" + } + }, "node_modules/jwt-decode": { "version": "4.0.0", "license": "MIT", @@ -4328,6 +4461,15 @@ "dev": true, "license": "MIT" }, + "node_modules/static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "license": "MIT", + "dependencies": { + "escodegen": "^1.8.1" + } + }, "node_modules/std-env": { "version": "3.7.0", "dev": true, @@ -4637,6 +4779,12 @@ "react": ">=15.0.0" } }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.19.8", "license": "MIT" @@ -4945,7 +5093,6 @@ }, "node_modules/word-wrap": { "version": "1.2.5", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index 51e630c..5004d5d 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "bootstrap-css": "^4.0.0-alpha.5", "formik": "^2.4.5", "json-schema-to-typescript": "^13.1.2", + "jsonpath": "^1.1.1", "lodash": "^4.17.21", "markdown-it": "^14.1.0", "moment": "^2.30.1", @@ -40,6 +41,7 @@ "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", + "@types/jsonpath": "^0.2.4", "@types/markdown-it": "^14.1.2", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", diff --git a/src/App.css b/src/App.css index 919ce59..7c22587 100644 --- a/src/App.css +++ b/src/App.css @@ -225,6 +225,10 @@ p { border: 1px solid var(--primary); } +.b-grey { + border: 1px solid var(--grey); +} + .b-none { border: none; } diff --git a/src/app/Types.ts b/src/app/Types.ts index baae8c0..a1d4f73 100644 --- a/src/app/Types.ts +++ b/src/app/Types.ts @@ -59,6 +59,19 @@ export type Filter = { }[] }; +/* Type for a form field */ +export type FormField = { + jsonPath: string, + title: string, + description: string, + type: string, + options?: string[], + mapping?: { + [option: string]: string + }, + required?: boolean +}; + /* Type for a Dropdown item */ export type DropdownItem = { label: string, diff --git a/src/app/types/TaxonomicService.d.ts b/src/app/types/TaxonomicService.d.ts index 5ca8b9e..4360158 100644 --- a/src/app/types/TaxonomicService.d.ts +++ b/src/app/types/TaxonomicService.d.ts @@ -282,7 +282,7 @@ export interface TaxonomicService { * A unique identifier to identify the author; ORCID identifiers are valid */ "schema:identifier": string; - "schema:affiliation": { + "schema:Affiliation"?: { /** * The type of the affiliation */ @@ -324,7 +324,7 @@ export interface TaxonomicService { /** * The organisation the maintainer is affiliated with */ - "schema:affiliation": { + "schema:Affiliation"?: { /** * The type of the affiliation */ diff --git a/src/components/general/formElements/InputField.tsx b/src/components/general/formElements/InputField.tsx new file mode 100644 index 0000000..6debd1c --- /dev/null +++ b/src/components/general/formElements/InputField.tsx @@ -0,0 +1,33 @@ +/* Import Dependencies */ +import { Field } from "formik"; + + +/* Props Type */ +type Props = { + name: string, + title: string +}; + + +/** + * Component that renders an input field for a free text insert + * @param name The name of the form field + * @param title The title that should be displayed along the form field + * @returns JSX Component + */ +const InputField = (props: Props) => { + const { name, title } = props; + + return ( +
+

+ {title} +

+ +
+ ); +}; + +export default InputField; \ No newline at end of file diff --git a/src/components/search/components/TopBar.tsx b/src/components/search/components/TopBar.tsx index 1713ffc..98b4e20 100644 --- a/src/components/search/components/TopBar.tsx +++ b/src/components/search/components/TopBar.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; import { useState, useRef } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams, useNavigate } from 'react-router-dom'; import { Row, Col } from 'react-bootstrap'; /* Import Hooks */ @@ -25,6 +25,7 @@ import { Button } from 'components/general/CustomComponents'; const TopBar = () => { /* Hooks */ const [searchParams] = useSearchParams(); + const navigate = useNavigate(); /* Base variables */ const [filtersToggle, setFiltersToggle] = useState(false); @@ -76,10 +77,7 @@ const TopBar = () => { diff --git a/src/components/taxonomicService/Routes.tsx b/src/components/taxonomicService/Routes.tsx index 951db15..db4ef63 100644 --- a/src/components/taxonomicService/Routes.tsx +++ b/src/components/taxonomicService/Routes.tsx @@ -3,11 +3,13 @@ import { Route } from "react-router-dom"; /* Import Components */ import TaxonomicService from "./TaxonomicService"; +import TaxonomicServiceForm from "./TaxonomicServiceForm"; /* Routes associated with the Taxonomic Service page */ const routes = [ - } /> + } />, + } /> ]; export default routes; \ No newline at end of file diff --git a/src/components/taxonomicService/TaxonomicService.tsx b/src/components/taxonomicService/TaxonomicService.tsx index a6d4307..4b32fac 100644 --- a/src/components/taxonomicService/TaxonomicService.tsx +++ b/src/components/taxonomicService/TaxonomicService.tsx @@ -1,6 +1,5 @@ /* Import Dependencies */ import classNames from 'classnames'; -import moment from 'moment'; import { useState } from 'react'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { Container, Row, Col } from 'react-bootstrap'; @@ -132,9 +131,7 @@ const TaxonomicService = () => { topicDiscipline: taxonomicService.taxonomicService['ods:topicDiscipline'], geographicArea: taxonomicService.taxonomicService['schema:geographicArea'], licence: taxonomicService.taxonomicService['schema:license'], - published: moment(taxonomicService.taxonomicService['schema:datePublished']).format('MMM DD - YYYY'), - paymentModel: taxonomicService.taxonomicService['schema:FundingScheme']?.['schema:Funding']?.['schema:identifier'], - fundingProgram: taxonomicService.taxonomicService['schema:FundingScheme']?.['schema:Funding']?.['schema:description'] + fundingProgram: taxonomicService.taxonomicService['schema:FundingScheme']?.['schema:Funding']?.['schema:identifier'] }} /> diff --git a/src/components/taxonomicService/TaxonomicServiceForm.tsx b/src/components/taxonomicService/TaxonomicServiceForm.tsx new file mode 100644 index 0000000..8190411 --- /dev/null +++ b/src/components/taxonomicService/TaxonomicServiceForm.tsx @@ -0,0 +1,61 @@ +/* Import Dependencies */ +import { Container, Row, Col, Card } from 'react-bootstrap'; + +/* Import Sources */ +import TaxonomicServiceFormJSON from 'sources/forms/TaxonomicServiceForm.json'; + +/* Import Components */ +import Header from 'components/general/header/Header'; +import Footer from 'components/general/footer/Footer'; +import FormBuilder from './taxonomicServiceFormComponents/FormBuilder'; + + +/** + * Component that renders the taxonomic service form + * @returns JSX Component + */ +const TaxonomicServiceForm = () => { + return ( +
+ {/* Render Header */} +
+ + {/* Home page Body */} + + + + + {/* Form title and description */} + + +

+ Suggest a new taxonomic e-service +

+

+ Use this form to suggest new taxonomic e-services or tools that should be listed in the CETAF Marketplace. + Please fill in the required fields and add as much additional information as you can. + The CETAF secretariat will review submissions and may alter your service description before adding + it to the marketplace, or reject it if the suggested resource does not meet CETAF requirements of relevance and quality. +

+ +
+ {/* Form content */} + + + + + +
+ +
+
+ + {/* Render Footer */} + < Footer /> +
+ ); +}; + +export default TaxonomicServiceForm; \ No newline at end of file diff --git a/src/components/taxonomicService/taxonomicServiceFormComponents/BooleanField.tsx b/src/components/taxonomicService/taxonomicServiceFormComponents/BooleanField.tsx new file mode 100644 index 0000000..f5b9a77 --- /dev/null +++ b/src/components/taxonomicService/taxonomicServiceFormComponents/BooleanField.tsx @@ -0,0 +1,50 @@ +/* Import Dependencies */ +import classNames from 'classnames'; +import { Field } from "formik"; +import { Row, Col } from 'react-bootstrap'; + +/* Import Types */ +import { FormField } from "app/Types"; + + +/* Props Type */ +type Props = { + field: FormField +}; + + +/** + * Component that renders an input field for a free text insert + * @param field The provided form field + * @returns JSX Component + */ +const BooleanField = (props: Props) => { + const { field } = props; + + /* Class Names */ + const formFieldClass = classNames({ + 'b-primary': field.required + }); + + return ( +
+ + +

+ {field.title}{field.required ? * : ''} +

+ + + + +
+
+ ); +}; + +export default BooleanField; \ No newline at end of file diff --git a/src/components/taxonomicService/taxonomicServiceFormComponents/DateField.tsx b/src/components/taxonomicService/taxonomicServiceFormComponents/DateField.tsx new file mode 100644 index 0000000..222ec26 --- /dev/null +++ b/src/components/taxonomicService/taxonomicServiceFormComponents/DateField.tsx @@ -0,0 +1,43 @@ +/* Import Dependencies */ +import DatePicker from 'react-datepicker'; + +/* Import Types */ +import { FormField } from "app/Types"; + + +/* Props Type */ +type Props = { + field: FormField, + fieldValue: Date, + SetFieldValue: Function +}; + + +/** + * Component that renders an input field for a date insert + * @param field The provided form field + * @param fieldValue The current value of the field in the form state + * @param SetFieldValue Function to set the value of a field in the form + * @returns JSX Component + */ +const DateField = (props: Props) => { + const { field, fieldValue, SetFieldValue } = props; + + /* Base variables */ + const jsonPath = field.jsonPath.replace('$', ''); + + return ( +
+

+ {field.title}{field.required ? * : ''} +

+ SetFieldValue(jsonPath, date)} + className="w-100 py-1 px-2 br-corner" + wrapperClassName="w-100" + /> +
+ ); +}; + +export default DateField; \ No newline at end of file diff --git a/src/components/taxonomicService/taxonomicServiceFormComponents/FormBuilder.tsx b/src/components/taxonomicService/taxonomicServiceFormComponents/FormBuilder.tsx new file mode 100644 index 0000000..1241aff --- /dev/null +++ b/src/components/taxonomicService/taxonomicServiceFormComponents/FormBuilder.tsx @@ -0,0 +1,207 @@ +/* Import Dependencies */ +import { Formik, Form } from "formik"; +import jp from 'jsonpath'; +import { Row, Col } from 'react-bootstrap'; + +/* Import Types */ +import { FormField, Dict } from "app/Types"; + +/* Import Components */ +import BooleanField from "./BooleanField"; +import DateField from "./DateField"; +import FormBuilderFieldArray from "./FormBuilderFieldArray"; +import MultiSelectField from "./MultiSelectField"; +import SelectField from "./SelectField"; +import StringField from "./StringField"; +import StringArrayField from "./StringArrayField"; +import TextField from "./TextField"; +import { Button } from "components/general/CustomComponents"; + + +/* Props Type */ +type Props = { + formTemplate: { + [formSection: string]: { + title: string, + type: string, + jsonPath?: string, + fields: FormField[] + } + } +}; + + +/** + * Component that renders a form builder for rendering forms based on JSON files + * @param formTemplate The form template to build the form from + * @returns JSX Component + */ +const FormBuilder = (props: Props) => { + const { formTemplate } = props; + + /* Base variables */ + const formSections: { + [section: string]: { + type: string, + jsonPath: string, + fields: FormField[] + } + } = {}; + const initialFormValues: Dict = {}; + + /** + * Function to flatten a JSON path + * @returns flattened JSON path string + */ + const FlattenJSONPath = (jsonPath: string): string => { + return jsonPath.replaceAll('[', '_').replaceAll(']', '').replaceAll("'", ''); + }; + + /** + * Function to determine the initial form field type + * @param fieldType + */ + const DetermineInitialFormValue = (fieldType: string) => { + switch (fieldType) { + case 'boolean': + return false; + case 'array': + return []; + case 'multi-string': + return ['']; + case 'ror': + return { + "schema:identifier": '', + "schema:name": '' + }; + default: + return ''; + }; + }; + + /* Construct initial form values */ + Object.entries(formTemplate).forEach(([_key, formSection]) => { + formSections[formSection.title] = { + type: formSection.type, + jsonPath: formSection.jsonPath ?? '', + fields: [] + }; + + /* If is array, push to initial form values */ + if (formSection.type === 'array') { + jp.value(initialFormValues, formSection.jsonPath ?? '', []); + } + + formSection.fields.forEach(field => { + let jsonPath: string = ''; + + if (formSection.type === 'array') { + let pathSuffix: string = FlattenJSONPath(field.jsonPath).split('_').at(-1) as string; + + jsonPath = jsonPath.concat(`${formSection.jsonPath ?? ''}[0]['${pathSuffix}']`); + + /* Add to initial form values array zero index */ + jp.value(initialFormValues, jsonPath, DetermineInitialFormValue(field.type)); + } else { + /* Add to initial form values */ + jp.value(initialFormValues, field.jsonPath, DetermineInitialFormValue(field.type)); + } + + /* Push to form fields */ + formSections[formSection.title].fields.push(field); + }); + }); + + /* Function to construct form field based upon given field */ + const ConstructFormField = (field: FormField, fieldValues?: any, SetFieldValue?: Function) => { + switch (field.type) { + case 'boolean': { + return ; + } case 'date': { + let dateValue: Date; + + if (fieldValues) { + dateValue = new Date(fieldValues); + } else { + dateValue = new Date(); + } + + return SetFieldValue?.(fieldName, value)} + />; + } case 'multi-string': { + return ; + } case 'select': { + return SetFieldValue?.(fieldName, value)} + />; + } case 'multi-select': { + return SetFieldValue?.(fieldName, value)} + />; + } case 'text': { + return ; + } default: { + return ; + } + }; + }; + + return ( +
+ { + await new Promise((resolve) => setTimeout(resolve, 100)); + }} + > + {({ values, setFieldValue }) => ( +
+ {Object.entries(formSections).map(([title, section]) => ( + + + {section.type === 'object' ? +
+

{`${title}`}

+ + {section.fields.map(field => ( + + + {ConstructFormField(field, jp.value(values, field.jsonPath), setFieldValue)} + + + ))} +
+ : + } + +
+ ))} + + + + + +
+ )} +
+
+ ); +}; + +export default FormBuilder; \ No newline at end of file diff --git a/src/components/taxonomicService/taxonomicServiceFormComponents/FormBuilderFieldArray.tsx b/src/components/taxonomicService/taxonomicServiceFormComponents/FormBuilderFieldArray.tsx new file mode 100644 index 0000000..2870d74 --- /dev/null +++ b/src/components/taxonomicService/taxonomicServiceFormComponents/FormBuilderFieldArray.tsx @@ -0,0 +1,118 @@ +/* Import Dependencies */ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { FieldArray } from "formik"; +import jp from 'jsonpath'; +import { cloneDeep } from "lodash"; +import { Row, Col } from "react-bootstrap"; + +/* Import Utilities */ +import { MakeReadableString } from "app/Utilities"; + +/* Import Types */ +import { FormField, Dict } from "app/Types"; + +/* Import Icons */ +import { faX } from "@fortawesome/free-solid-svg-icons"; + +/* Import Components */ +import { Button } from "components/general/CustomComponents"; + + +/* Props Type */ +type Props = { + section: Dict, + title: string, + initialFormValues: Dict + values: Dict, + formSections: { + [section: string]: { + type: string; + jsonPath: string; + fields: FormField[]; + } + }, + FlattenJSONPath: Function, + ConstructFormField: Function +}; + + +const FormBuilderFieldArray = (props: Props) => { + const { section, title, initialFormValues, values, formSections, FlattenJSONPath, ConstructFormField } = props; + + return ( + + {({ push, remove }) => ( +
+ + +

{`${title}${title.at(-1) !== 'a' ? 's' : ''}`}

+ + + + +
+ + {jp.value(values, section.jsonPath).map((_fields: Dict, index: number) => { + const key = `${section.jsonPath}-${index}`; + + return ( +
+ + +

+ {`${title} #${index + 1}`} +

+ + {jp.value(values, section.jsonPath).length > 1 && + + + + } +
+ + + {formSections[MakeReadableString(FlattenJSONPath(section.jsonPath)).split(' ').slice(1).join(' ')].fields.map(field => { + let localField = cloneDeep(field); + + localField.jsonPath = field.jsonPath.replace('index', String(index)); + + return ( + + + {ConstructFormField(localField, jp.value(values, localField.jsonPath))} + + + ); + })} + + + +
+ ); + })} +
+ )} +
+ ); +}; + +export default FormBuilderFieldArray; \ No newline at end of file diff --git a/src/components/taxonomicService/taxonomicServiceFormComponents/MultiSelectField.tsx b/src/components/taxonomicService/taxonomicServiceFormComponents/MultiSelectField.tsx new file mode 100644 index 0000000..9f1de84 --- /dev/null +++ b/src/components/taxonomicService/taxonomicServiceFormComponents/MultiSelectField.tsx @@ -0,0 +1,63 @@ +/* Import Dependencies */ +import Select from "react-select"; + +/* Import Types */ +import { FormField } from "app/Types"; + + +/* Props Type */ +type Props = { + field: FormField, + SetFieldValue: Function +}; + + +/** + * Component that renders a select field for multi selection + * @param field The provided form field + * @param SetFieldValue Function to set the value of a field in the form + * @returns JSX Component + */ +const MultiSelectField = (props: Props) => { + const { field, SetFieldValue } = props; + + /* Base variables */ + const jsonPath = field.jsonPath.replace('$', ''); + const selectItems: { + label: string, + value: string + }[] = []; + + /* Construct select items */ + field.options?.forEach(option => { + selectItems.push({ + label: field.mapping ? field.mapping[option] : option, + value: option + }); + }); + + return ( +
+

+ {field.title}{field.required ? * : ''} +

+ SetFieldValue(jsonPath, dropdownOption?.value)} + /> +
+ ); +}; + +export default SelectField; \ No newline at end of file diff --git a/src/components/taxonomicService/taxonomicServiceFormComponents/StringArrayField.tsx b/src/components/taxonomicService/taxonomicServiceFormComponents/StringArrayField.tsx new file mode 100644 index 0000000..1374128 --- /dev/null +++ b/src/components/taxonomicService/taxonomicServiceFormComponents/StringArrayField.tsx @@ -0,0 +1,96 @@ +/* Import Dependencies */ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { FieldArray, Field } from "formik"; +import { Row, Col } from 'react-bootstrap'; + +/* Import Types */ +import { FormField } from "app/Types"; + +/* Import Icons */ +import { faX } from "@fortawesome/free-solid-svg-icons"; + +/* Import Components */ +import { Button } from "components/general/CustomComponents"; + + +/* Props Type */ +type Props = { + field: FormField, + fieldValues: string[] +}; + + +/** + * Component that returns a section of an array with string fields in the form + * @param field The provided form field + * @param fieldValues The current values of the field in the form state + * @returns JSX Component + */ +const StringArrayField = (props: Props) => { + const { field, fieldValues } = props; + + /* Base variables */ + const jsonPath = field.jsonPath.replace('$', ''); + + return ( +
+ + {({ push, remove }) => ( +
+ + +

+ {field.title}{field.required ? * : ''} +

+ + + + +
+ {fieldValues.map((_fieldValue, index) => { + const key = `${jsonPath}-${index}`; + + return ( + + + + + {fieldValues.length > 1 && + + + + } + + ); + })} +
+ )} +
+
+ ); +}; + +export default StringArrayField; \ No newline at end of file diff --git a/src/components/taxonomicService/taxonomicServiceFormComponents/StringField.tsx b/src/components/taxonomicService/taxonomicServiceFormComponents/StringField.tsx new file mode 100644 index 0000000..34c47b7 --- /dev/null +++ b/src/components/taxonomicService/taxonomicServiceFormComponents/StringField.tsx @@ -0,0 +1,40 @@ +/* Import Dependencies */ +import classNames from 'classnames'; +import { Field } from "formik"; + +/* Import Types */ +import { FormField } from "app/Types"; + + +/* Props Type */ +type Props = { + field: FormField +}; + + +/** + * Component that renders an input field for a free text insert + * @param field The provided form field + * @returns JSX Component + */ +const StringField = (props: Props) => { + const { field } = props; + + /* Class Names */ + const formFieldClass = classNames({ + 'b-primary': field.required + }); + + return ( +
+

+ {field.title}{field.required ? * : ''} +

+ +
+ ); +}; + +export default StringField; \ No newline at end of file diff --git a/src/components/taxonomicService/taxonomicServiceFormComponents/TextField.tsx b/src/components/taxonomicService/taxonomicServiceFormComponents/TextField.tsx new file mode 100644 index 0000000..7d6dead --- /dev/null +++ b/src/components/taxonomicService/taxonomicServiceFormComponents/TextField.tsx @@ -0,0 +1,42 @@ +/* Import Dependencies */ +import classNames from 'classnames'; +import { Field } from "formik"; + +/* Import Types */ +import { FormField } from "app/Types"; + + +/* Props Type */ +type Props = { + field: FormField +}; + + +/** + * Component that renders an input text field for a free, long text insert + * @param field The provided form field + * @returns JSX Component + */ +const TextField = (props: Props) => { + const { field } = props; + + /* Class Names */ + const formFieldClass = classNames({ + 'b-primary': field.required + }); + + return ( +
+

+ {field.title}{field.required ? * : ''} +

+ +
+ ); +}; + +export default TextField; \ No newline at end of file diff --git a/src/sources/dataModel/taxonomic-service.json b/src/sources/dataModel/taxonomic-service.json index 98d4e89..ebb38f2 100644 --- a/src/sources/dataModel/taxonomic-service.json +++ b/src/sources/dataModel/taxonomic-service.json @@ -518,22 +518,17 @@ "description": "The type of the maintainer", "const": "schema:Person" }, - "schema:identifier": [ - { - "type": "string", - "description": "A unique identifier to identify the maintainer; GitHub identifiers are valid", - "examples": [ - "https://api.github.com/users/username" - ] + "schema:identifier": { + "type": "string", + "description": "A unique identifier to identify the maintainer; GitHub and ORCID identifiers are valid", + "items": { + "type": "string" }, - { - "type": "string", - "description": "A unique identifier to identify the maintainer; ORCID identifiers are valid", - "examples": [ - "https://orcid.org/0000-0001-9790-9277" - ] - } - ], + "examples": [ + "https://api.github.com/users/username", + "https://orcid.org/0000-0001-9790-9277" + ] + }, "schema:name": { "type": "string", "description": "Full name of the maintainer", diff --git a/src/sources/forms/TaxonomicServiceForm.json b/src/sources/forms/TaxonomicServiceForm.json new file mode 100644 index 0000000..5ca0536 --- /dev/null +++ b/src/sources/forms/TaxonomicServiceForm.json @@ -0,0 +1,452 @@ +{ + "taxonomicService": { + "title": "Taxonomic Service", + "type": "object", + "fields": [ + { + "jsonPath": "$['schema:service']['schema:serviceType']", + "title": "Service Type", + "description": "A type that defines the kind of taxonomic service", + "type": "select", + "options": [ + "AI training dataset", + "Community group", + "CrowdSourcing", + "Data tool", + "e-Learning service", + "Factsheet", + "Identification tool", + "Knowledge website", + "Mobile app", + "Service inventory", + "Reference collection", + "Specimen dataset not in GBIF" + ], + "required": true + }, + { + "jsonPath": "$['schema:service']['schema:name']", + "title": "Name", + "description": "The preferred name of the service (can be in any language)", + "type": "string", + "required": true + }, + { + "jsonPath": "$['schema:service']['schema:description']", + "title": "Description", + "description": "A description of the service (english)", + "type": "text", + "required": true + }, + { + "jsonPath": "$['schema:service']['schema:slogan']", + "title": "Slogan", + "description": "A slogan or motto associated with the Service (english)", + "type": "string" + }, + { + "jsonPath": "$['schema:service']['schema:logo']", + "title": "Logo", + "description": "An associated logo URL", + "type": "string" + }, + { + "jsonPath": "$['schema:datePublished']", + "title": "Date Published", + "description": "Date the service was published, following the ISO Date Time Format yyyy-MM-dd'T'HH:mm:ss.SSSXXX", + "type": "date" + }, + { + "jsonPath": "$['schema:service']['schema:additionalProperty'][0]", + "title": "Update Frequency", + "description": "Indicator if the service is frequently updated or not, 'frequently updated' if the resource is updated multiple times a year", + "type": "select", + "options": [ + "published once", + "frequently updated" + ] + }, + { + "jsonPath": "$['schema:service']['schema:termsOfService']", + "title": "Terms of Service", + "description": "URL pointing to Terms of Service, Terms of Use or Terms and Conditions, the legal agreements between a service provider and a person who wants to use that service", + "type": "string" + }, + { + "jsonPath": "$['schema:license']", + "title": "Service License", + "description": "A license document that applies to this content, typically indicated by URL", + "type": "select", + "options": [ + "https://spdx.org/licenses/CC-BY-1.0.json", + "https://spdx.org/licenses/CC-BY-2.0.json", + "https://spdx.org/licenses/CC-BY-2.5.json", + "https://spdx.org/licenses/CC-BY-2.5-AU.json", + "https://spdx.org/licenses/CC-BY-3.0.json", + "https://spdx.org/licenses/CC-BY-3.0-AT.json", + "https://spdx.org/licenses/CC-BY-3.0-AU.json", + "https://spdx.org/licenses/CC-BY-3.0-DE.json", + "https://spdx.org/licenses/CC-BY-3.0-IGO.json", + "https://spdx.org/licenses/CC-BY-3.0-NL.json", + "https://spdx.org/licenses/CC-BY-3.0-US.json", + "https://spdx.org/licenses/CC-BY-4.0.json", + "https://spdx.org/licenses/CC-BY-NC-1.0.json", + "https://spdx.org/licenses/CC-BY-NC-2.0.json", + "https://spdx.org/licenses/CC-BY-NC-2.5.json", + "https://spdx.org/licenses/CC-BY-NC-3.0.json", + "https://spdx.org/licenses/CC-BY-NC-3.0-DE.json", + "https://spdx.org/licenses/CC-BY-NC-4.0.json", + "https://spdx.org/licenses/CC-BY-NC-ND-1.0.json", + "https://spdx.org/licenses/CC-BY-NC-ND-2.0.json", + "https://spdx.org/licenses/CC-BY-NC-ND-2.5.json", + "https://spdx.org/licenses/CC-BY-NC-ND-3.0.json", + "https://spdx.org/licenses/CC-BY-NC-ND-3.0-DE.json", + "https://spdx.org/licenses/CC-BY-NC-ND-3.0-IGO.json", + "https://spdx.org/licenses/CC-BY-NC-ND-4.0.json", + "https://spdx.org/licenses/CC-BY-NC-SA-1.0.json", + "https://spdx.org/licenses/CC-BY-NC-SA-2.0.json", + "https://spdx.org/licenses/CC-BY-NC-SA-2.0-DE.json", + "https://spdx.org/licenses/CC-BY-NC-SA-2.0-FR.json", + "https://spdx.org/licenses/CC-BY-NC-SA-2.0-UK.json", + "https://spdx.org/licenses/CC-BY-NC-SA-2.5.json", + "https://spdx.org/licenses/CC-BY-NC-SA-3.0.json", + "https://spdx.org/licenses/CC-BY-NC-SA-3.0-DE.json", + "https://spdx.org/licenses/CC-BY-NC-SA-3.0-IGO.json", + "https://spdx.org/licenses/CC-BY-NC-SA-4.0.json", + "https://spdx.org/licenses/CC-BY-ND-1.0.json", + "https://spdx.org/licenses/CC-BY-ND-2.0.json", + "https://spdx.org/licenses/CC-BY-ND-2.5.json", + "https://spdx.org/licenses/CC-BY-ND-3.0.json", + "https://spdx.org/licenses/CC-BY-ND-3.0-DE.json", + "https://spdx.org/licenses/CC-BY-ND-4.0.json", + "https://spdx.org/licenses/CC-BY-SA-1.0.json", + "https://spdx.org/licenses/CC-BY-SA-2.0.json", + "https://spdx.org/licenses/CC-BY-SA-2.0-UK.json", + "https://spdx.org/licenses/CC-BY-SA-2.1-JP.json", + "https://spdx.org/licenses/CC-BY-SA-2.5.json", + "https://spdx.org/licenses/CC-BY-SA-3.0.json", + "https://spdx.org/licenses/CC-BY-SA-3.0-AT.json", + "https://spdx.org/licenses/CC-BY-SA-3.0-DE.json", + "https://spdx.org/licenses/CC-BY-SA-3.0-IGO.json", + "https://spdx.org/licenses/CC-BY-SA-4.0.json", + "https://spdx.org/licenses/CC-PDDC.json", + "https://spdx.org/licenses/CC0-1.0.json", + "https://spdx.org/licenses/DL-DE-BY-2.0.json", + "https://spdx.org/licenses/DL-DE-ZERO-2.0.json", + "https://www.govdata.de/dl-de/by-1-0", + "https://www.govdata.de/dl-de/by-nc-1-0", + "https://spdx.org/licenses/EUPL-1.0.json", + "https://spdx.org/licenses/EPL-2.0.json", + "https://spdx.org/licenses/EUPL-1.2.json", + "https://www.etalab.gouv.fr/licence-ouverte-open-licence/", + "https://spdx.org/licenses/GFDL-1.1-invariants-only.json", + "https://spdx.org/licenses/GFDL-1.1-invariants-or-later.json", + "https://spdx.org/licenses/GFDL-1.1-no-invariants-only.json", + "https://spdx.org/licenses/GFDL-1.1-no-invariants-or-later.json", + "https://spdx.org/licenses/GFDL-1.1-only.json", + "https://spdx.org/licenses/GFDL-1.1-or-later.json", + "https://spdx.org/licenses/GFDL-1.2-invariants-only.json", + "https://spdx.org/licenses/GFDL-1.2-invariants-or-later.json", + "https://spdx.org/licenses/GFDL-1.2-no-invariants-only.json", + "https://spdx.org/licenses/GFDL-1.2-no-invariants-or-later.json", + "https://spdx.org/licenses/GFDL-1.2-only.json", + "https://spdx.org/licenses/GFDL-1.2-or-later.json", + "https://spdx.org/licenses/GFDL-1.3-invariants-only.json", + "https://spdx.org/licenses/GFDL-1.3-invariants-or-later.json", + "https://spdx.org/licenses/GFDL-1.3-no-invariants-only.json", + "https://spdx.org/licenses/GFDL-1.3-no-invariants-or-later.json", + "https://spdx.org/licenses/GFDL-1.3-only.json", + "https://spdx.org/licenses/GFDL-1.3-or-later.json", + "https://data.gov.hr/open-licence-republic-croatia", + "https://www.dati.gov.it/iodl/2.0/", + "https://spdx.org/licenses/NLOD-1.0.json", + "https://spdx.org/licenses/NLOD-2.0.json", + "https://spdx.org/licenses/ODC-By-1.0.json", + "https://spdx.org/licenses/ODbL-1.0.json", + "https://spdx.org/licenses/PDDL-1.0.json", + "https://spdx.org/licenses/OGL-Canada-2.0.json", + "https://spdx.org/licenses/OGL-UK-1.0.json", + "https://spdx.org/licenses/OGL-UK-2.0.json", + "https://spdx.org/licenses/OGL-UK-3.0.json", + "https://data.gov.ro/pages/licence" + ] + }, + { + "jsonPath": "$['schema:availableLanguage']", + "title": "Available Languages", + "description": "A language someone may use with or at the item, service or place. Please use one of the language codes from the IETF BCP 47 standard", + "type": "multi-select", + "options": [ + "bg", + "hr", + "cs", + "da", + "lb", + "nl", + "en", + "et", + "fi", + "fr", + "de", + "el", + "hu", + "ga", + "it", + "lv", + "lt", + "mt", + "pl", + "pt", + "ro", + "ru", + "sk", + "es", + "sv", + "tk", + "uk" + ], + "mapping": { + "bg": "Bulgarian", + "hr": "Croatian", + "cs": "Czech", + "da": "Danish", + "lb": "Luxembourgish", + "nl": "Dutch", + "en": "English", + "et": "Estonian", + "fi": "Finnish", + "fr": "French", + "de": "German", + "el": "Greek", + "hu": "Hungarian", + "ga": "Irish", + "it": "Italian", + "lv": "Latvian", + "lt": "Lithuanian", + "mt": "Maltese", + "pl": "Polish", + "pt": "Portugese", + "ro": "Romanian", + "ru": "Russian", + "sk": "Slovak", + "es": "Spanish", + "sv": "Swedish", + "tk": "Turkish", + "uk": "Ukranian" + }, + "required": true + }, + { + "jsonPath": "$['ods:topicDiscipline']", + "title": "Topic Discipline", + "description": "The topic discipline relevant to the taxonomic range of the service", + "type": "select", + "options": [ + "Anthropology", + "Botany", + "Microbiology", + "Palaeontology", + "Zoology", + "Ecology", + "Other Biodiversity", + "Unclassified" + ] + }, + { + "jsonPath": "$['schema:taxonomicRange']", + "title": "Taxonomic Range", + "description": "The taxonomic grouping of the organism, e.g. 'No specific range' or 'Agromyzidae, Braconidae'", + "type": "multi-string" + }, + { + "jsonPath": "$['schema:additionalProperty'][0]", + "title": "Organism Group", + "description": "Common names for groups of organisms", + "type": "multi-string" + }, + { + "jsonPath": "$['schema:geographicArea']", + "title": "Geographic Area", + "description": "The geographic area associated with the service, e.g. World, Palearctic, South-East Europe, Mediterranean", + "type": "string" + }, + { + "jsonPath": "$['schema:url']", + "title": "Webpage", + "description": "URL of the item that leads to the resource of the service", + "type": "string" + }, + { + "jsonPath": "$['schema:documentation']", + "title": "Documentation URL", + "description": "URL to further documentation describing the service in more detail", + "type": "string" + }, + { + "jsonPath": "$['schema:about']", + "title": "Change Log", + "description": "The subject matter of the service including a summary of the Resource features updated from the previous version", + "type": "text" + }, + { + "jsonPath": "$['schema:additionalProperty'][1]", + "title": "Available on App Store", + "description": "Application is available on the following app stores, relevant for apps", + "type": "multi-string" + }, + { + "jsonPath": "$['schema:additionalProperty'][2]", + "title": "Payment Model", + "description": "URL to webpage with the supported payment models and restrictions that apply to the Resource", + "type": "string" + } + ] + }, + "contactPoint": { + "title": "Contact Point", + "type": "object", + "fields": [ + { + "jsonPath": "$['schema:contactPoint']['schema:email']", + "title": "Contact Email", + "description": "Email address of the contact point", + "type": "string" + }, + { + "jsonPath": "$['schema:contactPoint']['schema:url']", + "title": "Contact Webpage", + "description": "URL to a contact webpage or contact form for the resource", + "type": "string" + } + ] + }, + "author": { + "title": "Author", + "jsonPath": "$['schema:author']", + "type": "array", + "fields": [ + { + "jsonPath": "$['schema:author']['index']['schema:identifier']", + "title": "ORCID", + "description": "A unique identifier to identify the author; ORCID identifiers are valid", + "type": "string" + }, + { + "jsonPath": "$['schema:author']['index']['schema:affiliation']", + "title": "Affiliated Organisation", + "description": "The affiliated organisation", + "type": "ror" + } + ] + }, + "maintainer": { + "title": "Maintainer", + "jsonPath": "$['schema:maintainer']", + "type": "array", + "fields": [ + { + "jsonPath": "$['schema:maintainer']['index']['schema:identifier']", + "title": "GitHub / ORCID Identifier", + "description": "A unique identifier to identify the maintainer; ORCID and GitHub identifiers are valid", + "type": "multi-string" + }, + { + "jsonPath": "$['schema:maintainer']['index']['schema:name']", + "title": "Full Name", + "description": "Full name of the maintainer", + "type": "string" + }, + { + "jsonPath": "$['schema:maintainer']['index']['schema:affiliation']", + "title": "Affiliated Organisation", + "description": "The affiliated organisation", + "type": "ror" + } + ] + }, + "funding": { + "title": "Funding", + "type": "object", + "fields": [ + { + "jsonPath": "$['schema:fundingscheme']['schema:award']", + "title": "Award", + "description": "An award won by or for this service", + "type": "string" + }, + { + "jsonPath": "$['schema:fundingscheme']['schema:funding']['schema:identifier']", + "title": "Grant Identifier", + "description": "A unique identifier to identify the funder organisation; ROR identifiers are valid", + "type": "string" + }, + { + "jsonPath": "$['schema:fundingscheme']['schema:funding']['schema:description']", + "title": "Grant Description", + "description": "A description of the service's grant", + "type": "string" + }, + { + "jsonPath": "$['schema:fundingscheme']['schema:funding']['schema:funder']", + "title": "Funding Organisation", + "description": "An organization that supports (sponsors) something through some kind of financial contribution", + "type": "ror" + } + ] + }, + "softwareSourceCode": { + "title": "Software Source Code", + "type": "object", + "fields": [ + { + "jsonPath": "$['schema:softwareSourceCode']['schema:codeRepository']", + "title": "Code Repository", + "description": "Link to the repository where the un-compiled, human readable code and related code is located (SVN, GitHub, CodePlex)", + "type": "string" + }, + { + "jsonPath": "$['schema:softwareSourceCode']['schema:runtimePlatform']", + "title": "Runtime Platform", + "description": "Runtime platform or script interpreter dependencies (example: Java v1, Python 2.3, .NET Framework 3.0)", + "type": "string" + }, + { + "jsonPath": "$['schema:softwareSourceCode']['schema:creativeWorkStatus']", + "title": "Status", + "description": "The status of a creative work in terms of its stage in a lifecycle. Example terms include Incomplete, Draft, Published, Obsolete. Some organizations define a set of terms for the stages of their publication lifecycle", + "type": "string" + }, + { + "jsonPath": "$['schema:softwareSourceCode']['schema:programmingLanguage']", + "title": "Programming Languages", + "description": "The computer programming language", + "type": "string" + }, + { + "jsonPath": "$['schema:softwareSourceCode']['schema:license']", + "title": "Software License", + "description": "A license document that applies to this content, typically indicated by URL", + "type": "string" + }, + { + "jsonPath": "$['schema:softwareSourceCode']['schema:additionalProperty'][0]", + "title": "Is Open Source", + "description": "Boolean indicating is the software source code is available by open source", + "type": "boolean" + } + ] + }, + "associatedMedia": { + "title": "Associated Media", + "jsonPath": "$['schema:associatedMedia']", + "type": "array", + "fields": [ + { + "jsonPath": "$['schema:associatedMedia']['index']['schema:contentUrl']", + "title": "Content URL", + "description": "JPG, PNG or SVG file showing a screenshot or other relevant illustration of the resource. Add only files that are public domain", + "type": "string" + } + ] + } +} \ No newline at end of file diff --git a/src/sources/taxonomicServiceForm/TaxonomicServiceFormFields.json b/src/sources/taxonomicServiceForm/TaxonomicServiceFormFields.json new file mode 100644 index 0000000..8377cc5 --- /dev/null +++ b/src/sources/taxonomicServiceForm/TaxonomicServiceFormFields.json @@ -0,0 +1,256 @@ +{ + "taxonomicService": { + "title": "Taxonomic Service", + "formFields": [ + { + "name": "", + "title": "Service Type", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Name", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Description", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Slogan", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Logo", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Date Published", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Update Frequency", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Terms of Service", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Service License", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Available Languages", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Topic Discipline", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Taxonomic Range", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Organism Group", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Geographic Area", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Webpage", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Documentation URL", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Change Log", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Payment Model", + "description": "", + "type": "" + } + ] + }, + "contactPoint": { + "title": "Contact Point", + "fields": [ + { + "name": "", + "title": "Contact Email", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Contact Webpage", + "description": "", + "type": "" + }, + { + "name": "", + "title": "", + "description": "", + "type": "" + } + ] + }, + "author": { + "title": "Author", + "type": "array", + "fields": [ + { + "name": "", + "title": "ORCID", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Affiliated Organisation", + "description": "", + "type": "" + } + ] + }, + "maintainer": { + "title": "Maintainer", + "type": "array", + "fields": [ + { + "name": "", + "title": "GitHub Identifier", + "description": "", + "type": "" + }, + { + "name": "", + "title": "ORCID", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Full Name", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Affiliated Organisation", + "description": "", + "type": "ROR" + } + ] + }, + "funding": { + "title": "Funding", + "fields": [ + { + "name": "", + "title": "Award", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Grant", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Funding Organisation", + "description": "", + "type": "" + } + ] + }, + "softwareSourceCode": { + "title": "Software Source Code", + "fields": [ + { + "name": "", + "title": "Code Repository", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Runtime Platform", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Status", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Programming Languages", + "description": "", + "type": "" + }, + { + "name": "", + "title": "Software License", + "description": "", + "type": "" + } + ] + }, + "associatedMedia": { + "title": "Associated Media", + "type": "array", + "fields": [ + { + "name": "", + "title": "Content URL", + "description": "", + "type": "" + } + ] + } +} \ No newline at end of file