diff --git a/react-ui/src/components/devices/view/device.scss b/react-ui/src/components/devices/view/device.scss index 8d4099fc88cacccd6dd4b91d45db664668c5260c..bce275265104f8639d29cc520018f4b7edf8c19e 100755 --- a/react-ui/src/components/devices/view/device.scss +++ b/react-ui/src/components/devices/view/device.scss @@ -55,3 +55,41 @@ text-decoration: underline; } } +.transitions { + $transition-duration-hover: 0.1s; + $transition-duration-change: 0.2s; + transition: + --startOpacity $transition-duration-hover ease-in-out, + --startPosition $transition-duration-hover ease-in-out; +} + +@property --startOpacity { + syntax: "<number>"; + initial-value: 0.35; + inherits: false; +} + +@property --startPosition { + syntax: "<percentage>"; + initial-value: 10%; + inherits: false; +} + +.bg-gradient-fade { + $primary-color: map-get($theme-colors, "primary"); + background: linear-gradient( + to bottom, + rgba($primary-color, var(--startOpacity)) var(--startPosition), + rgba($primary-color, 0.1) 100% + ); + + &:hover { + --startOpacity: 0.4; + --startPosition: 30%; + } +} + +.disabled-hover:hover { + $primary-color: rgba(map-get($theme-colors, "dark"), 0.1); + background: linear-gradient(to bottom, rgb(223, 223, 223) 1%, white 100%); +} diff --git a/react-ui/src/components/devices/view/device.view.table.tsx b/react-ui/src/components/devices/view/device.view.table.tsx index f1eb53130e7fa34b2bf0cdb24b04242d45d38e51..ff46862e7c3983d741c66716f9df43ef40375de2 100755 --- a/react-ui/src/components/devices/view/device.view.table.tsx +++ b/react-ui/src/components/devices/view/device.view.table.tsx @@ -27,7 +27,7 @@ export const DeviceList = ({ searchRef }: { searchRef: RefObject<HTMLInputElemen const { id } = device setExpandedId(expandedId === id ? null : id); }, [expandedId]); - + 1 const getDeviceList = useCallback(() => { const search = searchRef?.current?.value; let filtered = devices; @@ -53,12 +53,14 @@ export const DeviceList = ({ searchRef }: { searchRef: RefObject<HTMLInputElemen return ( <div key={deviceId} - className={`border-bottom border-primary p-2 transition-bg ${isSelected && 'bg-gradient-fade'} ${!isSelected && 'text-disabled'}`} + className={`border-bottom border-primary p-2 transitions ${isSelected && 'bg-gradient-fade py-2'} ${!isSelected && 'text-disabled disabled-hover'}`} + onClick={() => !isExpanded && handleItemClick(device)} > <div aria-expanded={isExpanded} - className="d-flex justify-content-between py-4 clickable" - onClick={() => handleItemClick(device)}> + className="d-flex justify-content-between py-2 clickable" + onClick={() => isExpanded && handleItemClick(device)} + > <FontAwesomeIcon icon={faChevronDown} rotation={isExpanded ? undefined : 270} /> <span dangerouslySetInnerHTML={{ __html: search ? insertMarkTags(devicename, search) : DOMPurify.sanitize(devicename) @@ -85,7 +87,7 @@ export const DeviceList = ({ searchRef }: { searchRef: RefObject<HTMLInputElemen return ( <div className="rounded border border-primary mt-2"> - <div className="border-bottom border-primary d-flex justify-content-between px-4 py-2 clickable" onClick={() => handleItemClick(device)}> + <div className="border-bottom border-primary d-flex justify-content-between px-4 py-2 clickable"> <FontAwesomeIcon icon={faChevronDown} className="opacity-0" /> <span className="font-medium">{t('device.table.header.name')}</span> <span className="font-medium">{t('device.table.header.uuid')}</span> diff --git a/react-ui/src/components/devices/view/device.view.tsx b/react-ui/src/components/devices/view/device.view.tsx index 3e4b822d029f3511e47119c91da73c77c2e22835..350abadd0cd4e742a33295889aebd83376b265ef 100755 --- a/react-ui/src/components/devices/view/device.view.tsx +++ b/react-ui/src/components/devices/view/device.view.tsx @@ -14,7 +14,7 @@ import { DeviceList } from './device.view.table'; const DeviceView = () => { const { t } = useTranslation('common'); const searchRef = useRef<HTMLInputElement>(null); - const { jsonYang } = useDeviceViewModel(); + const { jsonYang, selectedDevice } = useDeviceViewModel(); return ( <div className='m-4 pt-4'> @@ -28,8 +28,8 @@ const DeviceView = () => { /> <FontAwesomeIcon icon={faGripVertical} className="drag-handle" /> <Row> - <Col sm={12} className='mt-4'> - <h3 className='text-black-50'>{t('device.title')}</h3> + <Col sm={12}> + <h3 className='c-box-title'>{t('device.title')}</h3> </Col> </Row> <Row> @@ -59,8 +59,8 @@ const DeviceView = () => { /> <FontAwesomeIcon icon={faGripVertical} className="drag-handle" /> <Row> - <Col xs={12} className='mt-4'> - {t('device.tabs.yang_model.title')} + <Col xs={12}> + <h3 className='c-box-title'>{t('device.tabs.yang_model.title')} <small>{selectedDevice?.device.name}</small></h3> </Col> </Row> <Row className='align-items-start'> diff --git a/react-ui/src/components/devices/view/subcomponent/device.view.list-detail.tsx b/react-ui/src/components/devices/view/subcomponent/device.view.list-detail.tsx index 6e904313da7ac192454bf9bd3ec927039ef1a5a7..7473eba6c488f28653b118b4f298b03a9599a83e 100644 --- a/react-ui/src/components/devices/view/subcomponent/device.view.list-detail.tsx +++ b/react-ui/src/components/devices/view/subcomponent/device.view.list-detail.tsx @@ -4,7 +4,7 @@ import { insertMarkTags } from "@helper/text"; import { useAppSelector } from "@hooks"; import { JsonViewer } from "@shared/components/json_viewer/view/json_viewer.view"; import DOMPurify from 'dompurify'; -import { useCallback, useEffect, useState } from "react"; +import { useState } from "react"; import { Collapse } from "react-bootstrap"; interface DeviceListCollapsableProps { @@ -13,60 +13,48 @@ interface DeviceListCollapsableProps { search?: string, } - export const DeviceListCollapsable = ({ deviceId, username, search }: DeviceListCollapsableProps) => { const { selected } = useAppSelector(state => state.device); - const [metadata, setMetadata] = useState<boolean>(false) - - - useEffect(() => { - if (!selected?.json) return; - }) - - + const [metadata, setMetadata] = useState<boolean>(true) - const content = useCallback(() => { - if (!selected?.json) return; + const json = selected?.json || {} - const key = Object.keys(selected.json).at(2) as keyof typeof selected.json - const metadataObject = selected.json[key] as JSON; + const key = Object.keys(json).at(2) as keyof typeof json + const metadataObject = json[key] as JSON || {}; - return ( - <div id={`collapse-${deviceId}`}> - <div className="pb-4 pt-1 d-flex flex-column gap-1" > - <div className="d-flex justify-content-between"> - <div> - <FontAwesomeIcon className="me-2" icon={faHashtag} /> - UUID: - </div> - <span dangerouslySetInnerHTML={{ - __html: search ? insertMarkTags(deviceId, search) : DOMPurify.sanitize(deviceId) - }} /> + return ( + <div id={`collapse-${deviceId}`}> + <div className="pb-4 pt-1 d-flex flex-column gap-1" > + <div className="d-flex justify-content-between"> + <div> + <FontAwesomeIcon className="me-2" icon={faHashtag} /> + UUID: </div> - <div className="d-flex justify-content-between"> - <div> - <FontAwesomeIcon className="me-2" icon={faUser} /> - User: - </div> - <span>{username}</span> + <span dangerouslySetInnerHTML={{ + __html: search ? insertMarkTags(deviceId, search) : DOMPurify.sanitize(deviceId) + }} /> + </div> + <div className="d-flex justify-content-between"> + <div> + <FontAwesomeIcon className="me-2" icon={faUser} /> + User: </div> + <span>{username}</span> + </div> - <div className="d-flex justify-content-between clickable border-top border-dark mt-3 pt-2" aria-expanded={metadata} onClick={() => setMetadata(!metadata)}> - <div> - <FontAwesomeIcon icon={faChevronDown} rotation={metadata ? undefined : 270} /> - Metadata: - </div> + <div className="d-flex justify-content-between clickable border-top border-dark mt-3 pt-1" aria-expanded={metadata} onClick={() => setMetadata(!metadata)}> + <div> + <FontAwesomeIcon icon={faChevronDown} rotation={metadata ? undefined : 270} /> + Metadata </div> - - <Collapse in={metadata}> - <div id={`collapse-${deviceId}`}> - {JsonViewer({ json: metadataObject, options: { editable: false, searchEnabled: false } })} - </div> - </Collapse> </div> - </div > - ) - }, [metadata]) - return content() + <Collapse in={metadata}> + <div id={`collapse-${deviceId}`}> + {JsonViewer({ json: metadataObject, options: { editable: false, searchEnabled: false } })} + </div> + </Collapse> + </div> + </div > + ) } \ No newline at end of file diff --git a/react-ui/src/components/devices/view_model/device.viewmodel.ts b/react-ui/src/components/devices/view_model/device.viewmodel.ts index ba2ee88a934b320fb8bc233c19a9d98b72c3b026..f2fd635533e7de2e4931964ba0333fcdad9a9969 100755 --- a/react-ui/src/components/devices/view_model/device.viewmodel.ts +++ b/react-ui/src/components/devices/view_model/device.viewmodel.ts @@ -22,6 +22,7 @@ export const useDeviceViewModel = () => { const jsonYang = useMemo<JSON | null>(getYangModelJSON, [selectedDevice]) return { - jsonYang + jsonYang, + selectedDevice } } diff --git a/react-ui/src/i18n/locales/en/translations.json b/react-ui/src/i18n/locales/en/translations.json index 53444b9e0d6745f364a559342164dca89d7abc0f..e23061744f900487acb671353fd5cbfaed64eb4e 100755 --- a/react-ui/src/i18n/locales/en/translations.json +++ b/react-ui/src/i18n/locales/en/translations.json @@ -47,9 +47,6 @@ }, "add_device_button": "Add device", "tabs": { - "metadata": { - "title": "Metadata" - }, "yang_model": { "title": "YANG Model" } diff --git a/react-ui/src/shared/components/json_viewer/view/json_viewer.view.tsx b/react-ui/src/shared/components/json_viewer/view/json_viewer.view.tsx index da107ca93fce13f5a69c9db8ef50d97487c23f69..ba53d64d7446ae07daffffcd5467734b852c72cd 100755 --- a/react-ui/src/shared/components/json_viewer/view/json_viewer.view.tsx +++ b/react-ui/src/shared/components/json_viewer/view/json_viewer.view.tsx @@ -23,18 +23,6 @@ export const JsonViewer = ({ json, options = { searchEnabled: true, editable: tr const { getSubset, breadcrumbs, isCollapsed, collapseable, collapse, parameterizedJson, searchTerm } = useJsonViewer({ json, search, container: htmlContainer }); - const breadcrumbHTML = useMemo(() => { - return ( - <nav aria-label="breadcrumb"> - <ol className="breadcrumb"> - {breadcrumbs.map(breadcrumb => ( - <li key={breadcrumb} className="breadcrumb-item"><a href="#">{breadcrumb}</a></li> - ))} - </ol> - </nav> - ) - }, [breadcrumbs]) - const renderInner = (innerJson: JSON, nested: number = 0, parentKey: string = "", path: string = "/network-instance/0/"): JSX.Element => { path += parentKey + (parentKey === "" ? "" : "/") @@ -129,7 +117,7 @@ export const JsonViewer = ({ json, options = { searchEnabled: true, editable: tr const searchHTML = (): React.ReactElement => { return ( - <Form.Group controlId='json_viewer.search' className='p-0 mx-1 pt-2'> + <Form.Group controlId='json_viewer.search' className='p-0 '> <Form.Control type="text" placeholder={t('device.search.placeholder')} ref={search} /> </Form.Group> ) @@ -138,7 +126,6 @@ export const JsonViewer = ({ json, options = { searchEnabled: true, editable: tr return ( <div ref={htmlContainer}> {options?.searchEnabled && searchHTML()} - {breadcrumbHTML} {hierarchyHTML} </div> ) diff --git a/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx b/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx index 832e2b229bbbb8d8b23c2595291f683c79ef40d6..6a0c80c7e0ec7d09354415bec67fb1b33f333399 100644 --- a/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx +++ b/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx @@ -132,7 +132,7 @@ export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelTy innerSearch(json, searchTerm); return parameterizedJsonMap - }, [searchTerm]) + }, [searchTerm, json]) useEffect(() => { if (!container.current || !subscribe) { diff --git a/react-ui/src/shared/style/box.scss b/react-ui/src/shared/style/box.scss index 98475a02e2193bc5152921be09094a455db36373..62dd6530d63e2d4680b6ae9082e80afa50b7b95e 100755 --- a/react-ui/src/shared/style/box.scss +++ b/react-ui/src/shared/style/box.scss @@ -1,12 +1,12 @@ @import "./colors.scss"; -$box-padding: 1em; +$box-padding: 1.5em; $border-radius: 0.25em; $border-width: 2px; $transition-duration: 0.3s; .c-box { - padding: $box-padding !important; + padding: $box-padding / 2 $box-padding !important; background-color: white; position: relative; transition: box-shadow $transition-duration ease-in-out; @@ -23,6 +23,24 @@ $transition-duration: 0.3s; } } +.c-box-title { + $text-color: black; + color: $text-color; + padding: 0.5em 0; + margin-top: 0.2em; + + small { + font-size: 0.75em; + color: rgba($text-color, 0.65); + &::before { + content: "("; + } + &::after { + content: ")"; + } + } +} + .rounded { border-radius: $border-radius; } @@ -64,33 +82,3 @@ $transition-duration: 0.3s; transition: opacity $transition-duration ease-in-out; } } - -@property --startOpacity { - syntax: "<number>"; - initial-value: 0.35; - inherits: false; -} - -@property --startPosition { - syntax: "<percentage>"; - initial-value: 10%; - inherits: false; -} - -.bg-gradient-fade { - $primary-color: map-get($theme-colors, "primary"); - background: linear-gradient( - to bottom, - rgba($primary-color, var(--startOpacity)) var(--startPosition), - rgba($primary-color, 0.1) 100% - ); - - &:hover { - --startOpacity: 0.4; - --startPosition: 30%; - } - - transition: - --startOpacity $transition-duration ease-in-out, - --startPosition $transition-duration ease-in-out; -}