Skip to content

Commit

Permalink
UI: adds configurable 'video feed' widgets
Browse files Browse the repository at this point in the history
Adds support for 'video feed' widgets. Each widget provide access
to a video feed from 'Data collection' tab. This is typically used
for giving users an easy way to access hutch view cameras.

Adds new 'camera_setup' config to mxcube-web/ui.yaml. This element
allows to configure multiple video feeds to be made accessable from
'Data collection' tab.

Each configured video feed creates a button with a camera icon.
Clicking the button will show/hide the video feed. It also allows
to create a pop-out windows with the video feed.
  • Loading branch information
Jakub Kowalczyk authored and elmjag committed Sep 12, 2023
1 parent 8fd3d8d commit aa2361f
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 1 deletion.
4 changes: 4 additions & 0 deletions mxcube3/core/models/configmodels.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class UIComponentModel(BaseModel):
precision: Optional[int]
suffix: Optional[str]
format: Optional[str]
url: Optional[str]
description: Optional[str]
width: Optional[int]
height: Optional[int]

# Set internaly not to be set through configuration
value_type: Optional[str]
Expand Down
16 changes: 15 additions & 1 deletion test/HardwareObjectsMockup.xml/mxcube-web/ui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,18 @@ beamline_setup:
attribute: flux
precision: 2
suffix: ph/s
format: expo
format: expo

camera_setup:
id: camera_setup
components:
- label: Camera 1
attribute: jpg
url: http://localhost:8080/mjpg/video.mjpg?streamprofile=mxcube
width: 800
height: 450
- label: Camera 2
attribute: jpg
url: http://localhost:9090/mjpg/video.mjpg?streamprofile=mxcube
width: 800
height: 450
128 changes: 128 additions & 0 deletions ui/src/components/BeamlineCamera/BeamlineCamera.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React, { useState } from 'react';
import { Badge, Button, OverlayTrigger, Popover } from 'react-bootstrap';
import './style.css';
import pip from './picture_in_picture.svg';

const BeamlineCamera = ({
labelText,
url,
width,
height,
optionsOverlay,
format,
description,
}) => {
const [showLabelOverlay, setShowLabelOverlay] = useState(false);
const [showValueOverlay, setShowValueOverlay] = useState(false);

const onImageClick = (ev) => {
setShowValueOverlay(false);
window.open(
url,
'webcam',
`toolbar=0,location=0,menubar=0,addressbar=0,height=${height},width=${width}`,
'popup',
);
};

const renderLabel = () => {
if (!optionsOverlay) {
return (
<Badge bg="secondary" style={{ display: 'block', marginBottom: '3px' }}>
{labelText}
</Badge>
);
}

return (
<OverlayTrigger
show={showLabelOverlay}
rootClose
trigger="click"
placement="bottom"
overlay={optionsOverlay}
>
<div onClick={() => setShowLabelOverlay(!showLabelOverlay)}>
<Badge
bg="secondary"
style={{ display: 'block', marginBottom: '3px' }}
>
{labelText}
<i className="fas fa-cog ms-2" />
</Badge>
</div>
</OverlayTrigger>
);
};

const msgLabelStyle = {
display: 'block',
fontSize: '100%',
borderRadius: '0px',
color: '#000',
};

const video = (
<div>
{format != 'mp4' ? (
<img
onClick={onImageClick}
src={url}
alt={labelText}
width={width}
height={height}
/>
) : (
<video
src={url}
alt={labelText}
onClick={onImageClick}
width={width}
height={height}
></video>
)}
<Button
variant="outline-secondary"
onClick={onImageClick}
size="sm"
style={{ position: 'absolute', left: '90%', bottom: '10px' }}
>
<img src={pip} alt="PIP Icon" />
</Button>
</div>
);

return (
<div className="inout-switch">
{renderLabel()}
<OverlayTrigger
show={showValueOverlay}
rootClose
trigger="click"
placement="bottom"
overlay={
<Popover style={{ padding: '0.5em' }} id={`${labelText} popover`}>
{video}
</Popover>
}
>
<div onClick={() => setShowValueOverlay(!showValueOverlay)}>
<Badge bg="success" style={msgLabelStyle}>
<i className="fas fa-video" />
</Badge>
</div>
</OverlayTrigger>
</div>
);
};

BeamlineCamera.defaultProps = {
labelText: '',
width: 0,
height: 0,
url: '',
optionsOverlay: false,
format: '',
};

export default BeamlineCamera;
1 change: 1 addition & 0 deletions ui/src/components/BeamlineCamera/picture_in_picture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions ui/src/components/BeamlineCamera/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.inout-switch {
width: 7em;
}

.inout-switch .badge {
overflow: hidden;
text-overflow: ellipsis;
}
28 changes: 28 additions & 0 deletions ui/src/containers/BeamlineSetupContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { Navbar, Nav, Table, Popover } from 'react-bootstrap';
import PopInput from '../components/PopInput/PopInput';
import BeamlineActions from './BeamlineActionsContainer';
import BeamlineCamera from '../components/BeamlineCamera/BeamlineCamera';
import InOutSwitch from '../components/InOutSwitch/InOutSwitch';
import SampleChangerSwitch from '../components/SampleChangerSwitch/SampleChangerSwitch';
import DeviceState from '../components/DeviceState/DeviceState';
Expand Down Expand Up @@ -134,6 +135,32 @@ class BeamlineSetupContainer extends React.Component {
return acts;
}

createCameraComponent() {
const acts = [];

const { uiproperties } = this.props;

if (uiproperties.hasOwnProperty('camera_setup')) {
for (const [
key,
camera,
] of uiproperties.camera_setup.components.entries()) {
acts.push(
<Nav.Item key={key} className="ms-3">
<BeamlineCamera
labelText={camera.label}
format={camera.attribute}
url={camera.url}
width={camera.width}
height={camera.height}
/>
</Nav.Item>,
);
}
}
return acts;
}

dmState() {
return this.props.beamline.hardwareObjects.diffractometer.state;
}
Expand Down Expand Up @@ -247,6 +274,7 @@ class BeamlineSetupContainer extends React.Component {
</Table>
</Nav.Item>
</Nav>
<Nav className="me-3">{this.createCameraComponent()}</Nav>
<Nav className="me-3">
<Nav.Item>
<DeviceState
Expand Down

0 comments on commit aa2361f

Please sign in to comment.