diff --git a/ui/public/data/bundles.json b/ui/public/data/bundles.json new file mode 100644 index 000000000..11a20d22d --- /dev/null +++ b/ui/public/data/bundles.json @@ -0,0 +1,10 @@ +[ + { + "resourceId": "abc", + "name": "Bundle 1", + "attributes": [ + "abc123", + "xyz456" + ] + } +] \ No newline at end of file diff --git a/ui/src/app/core/components/Header.js b/ui/src/app/core/components/Header.js index 0c925ac50..95047f1e0 100644 --- a/ui/src/app/core/components/Header.js +++ b/ui/src/app/core/components/Header.js @@ -4,11 +4,9 @@ import { Link } from 'react-router-dom'; import Nav from 'react-bootstrap/Nav'; import Navbar from 'react-bootstrap/Navbar'; import Dropdown from 'react-bootstrap/Dropdown'; -import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; -import Tooltip from 'react-bootstrap/Tooltip'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTh, faSignOutAlt, faPlusCircle, faCube, faCubes, faUsersCog, faUser, faSpinner, faUserCircle } from '@fortawesome/free-solid-svg-icons'; +import { faTh, faSignOutAlt, faPlusCircle, faCube, faCubes, faUsersCog, faSpinner, faUserCircle, faCog, faLayerGroup, faFileArchive } from '@fortawesome/free-solid-svg-icons'; import Translate from '../../i18n/components/translate'; import { useTranslator } from '../../i18n/hooks'; @@ -67,7 +65,7 @@ export function Header () { {isAdmin && - + @@ -75,6 +73,10 @@ export function Header () { + + + + @@ -84,7 +86,7 @@ export function Header () { } - + diff --git a/ui/src/app/metadata/Attribute.js b/ui/src/app/metadata/Attribute.js index c8d2aa502..a963887d1 100644 --- a/ui/src/app/metadata/Attribute.js +++ b/ui/src/app/metadata/Attribute.js @@ -4,6 +4,7 @@ import { MetadataAttributes } from './hoc/MetadataAttributes'; import { NewAttribute } from './new/NewAttribute'; import { MetadataAttributeEdit } from './view/MetadataAttributeEdit'; import { MetadataAttributeList } from './view/MetadataAttributeList'; +import { MetadataAttributeBundles } from './view/MetadataAttributeBundles'; export function Attribute() { @@ -24,6 +25,9 @@ export function Attribute() { } /> + + + } /> ); diff --git a/ui/src/app/metadata/domain/attribute/AttributeBundleDefinition.js b/ui/src/app/metadata/domain/attribute/AttributeBundleDefinition.js new file mode 100644 index 000000000..464bd1d59 --- /dev/null +++ b/ui/src/app/metadata/domain/attribute/AttributeBundleDefinition.js @@ -0,0 +1,29 @@ +import { defaultsDeep } from "lodash"; + +export const AttributeBundleDefinition = { + label: 'Metadata Attribute Bundle', + type: '@MetadataAttributeBundle', + steps: [], + schema: `/assets/schema/attribute/bundle.schema.json`, + + uiSchema: { + + }, + + parser: (data) => { + return data; + }, + + formatter: (changes) => { + return changes; + } +} + +export const CustomAttributeEditor = { + ...AttributeBundleDefinition, + uiSchema: defaultsDeep({ + attributeType: { + 'ui:disabled': true + } + }, AttributeBundleDefinition.uiSchema) +}; \ No newline at end of file diff --git a/ui/src/app/metadata/hoc/attribute/AttributeBundleApi.js b/ui/src/app/metadata/hoc/attribute/AttributeBundleApi.js new file mode 100644 index 000000000..c7ce85380 --- /dev/null +++ b/ui/src/app/metadata/hoc/attribute/AttributeBundleApi.js @@ -0,0 +1,69 @@ +import React from 'react'; +import useFetch from 'use-http'; +import API_BASE_PATH from '../../../App.constant'; + +import { DeleteConfirmation } from '../../../core/components/DeleteConfirmation'; +import { createNotificationAction, NotificationContext } from '../../../notifications/hoc/Notifications'; + +const api = '/custom/entity/bundles' + +export function AttributeBundleApi({ id, children }) { + + const { dispatch } = React.useContext(NotificationContext); + + const { get, put, post, del, response, loading } = useFetch(`/data/bundles.json`, { + cachePolicy: 'no-cache' + }); + + async function load(cb) { + const b = await get(``); + if (response.ok) { + cb && cb(b); + } + } + + async function find(id, cb) { + const b = await get(`/${id }`); + if (response.ok) { + cb && cb(b); + } + } + + async function update(id, body, cb) { + const b = await put(`/${id}`, body); + if (response.ok) { + dispatch(createNotificationAction( + `Bundle has been updated.` + )); + cb && cb(b); + } + } + + async function create(body, cb) { + const b = await post(``, body); + if (response.ok) { + dispatch(createNotificationAction( + `Bundle has been created.` + )); + cb && cb(b); + } + } + + async function remove(id, cb = () => { }) { + await del(`/${id}`); + if (response.ok) { + dispatch(createNotificationAction( + `Bundle has been deleted.` + )); + cb(); + } + } + + return ( + + {(block) => +
{children(load, find, create, update, (id) => block(() => remove(id)), loading)}
+ } +
+ ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/hoc/attribute/AttributeBundleList.js b/ui/src/app/metadata/hoc/attribute/AttributeBundleList.js new file mode 100644 index 000000000..0cf5b54d7 --- /dev/null +++ b/ui/src/app/metadata/hoc/attribute/AttributeBundleList.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export function AttributeBundleList({ load, children }) { + + const [bundles, setBundles] = React.useState([]); + + React.useEffect(() => { + load((list) => setBundles(list)); + }, []); + + return ( + {children(bundles)} + ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/hoc/attribute/AttributeBundleSelector.js b/ui/src/app/metadata/hoc/attribute/AttributeBundleSelector.js new file mode 100644 index 000000000..2ec7ffe4d --- /dev/null +++ b/ui/src/app/metadata/hoc/attribute/AttributeBundleSelector.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { useParams } from 'react-router-dom'; + +export function AttributeBundleSelector({ id, find, children }) { + const [bundle, setBundle] = React.useState([]); + + React.useEffect(() => { + find(id, (item) => setBundle(item)); + }, []); + + return ( + {children(bundle)} + ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/new/NewBundle.js b/ui/src/app/metadata/new/NewBundle.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/src/app/metadata/view/MetadataAttributeBundleEdit.js b/ui/src/app/metadata/view/MetadataAttributeBundleEdit.js new file mode 100644 index 000000000..e84395b89 --- /dev/null +++ b/ui/src/app/metadata/view/MetadataAttributeBundleEdit.js @@ -0,0 +1,86 @@ +import React from 'react'; +import { faSave, faSpinner } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import Button from 'react-bootstrap/Button'; +import { Prompt, useHistory, useParams } from 'react-router'; +import Translate from '../../i18n/components/translate'; +import { MetadataAttributeEditor } from '../editor/MetadataAttributeEditor'; + +import { AttributeBundleDefinition } from '../domain/attribute/AttributeBundleDefinition'; +import MetadataSchema from '../hoc/MetadataSchema'; +import { MetadataForm } from '../hoc/MetadataFormContext'; +import { AttributeBundleSelector } from '../hoc/attribute/AttributeBundleSelector'; +import { AttributeBundleApi } from '../hoc/attribute/AttributeBundleApi'; + +export function MetadataAttributeBundleEdit() { + const { id } = useParams(); + const history = useHistory(); + + const definition = AttributeBundleDefinition; + + const [blocking, setBlocking] = React.useState(false); + + const cancel = () => { + gotoDetail(); + }; + + const gotoDetail = (state = null) => { + setBlocking(false); + history.push(`/metadata/attributes`, state); + }; + + return ( + + {(load, find, create, update, remove, loading) => + + {(bundle) =>
+ + `message.unsaved-editor` + } + /> +
+
+
+
+ Add a new metadata attribute +
+
+
+
+ + {bundle && + + + {(filter, errors) => + + + + + } + + + } + +
+
+
+ } +
+ } +
+ ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/view/MetadataAttributeBundles.js b/ui/src/app/metadata/view/MetadataAttributeBundles.js new file mode 100644 index 000000000..520080b41 --- /dev/null +++ b/ui/src/app/metadata/view/MetadataAttributeBundles.js @@ -0,0 +1,82 @@ +import React from 'react'; +import { faEdit, faPlusCircle, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import Button from 'react-bootstrap/Button'; +import { Link } from 'react-router-dom'; + +import { Translate } from '../../i18n/components/translate'; +import { useTranslator } from '../../i18n/hooks'; + +import { AttributeBundleApi } from '../hoc/attribute/AttributeBundleApi'; + +import { AttributeBundleList } from '../hoc/attribute/AttributeBundleList'; + +export function MetadataAttributeBundles({ entities, onDelete }) { + + const translator = useTranslator(); + + return ( + + {(load, find, create, update, remove, loading) => + + {(bundles) => +
+
+
+
+ + Attribute Bundles + +
+
+
+ +   + Add new bundle + +
+
+ + + + + + + + + {bundles.map((bundle, i) => + + + + + )} + +
+ Bundle Name + Actions
{bundle.name} + + + + Edit + + + +
+
+
+
+ +
+
+ } +
+ } +
+ ); +} \ No newline at end of file