diff --git a/ui/package.json b/ui/package.json index 4a45f715a..3badfdea5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -21,6 +21,7 @@ "query-string": "^7.0.0", "react": "^17.0.2", "react-bootstrap": "^1.5.2", + "react-bootstrap-typeahead": "^5.1.4", "react-dom": "^17.0.2", "react-infinite-scroll-component": "^6.1.0", "react-router-dom": "^5.2.0", diff --git a/ui/public/assets/schema/source/metadata-source.json b/ui/public/assets/schema/source/metadata-source.json index c06e299a6..d31a32d44 100644 --- a/ui/public/assets/schema/source/metadata-source.json +++ b/ui/public/assets/schema/source/metadata-source.json @@ -26,38 +26,7 @@ "default": false }, "organization": { - "type": "object", - "properties": { - "name": { - "title": "label.organization-name", - "description": "tooltip.organization-name", - "type": "string" - }, - "displayName": { - "title": "label.organization-display-name", - "description": "tooltip.organization-display-name", - "type": "string" - }, - "url": { - "title": "label.organization-url", - "description": "tooltip.organization-url", - "type": "string" - } - }, - "dependencies": { - "name": [ - "displayName", - "url" - ], - "displayName": [ - "name", - "url" - ], - "url": [ - "name", - "displayName" - ] - } + "$ref": "#/definitions/Organization" }, "contacts": { "title": "label.contact-information", @@ -68,73 +37,7 @@ } }, "mdui": { - "type": "object", - "widget": { - "id": "fieldset" - }, - "fieldsets": [ - { - "type": "group", - "fields": [ - "displayName", - "informationUrl", - "description" - ] - }, - { - "type": "group", - "fields": [ - "privacyStatementUrl", - "logoUrl", - "logoWidth", - "logoHeight" - ] - } - ], - "properties": { - "displayName": { - "title": "label.display-name", - "description": "tooltip.mdui-display-name", - "type": "string" - }, - "informationUrl": { - "title": "label.information-url", - "description": "tooltip.mdui-information-url", - "type": "string" - }, - "privacyStatementUrl": { - "title": "label.privacy-statement-url", - "description": "tooltip.mdui-privacy-statement-url", - "type": "string" - }, - "description": { - "title": "label.description", - "description": "tooltip.mdui-description", - "type": "string", - "widget": { - "id": "textarea" - } - }, - "logoUrl": { - "title": "label.logo-url", - "description": "tooltip.mdui-logo-url", - "type": "string" - }, - "logoHeight": { - "title": "label.logo-height", - "description": "tooltip.mdui-logo-height", - "min": 0, - "type": "integer", - "default": 0 - }, - "logoWidth": { - "title": "label.logo-width", - "description": "tooltip.mdui-logo-width", - "min": 0, - "type": "integer", - "default": 0 - } - } + "$ref": "#/definitions/MDUI" }, "securityInfo": { "type": "object", @@ -162,70 +65,28 @@ "title": "label.is-there-a-x509-certificate", "description": "tooltip.is-there-a-x509-certificate", "type": "boolean", - "widget": { - "id": "boolean-radio" - }, - "oneOf": [ - { - "enum": [ - true - ], - "description": "value.true" - }, - { - "enum": [ - false - ], - "description": "value.false" - } - ], - "default": false + "enumNames": [ + "value.true", + "value.false" + ] }, "authenticationRequestsSigned": { "title": "label.authentication-requests-signed", "description": "tooltip.authentication-requests-signed", "type": "boolean", - "widget": { - "id": "boolean-radio" - }, - "oneOf": [ - { - "enum": [ - true - ], - "description": "value.true" - }, - { - "enum": [ - false - ], - "description": "value.false" - } - ], - "default": false + "enumNames": [ + "value.true", + "value.false" + ] }, "wantAssertionsSigned": { "title": "label.want-assertions-signed", "description": "tooltip.want-assertions-signed", "type": "boolean", - "widget": { - "id": "boolean-radio" - }, - "oneOf": [ - { - "enum": [ - true - ], - "description": "value.true" - }, - { - "enum": [ - false - ], - "description": "value.false" - } - ], - "default": false + "enumNames": [ + "value.true", + "value.false" + ] }, "x509Certificates": { "title": "label.x509-certificates", @@ -246,9 +107,6 @@ }, "serviceProviderSsoDescriptor": { "type": "object", - "widget": { - "id": "fieldset" - }, "fieldsets": [ { "type": "group", @@ -286,7 +144,7 @@ ] }, "nameIdFormats": { - "$ref": "#/definitions/NameIdFormatList" + "$ref": "#/definitions/nameIdFormats" } } }, @@ -331,45 +189,59 @@ "type": "boolean", "default": false }, - "forceAuthn": { - "title": "label.force-authn", - "description": "tooltip.force-authn", - "type": "boolean", - "default": false - }, "omitNotBefore": { "title": "label.omit-not-before-condition", - "type": "boolean", "description": "tooltip.omit-not-before-condition", + "type": "boolean", "default": false }, + "responderId": { + "title": "label.responder-id", + "description": "tooltip.responder-id", + "type": "string", + "default": "" + }, "nameIdFormats": { - "$ref": "#/definitions/NameIdFormatList" + "$ref": "#/definitions/nameIdFormats" }, "authenticationMethods": { - "$ref": "#/definitions/AuthenticationMethodList" + "$ref": "#/definitions/authenticationMethods" }, - "responderId": { - "title": "label.responder-id", - "description": "tooltip.responder-id", - "type": "string" + "forceAuthn": { + "title": "label.force-authn", + "description": "tooltip.force-authn", + "type": "boolean", + "default": false } } }, "attributeRelease": { "type": "array", + "title": "label.attribute-release", "description": "Attribute release table - select the attributes you want to release (default unchecked)", - "widget": { - "id": "checklist", - "dataUrl": "/customAttributes" - }, "items": { - "type": "string" - } + "type": "string", + "enum": [ + "eduPersonPrincipalName", + "uid", + "mail", + "surname", + "givenName", + "eduPersonAffiliation", + "eduPersonScopedAffiliation", + "eduPersonPrimaryAffiliation", + "eduPersonEntitlement", + "eduPersonAssurance", + "eduPersonUniqueId", + "employeeNumber" + ] + }, + "uniqueItems": true } }, "definitions": { "Contact": { + "title": "label.contact", "type": "object", "required": [ "name", @@ -421,7 +293,7 @@ "title": "label.contact-email-address", "description": "tooltip.contact-email", "type": "string", - "pattern": "^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$", + "pattern": "^(mailto:)?(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$", "minLength": 1, "maxLength": 255 } @@ -431,7 +303,6 @@ "type": "object", "title": "label.certificate", "required": [ - "name", "type", "value" ], @@ -440,7 +311,6 @@ "title": "label.certificate-name-display-only", "description": "tooltip.certificate-name", "type": "string", - "minLength": 1, "maxLength": 255 }, "type": { @@ -470,8 +340,7 @@ ], "description": "value.both" } - ], - "default": "both" + ] }, "value": { "title": "label.certificate", @@ -485,6 +354,10 @@ "AssertionConsumerService": { "type": "object", "title": "label.assertion-consumer-service-endpoint", + "required": [ + "locationUrl", + "binding" + ], "properties": { "locationUrl": { "title": "label.assertion-consumer-service-location", @@ -509,66 +382,45 @@ ], "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" }, + { + "enum": [ + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" + ], + "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" + }, + { + "enum": [ + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" + ], + "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" + }, + { + "enum": [ + "urn:oasis:names:tc:SAML:2.0:bindings:PAOS" + ], + "description": "urn:oasis:names:tc:SAML:2.0:bindings:PAOS" + }, { "enum": [ "urn:oasis:names:tc:SAML:1.0:profiles:browser-post" ], "description": "urn:oasis:names:tc:SAML:1.0:profiles:browser-post" + }, + { + "enum": [ + "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01" + ], + "description": "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01" } ] }, "makeDefault": { "title": "label.mark-as-default", "description": "tooltip.mark-as-default", - "type": "boolean", - "default": false + "type": "boolean" } } }, - "NameIdFormatList": { - "title": "label.nameid-format-to-send", - "placeholder": "label.nameid-format", - "description": "tooltip.nameid-format", - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 255, - "widget": { - "id": "datalist", - "data": [ - "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", - "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" - ] - } - }, - "default": null - }, - "AuthenticationMethodList": { - "title": "label.authentication-methods-to-use", - "description": "tooltip.authentication-methods-to-use", - "type": "array", - "placeholder": "label.authentication-method", - "uniqueItems": true, - "items": { - "type": "string", - "title": "label.authentication-method", - "minLength": 1, - "maxLength": 255, - "widget": { - "id": "datalist", - "data": [ - "https://refeds.org/profile/mfa", - "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken", - "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" - ] - } - }, - "default": null - }, "LogoutEndpoint": { "title": "label.new-endpoint", "description": "tooltip.new-endpoint", @@ -610,10 +462,181 @@ "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" ], "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + }, + { + "enum": [ + "urn:oasis:names:tc:SAML:2.0:bindings:SOAP" + ], + "description": "urn:oasis:names:tc:SAML:2.0:bindings:SOAP" + }, + { + "enum": [ + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" + ], + "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" } ] } } + }, + "MDUI": { + "type": "object", + "widget": { + "id": "fieldset" + }, + "fieldsets": [ + { + "type": "group", + "fields": [ + "displayName", + "informationUrl", + "description" + ] + }, + { + "type": "group", + "fields": [ + "privacyStatementUrl", + "logoUrl", + "logoWidth", + "logoHeight" + ] + } + ], + "properties": { + "displayName": { + "title": "label.display-name", + "description": "tooltip.mdui-display-name", + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "informationUrl": { + "title": "label.information-url", + "description": "tooltip.mdui-information-url", + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "privacyStatementUrl": { + "title": "label.privacy-statement-url", + "description": "tooltip.mdui-privacy-statement-url", + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "description": { + "title": "label.description", + "description": "tooltip.mdui-description", + "type": "string", + "widget": { + "id": "textarea" + }, + "minLength": 1, + "maxLength": 255 + }, + "logoUrl": { + "title": "label.logo-url", + "description": "tooltip.mdui-logo-url", + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "logoHeight": { + "title": "label.logo-height", + "description": "tooltip.mdui-logo-height", + "minimum": 0, + "type": "integer" + }, + "logoWidth": { + "title": "label.logo-width", + "description": "tooltip.mdui-logo-width", + "minimum": 0, + "type": "integer" + } + } + }, + "Organization": { + "type": "object", + "properties": { + "name": { + "title": "label.organization-name", + "description": "tooltip.organization-name", + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "displayName": { + "title": "label.organization-display-name", + "description": "tooltip.organization-display-name", + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "url": { + "title": "label.organization-url", + "description": "tooltip.organization-url", + "type": "string", + "minLength": 1, + "maxLength": 255 + } + }, + "dependencies": { + "name": { + "required": [ + "displayName", + "url" + ] + }, + "displayName": { + "required": [ + "name", + "url" + ] + }, + "url": { + "required": [ + "name", + "displayName" + ] + } + } + }, + "nameIdFormats": { + "title": "label.nameid-format-to-send", + "description": "tooltip.nameid-format", + "type": "array", + "uniqueItems": true, + "additionalProperties": { + "type": "string" + }, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "examples": [ + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", + "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" + ] + } + }, + "authenticationMethods": { + "title": "label.authentication-methods-to-use", + "description": "tooltip.authentication-methods-to-use", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "examples": [ + "https://refeds.org/profile/mfa", + "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken", + "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" + ] + } } } } \ No newline at end of file diff --git a/ui/src/app/form/component/AddButton.js b/ui/src/app/form/component/AddButton.js new file mode 100644 index 000000000..c5bb7714e --- /dev/null +++ b/ui/src/app/form/component/AddButton.js @@ -0,0 +1,14 @@ +import React from "react"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import Button from "react-bootstrap/Button"; +import Translate from "../../i18n/components/translate"; + +const AddButton = ({className, ...props}) => ( + +); +export default AddButton; \ No newline at end of file diff --git a/ui/src/app/form/component/CustomFieldTemplate.js b/ui/src/app/form/component/CustomFieldTemplate.js deleted file mode 100644 index 39a1a0202..000000000 --- a/ui/src/app/form/component/CustomFieldTemplate.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from "react"; - -import Form from "react-bootstrap/Form"; -import ListGroup from "react-bootstrap/ListGroup"; - -import {Translate} from '../../i18n/components/translate'; - -export function CustomFieldTemplate ({ - id, - children, - displayLabel, - rawErrors = [], - rawHelp, - rawDescription, - ...props -}) { - - console.log(props); - return ( - <>{!props.hidden ? - {children} - {displayLabel && rawDescription ? ( - 0 ? "text-danger" : "text-muted"}> - - - ) : null} - {rawErrors.length > 0 && ( - - {rawErrors.map((error) => { - return ( - - - {error} - - - ); - })} - - )} - {rawHelp && ( - 0 ? "text-danger" : "text-muted"} - id={id}> - - - )} - - : <>} - ); -}; - -export default CustomFieldTemplate; \ No newline at end of file diff --git a/ui/src/app/form/component/IconButton.js b/ui/src/app/form/component/IconButton.js new file mode 100644 index 000000000..4246d5df0 --- /dev/null +++ b/ui/src/app/form/component/IconButton.js @@ -0,0 +1,23 @@ +import React from "react"; +import Button from "react-bootstrap/Button"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowDown, faArrowUp, faTrash, faPlus } from "@fortawesome/free-solid-svg-icons"; + +const mappings = { + remove: , + plus: , + "arrow-up": , + "arrow-down": , +}; + +const IconButton = (props) => { + const { icon, className, ...otherProps } = props; + return ( + + ); +}; + +export default IconButton; \ No newline at end of file diff --git a/ui/src/app/form/component/InfoIcon.js b/ui/src/app/form/component/InfoIcon.js new file mode 100644 index 000000000..da27ca22f --- /dev/null +++ b/ui/src/app/form/component/InfoIcon.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; +import Popover from 'react-bootstrap/Popover'; +import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; +import Translate from '../../i18n/components/translate'; + +export function InfoIcon ({ value, ...props }) { + return( + + + + )}> + + + ); +} \ No newline at end of file diff --git a/ui/src/app/form/component/fields/DescriptionField.js b/ui/src/app/form/component/fields/DescriptionField.js new file mode 100644 index 000000000..aeea23ace --- /dev/null +++ b/ui/src/app/form/component/fields/DescriptionField.js @@ -0,0 +1,12 @@ +import React from "react"; +import { InfoIcon } from "../InfoIcon"; + +const DescriptionField = ({ description }) => { + if (description) { + return ; + } + + return null; +}; + +export default DescriptionField; \ No newline at end of file diff --git a/ui/src/app/form/component/fields/TitleField.js b/ui/src/app/form/component/fields/TitleField.js new file mode 100644 index 000000000..eecebb032 --- /dev/null +++ b/ui/src/app/form/component/fields/TitleField.js @@ -0,0 +1,8 @@ +import React from "react"; +import Translate from "../../../i18n/components/translate"; + +const TitleField = ({ title }) => ( + +); + +export default TitleField; \ No newline at end of file diff --git a/ui/src/app/form/component/index.js b/ui/src/app/form/component/index.js index a8888fab1..a13d935a4 100644 --- a/ui/src/app/form/component/index.js +++ b/ui/src/app/form/component/index.js @@ -1,3 +1,38 @@ +import TextWidget from './widgets/TextWidget'; +import TextareaWidget from './widgets/TextareaWidget'; +import SelectWidget from './widgets/SelectWidget'; +import CheckboxWidget from './widgets/CheckboxWidget'; +import AttributeReleaseWidget from './widgets/AttributeReleaseWidget'; +import RadioWidget from './widgets/RadioWidget'; +import OptionWidget from './widgets/OptionWidget'; +import UpDownWidget from './widgets/UpDownWidget'; + +import FieldTemplate from './templates/FieldTemplate'; +import ArrayFieldTemplate from './templates/ArrayFieldTemplate'; +import ObjectFieldTemplate from './templates/ObjectFieldTemplate'; + +import TitleField from './fields/TitleField'; +import DescriptionField from './fields/DescriptionField'; + export const fields = { // SchemaField: CustomSchemaField + TitleField, + DescriptionField +}; + +export const templates = { + FieldTemplate, + ArrayFieldTemplate, + ObjectFieldTemplate +} + +export const widgets = { + OptionWidget, + TextWidget, + TextareaWidget, + SelectWidget, + CheckboxWidget, + RadioWidget, + UpDownWidget, + AttributeReleaseWidget }; \ No newline at end of file diff --git a/ui/src/app/form/component/templates/ArrayFieldTemplate.js b/ui/src/app/form/component/templates/ArrayFieldTemplate.js new file mode 100644 index 000000000..9029888a1 --- /dev/null +++ b/ui/src/app/form/component/templates/ArrayFieldTemplate.js @@ -0,0 +1,199 @@ +import React from "react"; +import { utils } from "@rjsf/core"; +import Row from "react-bootstrap/Row"; +import Col from "react-bootstrap/Col"; +import Container from "react-bootstrap/Container"; + +import AddButton from "../AddButton"; +import IconButton from "../IconButton"; + +const { isMultiSelect, getDefaultRegistry } = utils; + +const ArrayFieldTemplate = (props) => { + const { schema, registry = getDefaultRegistry() } = props; + + // TODO: update types so we don't have to cast registry as any + if (isMultiSelect(schema, (registry).rootSchema)) { + + return ; + } else { + return ; + } +}; + + +const ArrayFieldTitle = ({ + TitleField, + idSchema, + title, + required, +}) => { + if (!title) { + return null; + } + + const id = `${idSchema.$id}__title`; + return ; +}; + + +const ArrayFieldDescription = ({ + DescriptionField, + idSchema, + description, +}) => { + if (!description) { + return null; + } + + const id = `${idSchema.$id}__description`; + return ; +}; + +// Used in the two templates +const DefaultArrayItem = (props) => { + const btnStyle = { + flex: 1, + paddingLeft: 6, + paddingRight: 6, + fontWeight: "bold", + }; + return ( +
+ + {props.children} + + + {props.hasToolbar && ( +
+ {(props.hasMoveUp || props.hasMoveDown) && ( +
+ +
+ )} + + {(props.hasMoveUp || props.hasMoveDown) && ( +
+ +
+ )} + + {props.hasRemove && ( +
+ +
+ )} +
+ )} + +
+
+ ); +}; + +const DefaultFixedArrayFieldTemplate = (props) => { + console.log(props) + return ( +
+
+ + {props.canAdd && ( + + )} +
+ + + {(props.uiSchema["ui:description"] || props.schema.description) && ( +
+ {props.uiSchema["ui:description"] || props.schema.description} +
+ )} + +
+ {props.items && props.items.map(DefaultArrayItem)} +
+ + +
+ ); +}; + +const DefaultNormalArrayFieldTemplate = (props) => { + return ( +
+ + +
+ + {props.canAdd && ( + + )} + {(props.uiSchema["ui:description"] || props.schema.description) && ( + + )} +
+ + {props.items && props.items.map(p => DefaultArrayItem(p))} + + + +
+
+ ); +}; + +export default ArrayFieldTemplate; \ No newline at end of file diff --git a/ui/src/app/form/component/templates/FieldTemplate.js b/ui/src/app/form/component/templates/FieldTemplate.js new file mode 100644 index 000000000..656bba72f --- /dev/null +++ b/ui/src/app/form/component/templates/FieldTemplate.js @@ -0,0 +1,48 @@ +import React from "react"; + +import Form from "react-bootstrap/Form"; +import ListGroup from "react-bootstrap/ListGroup"; + +import {Translate} from '../../../i18n/components/translate'; + +export function FieldTemplate ({ + id, + label, + children, + displayLabel, + rawErrors = [], + rawHelp, + rawDescription, + ...props +}) { + return ( + <>{!props.hidden ? + + {children} + {rawErrors.length > 0 && ( + + {rawErrors.map((error) => { + return ( + + + {error} + + + ); + })} + + )} + {rawHelp && ( + 0 ? "text-danger" : "text-muted"} + id={id}> + + + )} + + : <> + } + ); +}; + +export default FieldTemplate; \ No newline at end of file diff --git a/ui/src/app/form/component/templates/ObjectFieldTemplate.js b/ui/src/app/form/component/templates/ObjectFieldTemplate.js new file mode 100644 index 000000000..87bb170de --- /dev/null +++ b/ui/src/app/form/component/templates/ObjectFieldTemplate.js @@ -0,0 +1,49 @@ +import React from "react"; + +import Container from "react-bootstrap/Container"; +import Row from "react-bootstrap/Row"; +import Col from "react-bootstrap/Col"; + +const ObjectFieldTemplate = ({ + DescriptionField, + description, + TitleField, + title, + properties, + required, + uiSchema, + idSchema, + schema, + hidden +}) => { + return ( + <> + {!hidden && + <> + {(uiSchema["ui:title"] || (title && schema.title)) && ( + + )} + {description && ( + + )} + + {properties.map((element, index) => ( + + {element.content} + + ))} + + + } + + ); +}; + +export default ObjectFieldTemplate; \ No newline at end of file diff --git a/ui/src/app/form/component/widgets/AttributeReleaseWidget.js b/ui/src/app/form/component/widgets/AttributeReleaseWidget.js new file mode 100644 index 000000000..c547891d8 --- /dev/null +++ b/ui/src/app/form/component/widgets/AttributeReleaseWidget.js @@ -0,0 +1,133 @@ +import React from "react"; +import Form from "react-bootstrap/Form"; +import Translate from "../../../i18n/components/translate"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"; + +const selectValue = (value, selected, all) => { + const at = all.indexOf(value); + const updated = selected.slice(0, at).concat(value, selected.slice(at)); + + // As inserting values at predefined index positions doesn't work with empty + // arrays, we need to reorder the updated selection to match the initial order + return updated.sort((a, b) => all.indexOf(a) > all.indexOf(b)); +}; + +const deselectValue = (value, selected) => { + return selected.filter((v) => v !== value); +}; + +const AttributeReleaseWidget = ({ + schema, + label, + id, + disabled, + options, + value, + autofocus, + readonly, + required, + onChange, + onBlur, + onFocus, +}) => { + const { enumOptions, enumDisabled } = options; + + const _onChange = (option) => ({ + target: { checked }, + }) => { + const all = (enumOptions).map(({ value }) => value); + + if (checked) { + console.log(selectValue(option.value, value, all)) + onChange(selectValue(option.value, value, all)); + } else { + onChange(deselectValue(option.value, value)); + } + }; + + const _onBlur = ({ target: { value } }) => + onBlur(id, value); + const _onFocus = ({ + target: { value }, + }) => onFocus(id, value); + + const onCheckAll = () => { + const all = (enumOptions).map(({ value }) => value); + let update = []; + enumOptions.forEach(v => update = selectValue(v.value, update, all)); + + onChange(update); + } + const onClearAll = () => { + onChange([]); + } + + return ( +
+ + + + + + + + + + {(enumOptions).map((option, index) => { + const checked = value.indexOf(option.value) !== -1; + const itemDisabled = + enumDisabled && (enumDisabled).indexOf(option.value) !== -1; + return ( + + + + + ); + })} + + + + + + + + + +
Attribute NameYes
+
+
+ +
+
+
Check All Attributes + +
Clear All Attributes + +
+
+ ); +}; + +/* +*/ + +export default AttributeReleaseWidget; \ No newline at end of file diff --git a/ui/src/app/form/component/widgets/CheckboxWidget.js b/ui/src/app/form/component/widgets/CheckboxWidget.js new file mode 100644 index 000000000..4e02c38c1 --- /dev/null +++ b/ui/src/app/form/component/widgets/CheckboxWidget.js @@ -0,0 +1,57 @@ +import React from "react"; + +import Form from "react-bootstrap/Form"; +import Translate from "../../../i18n/components/translate"; +import { InfoIcon } from "../InfoIcon"; + +const CheckboxWidget = (props) => { + const { + id, + value, + required, + disabled, + readonly, + label, + schema, + autofocus, + onChange, + onBlur, + onFocus, + } = props; + + const _onChange = ({ + target: { checked }, + }) => onChange(checked); + const _onBlur = ({ + target: { checked }, + }) => onBlur(id, checked); + const _onFocus = ({ + target: { checked }, + }) => onFocus(id, checked); + + const desc = label || schema.description; + return ( + + + + + {(label || schema.title) && required ? * : null} + + {schema.description && } + } + checked={typeof value === "undefined" ? false : value} + required={required} + disabled={disabled || readonly} + autoFocus={autofocus} + onChange={_onChange} + type="checkbox" + onBlur={_onBlur} + onFocus={_onFocus} + /> + + ); +}; + +export default CheckboxWidget; \ No newline at end of file diff --git a/ui/src/app/form/component/widgets/OptionWidget.js b/ui/src/app/form/component/widgets/OptionWidget.js new file mode 100644 index 000000000..a6a935043 --- /dev/null +++ b/ui/src/app/form/component/widgets/OptionWidget.js @@ -0,0 +1,106 @@ +import React from "react"; + +import Form from "react-bootstrap/Form"; +import Translate from "../../../i18n/components/translate"; +import { InfoIcon } from "../InfoIcon"; + +import { Typeahead } from 'react-bootstrap-typeahead'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowDown, faArrowUp } from "@fortawesome/free-solid-svg-icons"; + +const ToggleButton = ({ isOpen, onClick }) => ( + +); + +const OptionWidget = ({ + id, + placeholder, + required, + readonly, + disabled, + type, + label, + value, + onChange, + onBlur, + onFocus, + autofocus, + options, + schema, + rawErrors = [], + uiSchema, + ...props +}) => { + const _onChange = (selected) => onChange(selected[0] === '' ? options.emptyValue : selected[0]); + const _onBlur = ({ target: { value } }) => onBlur(id, value); + const _onFocus = ({ target: { value } }) => onFocus(id, value); + const inputType = (type || schema.type) === 'string' ? 'text' : `${type || schema.type}`; + + const opts = Array.isArray(options) || options.enumOptions ? options : schema.examples ? schema.examples : []; + + console.log(opts); + + return ( + + 0 ? "text-danger" : ""}`}> + + + {(label || schema.title) && required ? * : null} + + {schema.description && } + + 0 ? "is-invalid" : ""}`} + options={opts} + placeholder={uiSchema.placeholder ? uiSchema.placeholder : ''} + disabled={disabled || readonly} + onChange={_onChange} + onBlur={_onBlur} + onFocus={_onFocus} + filterBy={(option, props) => true} + renderMenuItemChildren={(option, {options, text}, index) => { + return {option}; + }}> + {({ isMenuShown, toggleMenu }) => ( + toggleMenu()} /> + )} + + + {/**/} + {schema.examples ? ( + + {(schema.examples) + .concat(schema.default ? ([schema.default]) : []) + .map((example) => { + return + ) : null} + + ); +}; + +export default OptionWidget; \ No newline at end of file diff --git a/ui/src/app/form/component/widgets/RadioWidget.js b/ui/src/app/form/component/widgets/RadioWidget.js new file mode 100644 index 000000000..f533b704e --- /dev/null +++ b/ui/src/app/form/component/widgets/RadioWidget.js @@ -0,0 +1,73 @@ +import React from "react"; + +import Form from "react-bootstrap/Form"; +import Translate from "../../../i18n/components/translate"; +import { InfoIcon } from "../InfoIcon"; + + +const RadioWidget = ({ + id, + schema, + options, + value, + required, + disabled, + readonly, + label, + onChange, + onBlur, + onFocus, +}) => { + const { enumOptions, enumDisabled } = options; + + const _onChange = ({ + target: { value }, + }) => + onChange(schema.type === "boolean" ? value !== "false" : value); + const _onBlur = ({ target: { value } }) => + onBlur(id, value); + const _onFocus = ({ + target: { value }, + }) => onFocus(id, value); + + const inline = Boolean(options && options.inline); + + return ( + + + + + {(label || schema.title) && required ? * : null} + + {schema.description && } + + {(enumOptions).map((option, i) => { + const itemDisabled = + Array.isArray(enumDisabled) && + enumDisabled.indexOf(option.value) !== -1; + const checked = option.value === value; + + const radio = ( + } + id={option.label} + key={i} + name={id} + type="radio" + disabled={disabled || itemDisabled || readonly} + checked={checked} + required={required} + value={option.value} + onChange={_onChange} + onBlur={_onBlur} + onFocus={_onFocus} + /> + ); + return radio; + })} + + ); +}; + +export default RadioWidget; \ No newline at end of file diff --git a/ui/src/app/form/component/widgets/SelectWidget.js b/ui/src/app/form/component/widgets/SelectWidget.js new file mode 100644 index 000000000..bf8a525f8 --- /dev/null +++ b/ui/src/app/form/component/widgets/SelectWidget.js @@ -0,0 +1,135 @@ +import React from "react"; + +import Form from "react-bootstrap/Form"; + +import { utils } from "@rjsf/core"; + +import Translate from "../../../i18n/components/translate"; +import { InfoIcon } from "../InfoIcon"; + +const { asNumber, guessType } = utils; + +const nums = new Set(["number", "integer"]); + +/** + * This is a silly limitation in the DOM where option change event values are + * always retrieved as strings. + */ +const processValue = (schema, value) => { + // "enum" is a reserved word, so only "type" and "items" can be destructured + const { type, items } = schema; + if (value === "") { + return undefined; + } else if (type === "array" && items && nums.has(items.type)) { + return value.map(asNumber); + } else if (type === "boolean") { + return value === "true"; + } else if (type === "number") { + return asNumber(value); + } + + // If type is undefined, but an enum is present, try and infer the type from + // the enum values + if (schema.enum) { + if (schema.enum.every((x) => guessType(x) === "number")) { + return asNumber(value); + } else if (schema.enum.every((x) => guessType(x) === "boolean")) { + return value === "true"; + } + } + + return value; +}; + +const SelectWidget = ({ + schema, + id, + options, + label, + required, + disabled, + readonly, + value, + multiple, + autofocus, + onChange, + onBlur, + onFocus, + placeholder, + rawErrors = [], +}) => { + const { enumOptions, enumDisabled } = options; + + const emptyValue = multiple ? [] : ""; + + function getValue( + event, + multiple + ) { + if (multiple) { + return [].slice + .call(event.target.options) + .filter((o) => o.selected) + .map((o) => o.value); + } else { + return event.target.value; + } + } + + return ( + + 0 ? "text-danger" : ""}`}> + + + {(label || schema.title) && required ? * : null} + + {schema.description && } + + 0 ? "is-invalid" : ""} + onBlur={ + onBlur && + ((event) => { + const newValue = getValue(event, multiple); + onBlur(id, processValue(schema, newValue)); + }) + } + onFocus={ + onFocus && + ((event) => { + const newValue = getValue(event, multiple); + onFocus(id, processValue(schema, newValue)); + }) + } + onChange={(event) => { + const newValue = getValue(event, multiple); + onChange(processValue(schema, newValue)); + }}> + {!multiple && schema.default === undefined && ( + + )} + {(enumOptions).map(({ value, label }, i) => { + const disabled = + Array.isArray(enumDisabled) && + (enumDisabled).indexOf(value) != -1; + return ( + + ); + })} + + + ); +}; + +export default SelectWidget; \ No newline at end of file diff --git a/ui/src/app/form/component/widgets/TextWidget.js b/ui/src/app/form/component/widgets/TextWidget.js new file mode 100644 index 000000000..5160a00b8 --- /dev/null +++ b/ui/src/app/form/component/widgets/TextWidget.js @@ -0,0 +1,69 @@ +import React from "react"; + +import Form from "react-bootstrap/Form"; +import Translate from "../../../i18n/components/translate"; +import { InfoIcon } from "../InfoIcon"; + +const TextWidget = ({ + id, + placeholder, + required, + readonly, + disabled, + type, + label, + value, + onChange, + onBlur, + onFocus, + autofocus, + options, + schema, + rawErrors = [], + ...props +}) => { + const _onChange = ({target: { value }}) => onChange(value === "" ? options.emptyValue : value); + const _onBlur = ({ target: { value } }) => onBlur(id, value); + const _onFocus = ({target: { value }} ) => onFocus(id, value); + const inputType = (type || schema.type) === 'string' ? 'text' : `${type || schema.type}`; + + + // const classNames = [rawErrors.length > 0 ? "is-invalid" : "", type === 'file' ? 'custom-file-label': ""] + return ( + + 0 ? "text-danger" : ""}`}> + + + {(label || schema.title) && required ? * : null} + + {schema.description && } + + 0 ? "is-invalid" : ""} + list={schema.examples ? `examples_${id}` : undefined} + type={inputType} + value={value || value === 0 ? value : ""} + onChange={_onChange} + onBlur={_onBlur} + onFocus={_onFocus} + /> + {schema.examples ? ( + + {(schema.examples) + .concat(schema.default ? ([schema.default]) : []) + .map((example) => { + return + ) : null} + + ); +}; + +export default TextWidget; \ No newline at end of file diff --git a/ui/src/app/form/component/widgets/TextareaWidget.js b/ui/src/app/form/component/widgets/TextareaWidget.js new file mode 100644 index 000000000..e4829b4a5 --- /dev/null +++ b/ui/src/app/form/component/widgets/TextareaWidget.js @@ -0,0 +1,66 @@ +import React from "react"; + +import Form from "react-bootstrap/Form"; +import FormControl from "react-bootstrap/FormControl"; +import InputGroup from "react-bootstrap/InputGroup"; + +import Translate from "../../../i18n/components/translate"; +import { InfoIcon } from "../InfoIcon"; + +const TextareaWidget = ({ + id, + placeholder, + value, + required, + disabled, + autofocus, + label, + readonly, + onBlur, + onFocus, + onChange, + options, + schema, + rawErrors = [], +}) => { + const _onChange = ({ + target: { value }, + }) => + onChange(value === "" ? options.emptyValue : value); + const _onBlur = ({ + target: { value }, + }) => onBlur(id, value); + const _onFocus = ({ + target: { value }, + }) => onFocus(id, value); + + return ( + <> + 0 ? "text-danger" : ""}`}> + + + {(label || schema.title) && required ? * : null} + + {schema.description && } + + + + + + ); +}; + +export default TextareaWidget; \ No newline at end of file diff --git a/ui/src/app/form/component/widgets/UpDownWidget.js b/ui/src/app/form/component/widgets/UpDownWidget.js new file mode 100644 index 000000000..ca50b659e --- /dev/null +++ b/ui/src/app/form/component/widgets/UpDownWidget.js @@ -0,0 +1,55 @@ +import React from "react"; + +import Form from "react-bootstrap/Form"; + +import Translate from "../../../i18n/components/translate"; +import { InfoIcon } from "../InfoIcon"; + +const UpDownWidget = ({ + id, + required, + readonly, + disabled, + label, + value, + onChange, + onBlur, + onFocus, + autofocus, + schema +}) => { + const _onChange = ({ + target: { value }, + }) => onChange(value); + const _onBlur = ({ target: { value } }) => + onBlur(id, value); + const _onFocus = ({ + target: { value }, + }) => onFocus(id, value); + + return ( + + + + + {(label || schema.title) && required ? * : null} + + {schema.description && } + + + + ); +}; + +export default UpDownWidget; \ No newline at end of file diff --git a/ui/src/app/metadata/domain/data.js b/ui/src/app/metadata/domain/data.js new file mode 100644 index 000000000..45412d300 --- /dev/null +++ b/ui/src/app/metadata/domain/data.js @@ -0,0 +1,12 @@ +export const DurationOptions = [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT8H", + "PT12H", + "PT24H" +]; \ No newline at end of file diff --git a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js index 082ab3203..57176a5c6 100644 --- a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js @@ -23,4 +23,7 @@ export const BaseProviderDefinition = { }), {}) } }) : changes), + uiSchema: { + + } } \ No newline at end of file diff --git a/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js index 0595d0bad..e61cfb184 100644 --- a/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js @@ -1,4 +1,7 @@ +import defaultsDeep from 'lodash/defaultsDeep'; + import API_BASE_PATH from "../../../App.constant"; +import {DurationOptions} from '../data'; import { BaseProviderDefinition } from "./BaseProviderDefinition"; export const LocalDynamicMetadataProviderWizard = { @@ -6,6 +9,14 @@ export const LocalDynamicMetadataProviderWizard = { label: 'LocalDynamicMetadataProvider', type: 'LocalDynamicMetadataResolver', schema: `${API_BASE_PATH}/ui/MetadataResolver/LocalDynamicMetadataResolver`, + uiSchema: { + dynamicMetadataResolverAttributes: { + minCacheDuration: { + 'ui:widget': 'OptionWidget', + options: DurationOptions + } + } + }, steps: [ { id: 'common', @@ -69,6 +80,11 @@ export const LocalDynamicMetadataProviderWizard = { export const LocalDynamicMetadataProviderEditor = { ...LocalDynamicMetadataProviderWizard, + uiSchema: defaultsDeep({ + '@type': { + 'ui:disabled': true + } + }, LocalDynamicMetadataProviderWizard.uiSchema), steps: [ { id: 'common', diff --git a/ui/src/app/metadata/domain/source/SourceDefinition.js b/ui/src/app/metadata/domain/source/SourceDefinition.js index f838317ac..d8b02f08a 100644 --- a/ui/src/app/metadata/domain/source/SourceDefinition.js +++ b/ui/src/app/metadata/domain/source/SourceDefinition.js @@ -1,10 +1,12 @@ -import API_BASE_PATH from "../../../App.constant"; +import defaultsDeep from 'lodash/defaultsDeep'; +import API_BASE_PATH from '../../../App.constant'; export const SourceBase = { label: 'Metadata Source', type: '@MetadataProvider', steps: [], - schema: `${API_BASE_PATH}/ui/MetadataSources`, + schema: `/assets/schema/source/metadata-source.json`, + //${API_BASE_PATH}/ui/MetadataSources validatorParams: [/*getAllOtherIds*/], bindings: { @@ -117,8 +119,49 @@ export const SourceBase = { return validators; }, uiSchema: { + attributeRelease: { + 'ui:widget': 'AttributeReleaseWidget' + }, + relyingPartyOverrides: { + nameIdFormats: { + items: { + 'ui:widget': 'OptionWidget' + } + }, + authenticationMethods: { + items: { + 'ui:widget': 'OptionWidget' + } + } + }, + securityInfo: { + x509CertificateAvailable: { + 'ui:widget': 'radio' + }, + authenticationRequestsSigned: { + 'ui:widget': 'radio' + }, + wantAssertionsSigned: { + 'ui:widget': 'radio' + }, + x509Certificates: { + items: { + type: { + 'ui:widget': 'radio' + }, + value: { + 'ui:widget': 'textarea' + } + } + } + }, mdui: { - 'ui:widget': 'hidden' + logoHeight: { + 'ui:widget': 'updown' + }, + logoWidth: { + 'ui:widget': 'updown' + } } } } @@ -126,7 +169,7 @@ export const SourceBase = { export const SourceEditor = { ...SourceBase, - schema: `${API_BASE_PATH}/ui/MetadataSources`, + uiSchema: defaultsDeep({}, SourceBase.uiSchema), steps: [ { index: 1, diff --git a/ui/src/app/metadata/editor/MetadataEditorForm.js b/ui/src/app/metadata/editor/MetadataEditorForm.js index 12e6e5e64..1d0d9adc5 100644 --- a/ui/src/app/metadata/editor/MetadataEditorForm.js +++ b/ui/src/app/metadata/editor/MetadataEditorForm.js @@ -2,8 +2,8 @@ import React from 'react'; import Form from '@rjsf/bootstrap-4'; -import { fields } from '../../form/component'; -import { CustomFieldTemplate } from '../../form/component/CustomFieldTemplate'; +import { fields, widgets } from '../../form/component'; +import { templates } from '../../form/component'; import { useUiSchema } from '../hooks/schema'; export function MetadataEditorForm ({ metadata, definition, schema, current }) { @@ -14,14 +14,23 @@ export function MetadataEditorForm ({ metadata, definition, schema, current }) { React.useEffect(() => setData(metadata), [metadata]); + const onSubmit = () => {}; + return ( <>
setData(formData) } + onSubmit={() => onSubmit()} schema={schema} uiSchema={uiSchema} - FieldTemplate={CustomFieldTemplate} - fields={ fields }>
+ FieldTemplate={templates.FieldTemplate} + ObjectFieldTemplate={templates.ObjectFieldTemplate} + ArrayFieldTemplate={templates.ArrayFieldTemplate} + fields={ fields } + widgets={widgets}> + <> +
{JSON.stringify(data, null, 4)}
); diff --git a/ui/src/app/metadata/hooks/schema.js b/ui/src/app/metadata/hooks/schema.js index f7b3ee2c3..263bc8bbe 100644 --- a/ui/src/app/metadata/hooks/schema.js +++ b/ui/src/app/metadata/hooks/schema.js @@ -1,8 +1,5 @@ import React from 'react'; -import assignInWith from 'lodash/assignInWith'; -import { I18nContext } from '../../i18n/context/I18n.provider'; - const fillInRootProperties = (keys, ui) => { return keys.reduce((sch, key, idx) => { if (!sch.hasOwnProperty(key)) { diff --git a/ui/src/theme/project/forms.scss b/ui/src/theme/project/forms.scss index 2b2ec2ab9..4b9c67712 100644 --- a/ui/src/theme/project/forms.scss +++ b/ui/src/theme/project/forms.scss @@ -87,6 +87,20 @@ select.form-control:disabled { } } +.toggle-typeahead { + padding-right: 36px; +} + +.toggle-button { + bottom: 0; + position: absolute; + right: 0; + top: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + background: white; +} + @media only screen and (max-width: 1200px) { .form-section:not(:first-child) { border-left: 0px; diff --git a/ui/yarn.lock b/ui/yarn.lock index c008997fc..bbc9aa851 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1113,7 +1113,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.13.8", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.13.8", "@babel/runtime@^7.3.4", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== @@ -1257,6 +1257,14 @@ dependencies: "@hapi/hoek" "^8.3.0" +"@hypnosphi/create-react-context@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz#f8bfebdc7665f5d426cba3753e0e9c7d3154d7c6" + integrity sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A== + dependencies: + gud "^1.0.0" + warning "^4.0.3" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1485,7 +1493,7 @@ schema-utils "^2.6.5" source-map "^0.7.3" -"@popperjs/core@^2.8.6": +"@popperjs/core@^2.5.3", "@popperjs/core@^2.8.6": version "2.9.2" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== @@ -1495,7 +1503,7 @@ resolved "https://registry.yarnpkg.com/@restart/context/-/context-2.1.4.tgz#a99d87c299a34c28bd85bb489cb07bfd23149c02" integrity sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q== -"@restart/hooks@^0.3.26": +"@restart/hooks@^0.3.22", "@restart/hooks@^0.3.25", "@restart/hooks@^0.3.26": version "0.3.26" resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.3.26.tgz#ade155a7b0b014ef1073391dda46972c3a14a129" integrity sha512-7Hwk2ZMYm+JLWcb7R9qIXk1OoUg1Z+saKWqZXlrvFwT3w6UArVNWgxYOzf+PJoK9zZejp8okPAKTctthhXLt5g== @@ -3359,7 +3367,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@*, classnames@^2.2.6: +classnames@*, classnames@^2.2.0, classnames@^2.2.6: version "2.3.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== @@ -3544,6 +3552,11 @@ compute-lcm@^1.1.0: validate.io-function "^1.0.2" validate.io-integer-array "^1.0.0" +compute-scroll-into-view@^1.0.17: + version "1.0.17" + resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab" + integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -4078,7 +4091,7 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= -deep-equal@^1.0.1: +deep-equal@^1.0.1, deep-equal@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== @@ -5485,6 +5498,11 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + gzip-size@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" @@ -5964,7 +5982,7 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -invariant@^2.2.4: +invariant@^2.2.1, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -7154,6 +7172,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -8281,6 +8304,11 @@ pnp-webpack-plugin@1.6.4: dependencies: ts-pnp "^1.1.6" +popper.js@^1.14.4: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== + portfinder@^1.0.26: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" @@ -9047,7 +9075,7 @@ prop-types-extra@^1.1.0: react-is "^16.3.2" warning "^4.0.0" -prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -9245,6 +9273,23 @@ react-app-polyfill@^2.0.0: regenerator-runtime "^0.13.7" whatwg-fetch "^3.4.1" +react-bootstrap-typeahead@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/react-bootstrap-typeahead/-/react-bootstrap-typeahead-5.1.4.tgz#856d30438bb7a254cfeafa3ec03ddf90285f7a4d" + integrity sha512-CfoaSjGV3MZS9yrkLfYkvimzRYru1Ko7qKKzacbsBeZ/sHCxFvUhxnCXmTy/K2vZCkOmAQpU/sbgQVOkMRNlSQ== + dependencies: + "@babel/runtime" "^7.3.4" + "@restart/hooks" "^0.3.22" + classnames "^2.2.0" + fast-deep-equal "^3.1.1" + invariant "^2.2.1" + lodash.debounce "^4.0.8" + prop-types "^15.5.8" + react-overlays "^4.1.1" + react-popper "^1.0.0" + scroll-into-view-if-needed "^2.2.20" + warning "^4.0.1" + react-bootstrap@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-1.5.2.tgz#07dabec53d10491a520c49f102170b440fa89008" @@ -9342,6 +9387,20 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-overlays@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-4.1.1.tgz#0060107cbe1c5171a744ccda3fbf0556d064bc5f" + integrity sha512-WtJifh081e6M24KnvTQoNjQEpz7HoLxqt8TwZM7LOYIkYJ8i/Ly1Xi7RVte87ZVnmqQ4PFaFiNHZhSINPSpdBQ== + dependencies: + "@babel/runtime" "^7.12.1" + "@popperjs/core" "^2.5.3" + "@restart/hooks" "^0.3.25" + "@types/warning" "^3.0.0" + dom-helpers "^5.2.0" + prop-types "^15.7.2" + uncontrollable "^7.0.0" + warning "^4.0.3" + react-overlays@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-5.0.1.tgz#7e2c3cd3c0538048b0b7451d203b1289c561b7f2" @@ -9356,6 +9415,19 @@ react-overlays@^5.0.0: uncontrollable "^7.2.1" warning "^4.0.3" +react-popper@^1.0.0: + version "1.3.11" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.11.tgz#a2cc3f0a67b75b66cfa62d2c409f9dd1fcc71ffd" + integrity sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg== + dependencies: + "@babel/runtime" "^7.1.2" + "@hypnosphi/create-react-context" "^0.3.1" + deep-equal "^1.1.1" + popper.js "^1.14.4" + prop-types "^15.6.1" + typed-styles "^0.0.7" + warning "^4.0.2" + react-refresh@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -10035,6 +10107,13 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" +scroll-into-view-if-needed@^2.2.20: + version "2.2.28" + resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz#5a15b2f58a52642c88c8eca584644e01703d645a" + integrity sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w== + dependencies: + compute-scroll-into-view "^1.0.17" + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -11103,6 +11182,11 @@ type@^2.0.0: resolved "https://registry.yarnpkg.com/type/-/type-2.3.0.tgz#ada7c045f07ead08abf9e2edd29be1a0c0661132" integrity sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg== +typed-styles@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" + integrity sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -11115,7 +11199,7 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -uncontrollable@^7.2.1: +uncontrollable@^7.0.0, uncontrollable@^7.2.1: version "7.2.1" resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== @@ -11454,7 +11538,7 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" -warning@^4.0.0, warning@^4.0.3: +warning@^4.0.0, warning@^4.0.1, warning@^4.0.2, warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==