finished tweaking the bin players visual aspect and controls, made some smaller visual adjustments and added a checkbox for showing labels
This commit is contained in:
parent
d30ed99ca7
commit
f6de509561
6 changed files with 241 additions and 25 deletions
|
|
@ -163,7 +163,7 @@ export function BuildCableData(bin: Bin, dims: BinDims): CableData[] {
|
||||||
const layout = getLayoutFromDiameter(dims.diameter);
|
const layout = getLayoutFromDiameter(dims.diameter);
|
||||||
const distributed = distributeCables(cables.length, layout);
|
const distributed = distributeCables(cables.length, layout);
|
||||||
|
|
||||||
const cableRadius = 5;
|
const cableRadius = 3;
|
||||||
|
|
||||||
const result: CableData[] = [];
|
const result: CableData[] = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export interface NodeData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BuildNodeData(cables: CableData[]): NodeData[] {
|
export function BuildNodeData(cables: CableData[]): NodeData[] {
|
||||||
const nodeRadius = 15
|
const nodeRadius = 10
|
||||||
let nodeData: NodeData[] = []
|
let nodeData: NodeData[] = []
|
||||||
|
|
||||||
cables.forEach((cable, cableIndex) => {
|
cables.forEach((cable, cableIndex) => {
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,10 @@ export default function CableNode(props: Props) {
|
||||||
const { camera } = useThree();
|
const { camera } = useThree();
|
||||||
const [{ user }] = useGlobalState();
|
const [{ user }] = useGlobalState();
|
||||||
// const [showLabel, setShowLabel] = useState(false);
|
// const [showLabel, setShowLabel] = useState(false);
|
||||||
const ROW_HEIGHT = 45;
|
const ROW_HEIGHT = 35;
|
||||||
const PADDING = 15;
|
const PADDING = 5;
|
||||||
const LABEL_WIDTH = 220;
|
const LABEL_WIDTH = 200;
|
||||||
|
const FONT_SIZE = 25;
|
||||||
const flagRef = React.useRef<Group>(null);
|
const flagRef = React.useRef<Group>(null);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
|
|
@ -170,7 +171,7 @@ export default function CableNode(props: Props) {
|
||||||
<Text
|
<Text
|
||||||
key={i}
|
key={i}
|
||||||
position={[0, getRowY(i), 0]}
|
position={[0, getRowY(i), 0]}
|
||||||
fontSize={35}
|
fontSize={FONT_SIZE}
|
||||||
fontWeight={650}
|
fontWeight={650}
|
||||||
color={row.color}
|
color={row.color}
|
||||||
anchorX="center"
|
anchorX="center"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import { Button } from "@mui/material";
|
import { DateRange, PlayArrow, Start, Stop } from "@mui/icons-material";
|
||||||
|
import { Box, Button, DialogActions, DialogContent, FormControl, IconButton, LinearProgress, MenuItem, Select, TextField, Typography } from "@mui/material";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
import { GetDefaultDateRange } from "common/time/DateRange";
|
import { GetDefaultDateRange } from "common/time/DateRange";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { Bin } from "models";
|
import { Bin } from "models";
|
||||||
|
|
@ -12,6 +14,7 @@ const CHECKPOINT_COUNT = 10;
|
||||||
|
|
||||||
interface StatusCheckpoint {
|
interface StatusCheckpoint {
|
||||||
time: Moment;
|
time: Moment;
|
||||||
|
timeString: string;
|
||||||
cables: pond.GrainCable[];
|
cables: pond.GrainCable[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,6 +214,11 @@ function buildGrainCableFromReadings(
|
||||||
): pond.GrainCable {
|
): pond.GrainCable {
|
||||||
const cable = pond.GrainCable.fromObject(cloneDeep(template));
|
const cable = pond.GrainCable.fromObject(cloneDeep(template));
|
||||||
|
|
||||||
|
// Clear template values so empty checkpoints don't inherit live data
|
||||||
|
cable.celcius = [];
|
||||||
|
cable.relativeHumidity = [];
|
||||||
|
cable.moisture = [];
|
||||||
|
|
||||||
if (readings.temperature) {
|
if (readings.temperature) {
|
||||||
cable.celcius = [...readings.temperature.values];
|
cable.celcius = [...readings.temperature.values];
|
||||||
}
|
}
|
||||||
|
|
@ -238,6 +246,7 @@ function buildStatusCheckpoints(
|
||||||
count: number = CHECKPOINT_COUNT
|
count: number = CHECKPOINT_COUNT
|
||||||
): StatusCheckpoint[] {
|
): StatusCheckpoint[] {
|
||||||
const times = buildCheckpointTimes(start, end, count);
|
const times = buildCheckpointTimes(start, end, count);
|
||||||
|
console.log(times)
|
||||||
const cableKeys = templates.map(c => c.key);
|
const cableKeys = templates.map(c => c.key);
|
||||||
const buckets = assignMeasurementsToCheckpoints(
|
const buckets = assignMeasurementsToCheckpoints(
|
||||||
cableKeys,
|
cableKeys,
|
||||||
|
|
@ -259,8 +268,26 @@ function buildStatusCheckpoints(
|
||||||
carriedByCable.set(key, merged);
|
carriedByCable.set(key, merged);
|
||||||
return buildGrainCableFromReadings(template, merged, time);
|
return buildGrainCableFromReadings(template, merged, time);
|
||||||
});
|
});
|
||||||
return { time, cables };
|
return { time, cables, timeString: time.toISOString() };
|
||||||
});
|
}).filter(checkpoint => //filter the checkpoints to only include ones that have readings
|
||||||
|
checkpoint.cables.some(cable =>
|
||||||
|
cable.celcius.length > 0 ||
|
||||||
|
cable.relativeHumidity.length > 0 ||
|
||||||
|
cable.moisture.length > 0
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the number of checkpoints to create based on the start and end dates.
|
||||||
|
* Is hard coded to space the checkpoints 6 hours apart.
|
||||||
|
* @param start - The start date
|
||||||
|
* @param end - The end date
|
||||||
|
* @returns The number of checkpoints to create
|
||||||
|
*/
|
||||||
|
function checkpointCountFromRange(start: Moment, end: Moment): number {
|
||||||
|
const hours = end.diff(start, 'hours');
|
||||||
|
return Math.max(1, Math.floor(hours / 6) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -272,14 +299,52 @@ function buildStatusCheckpoints(
|
||||||
export default function BinPlayer(props: Props) {
|
export default function BinPlayer(props: Props) {
|
||||||
const { bin, componentDevices, setBin } = props;
|
const { bin, componentDevices, setBin } = props;
|
||||||
const componentAPI = useComponentAPI();
|
const componentAPI = useComponentAPI();
|
||||||
const defaultDateRange = GetDefaultDateRange();
|
//the default start and end dates are 1 week ago and now
|
||||||
const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
|
const [dateSelect, setDateSelect] = useState<number>(0);
|
||||||
const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
|
const [startDate, setStartDate] = useState<Moment>(moment().subtract(1, 'week'));
|
||||||
// const [statusCheckpoints, setStatusCheckpoints] = useState<StatusCheckpoint[]>([]);
|
const [endDate, setEndDate] = useState<Moment>(moment);
|
||||||
|
const [dateRangeDialog, setDateRangeDialog] = useState(false);
|
||||||
|
const [tempStartDate, setTempStartDate] = useState<Moment>(moment().subtract(1, 'week'));
|
||||||
|
const [tempEndDate, setTempEndDate] = useState<Moment>(moment());
|
||||||
|
const [currentCheckpointTime, setCurrentCheckpointTime] = useState<Moment | undefined>(undefined)
|
||||||
|
const [displayProgress, setDisplayProgress] = useState(0);
|
||||||
|
const totalCheckpoints = useRef(0);
|
||||||
|
const checkpointStartTime = useRef(0);
|
||||||
|
const currentCheckpointIndex = useRef(0);
|
||||||
const isPlaying = useRef(false);
|
const isPlaying = useRef(false);
|
||||||
|
const PLAYBACK_INTERVAL = 2000 //playback time between checkpoints
|
||||||
|
const [isPlayingState, setIsPlayingState] = useState(false);
|
||||||
const stopPlayer = () => { isPlaying.current = false; };
|
const stopPlayer = () => { isPlaying.current = false; };
|
||||||
const originalBin = useRef<Bin | null>(null);
|
const originalBin = useRef<Bin | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPlayingState) return;
|
||||||
|
|
||||||
|
const fps = 30;
|
||||||
|
const interval = 1000 / fps;
|
||||||
|
const stepDuration = PLAYBACK_INTERVAL; // must match your setTimeout delay
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (!isPlayingState) {
|
||||||
|
clearInterval(timer);
|
||||||
|
setDisplayProgress(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const total = totalCheckpoints.current;
|
||||||
|
if (total === 0) return;
|
||||||
|
|
||||||
|
const idx = currentCheckpointIndex.current;
|
||||||
|
// and change the progress formula to account for the last checkpoint reaching 100%:
|
||||||
|
const elapsed = Date.now() - checkpointStartTime.current;
|
||||||
|
const stepProgress = Math.min(elapsed / stepDuration, 1);
|
||||||
|
// use (total - 1) as denominator so the last checkpoint goes from ~end to 100%
|
||||||
|
const raw = total <= 1 ? 100 : ((idx + stepProgress) / (total - 1)) * 100;
|
||||||
|
setDisplayProgress(Math.min(raw, 100));
|
||||||
|
}, interval);
|
||||||
|
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [isPlayingState]); // re-run when playing state changes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches sampled unit measurements for every grain cable in the current bin status,
|
* Fetches sampled unit measurements for every grain cable in the current bin status,
|
||||||
* then builds `statusCheckpoints` for playback over [startDate, endDate].
|
* then builds `statusCheckpoints` for playback over [startDate, endDate].
|
||||||
|
|
@ -289,10 +354,11 @@ const startPlayer = async () => {
|
||||||
console.log(endDate.toISOString())
|
console.log(endDate.toISOString())
|
||||||
originalBin.current = cloneDeep(bin); // capture before async work begins
|
originalBin.current = cloneDeep(bin); // capture before async work begins
|
||||||
isPlaying.current = true;
|
isPlaying.current = true;
|
||||||
|
setIsPlayingState(true)
|
||||||
|
|
||||||
const sampleRequests = bin.status.grainCables.map(cable => {
|
const sampleRequests = bin.status.grainCables.map(cable => {
|
||||||
const deviceID = componentDevices.get(cable.key) ?? 0;
|
const deviceID = componentDevices.get(cable.key) ?? 0;
|
||||||
return componentAPI.sampleUnitMeasurements(deviceID, cable.key, startDate, endDate, 100);
|
return componentAPI.sampleUnitMeasurements(deviceID, cable.key, startDate, endDate, 100, undefined, undefined, undefined, undefined, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
const responses = await Promise.all(sampleRequests);
|
const responses = await Promise.all(sampleRequests);
|
||||||
|
|
@ -300,29 +366,162 @@ const startPlayer = async () => {
|
||||||
bin.status.grainCables,
|
bin.status.grainCables,
|
||||||
responses.map(r => r.data),
|
responses.map(r => r.data),
|
||||||
startDate,
|
startDate,
|
||||||
endDate
|
endDate,
|
||||||
|
checkpointCountFromRange(startDate, endDate)
|
||||||
);
|
);
|
||||||
console.log(responses)
|
console.log(responses)
|
||||||
console.log(checkpoints)
|
console.log(checkpoints)
|
||||||
|
|
||||||
for (const checkpoint of checkpoints) {
|
for (let i = 0; i < checkpoints.length; i++) {
|
||||||
if (!isPlaying.current) {
|
if (!isPlaying.current) {
|
||||||
setBin(originalBin.current!);
|
setBin(originalBin.current!);
|
||||||
|
setCurrentCheckpointTime(undefined);
|
||||||
|
setIsPlayingState(false)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
currentCheckpointIndex.current = i;
|
||||||
|
totalCheckpoints.current = checkpoints.length;
|
||||||
|
checkpointStartTime.current = Date.now();
|
||||||
const newBin = cloneDeep(originalBin.current!);
|
const newBin = cloneDeep(originalBin.current!);
|
||||||
newBin.status.grainCables = checkpoint.cables;
|
newBin.status.grainCables = checkpoints[i].cables;
|
||||||
setBin(newBin);
|
setBin(newBin);
|
||||||
await new Promise(res => setTimeout(res, 2000));
|
setCurrentCheckpointTime(checkpoints[i].time)
|
||||||
|
await new Promise(res => setTimeout(res, PLAYBACK_INTERVAL));
|
||||||
console.log("next step")
|
console.log("next step")
|
||||||
}
|
}
|
||||||
isPlaying.current = false;
|
isPlaying.current = false;
|
||||||
|
setIsPlayingState(false)
|
||||||
setBin(originalBin.current!);
|
setBin(originalBin.current!);
|
||||||
|
setCurrentCheckpointTime(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const datePickerDialog = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={dateRangeDialog}
|
||||||
|
onClose={() => setDateRangeDialog(false)}
|
||||||
|
aria-labelledby="date-range-dialog">
|
||||||
|
<DialogContent>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="date"
|
||||||
|
label="Start Date"
|
||||||
|
value={tempStartDate.format("YYYY-MM-DD")}
|
||||||
|
onChange={e => {
|
||||||
|
setTempStartDate(moment(e.target.value));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="date"
|
||||||
|
label="End Date"
|
||||||
|
value={tempEndDate.format("YYYY-MM-DD")}
|
||||||
|
onChange={e => {
|
||||||
|
setTempEndDate(moment(e.target.value));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setDateRangeDialog(false)} color="primary">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => {
|
||||||
|
const now = moment();
|
||||||
|
setStartDate(tempStartDate.clone().set({
|
||||||
|
hour: now.hour(),
|
||||||
|
minute: now.minute(),
|
||||||
|
second: now.second()
|
||||||
|
}));
|
||||||
|
setEndDate(tempEndDate.clone().set({
|
||||||
|
hour: now.hour(),
|
||||||
|
minute: now.minute(),
|
||||||
|
second: now.second()
|
||||||
|
}));
|
||||||
|
setDateRangeDialog(false);
|
||||||
|
}} color="primary">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dateSelector = () => {
|
||||||
|
return (
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<Select
|
||||||
|
value={dateSelect}
|
||||||
|
sx={{
|
||||||
|
borderRadius: 1,
|
||||||
|
bgcolor: 'rgba(255,255,255,0.08)',
|
||||||
|
'& .MuiSelect-select': {
|
||||||
|
py: 1.5,
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': { border: 'none' },
|
||||||
|
'&:hover .MuiOutlinedInput-notchedOutline': { border: 'none' },
|
||||||
|
'&.Mui-focused .MuiOutlinedInput-notchedOutline': { border: 'none' },
|
||||||
|
}}
|
||||||
|
onChange={event => {
|
||||||
|
setDateSelect(event.target.value as number);
|
||||||
|
let currentDate = moment();
|
||||||
|
setEndDate(currentDate);
|
||||||
|
switch (event.target.value) {
|
||||||
|
case 0:
|
||||||
|
setStartDate(currentDate.clone().subtract(1, 'week'));
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
setStartDate(currentDate.clone().subtract(2, 'weeks'));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
setStartDate(currentDate.clone().subtract(4, 'weeks'));
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>1 Week</MenuItem>
|
||||||
|
<MenuItem value={1}>2 Weeks</MenuItem>
|
||||||
|
<MenuItem value={2}>4 Weeks</MenuItem>
|
||||||
|
<MenuItem value={3}>Custom</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
{dateSelect === 3 && (
|
||||||
|
<Button onClick={() => setDateRangeDialog(true)} color="primary"><DateRange /></Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Button onClick={startPlayer}>play</Button>
|
{datePickerDialog()}
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="space-between" width="100%" gap={2} alignContent={"center"} alignItems={"center"}>
|
||||||
|
{/* start/stop button */}
|
||||||
|
<Box>
|
||||||
|
{isPlayingState ? (
|
||||||
|
<IconButton color="primary" sx={{ border: "1px solid", borderRadius: "50%", padding: 1 }} onClick={stopPlayer}><Stop /></IconButton>
|
||||||
|
) : (
|
||||||
|
<IconButton color="primary" sx={{ border: "1px solid", borderRadius: "50%", padding: 1 }} onClick={startPlayer}><PlayArrow /></IconButton>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
{/* progress bar and date display */}
|
||||||
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
|
<Typography textAlign={"center"}>{currentCheckpointTime ? currentCheckpointTime.format("MMM D, YYYY h:mm A") : moment().format("MMM D, YYYY h:mm A")}</Typography>
|
||||||
|
<LinearProgress value={displayProgress} variant="determinate" />
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
|
<Typography>{startDate.format("MMM D, YYYY")}</Typography>
|
||||||
|
<Typography>{endDate.format("MMM D, YYYY")}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{/* date range selector */}
|
||||||
|
<Box>
|
||||||
|
{dateSelector()}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Box, Checkbox, FormControl, MenuItem, List, ListItem, InputLabel, Select, Typography, Stack } from "@mui/material";
|
import { Box, Checkbox, FormControl, MenuItem, List, ListItem, InputLabel, Select, Typography, Stack, FormControlLabel } from "@mui/material";
|
||||||
import Bin3dView from "bin/3dView/Scene/Bin3dView";
|
import Bin3dView from "bin/3dView/Scene/Bin3dView";
|
||||||
import { Bin, Component, Device } from "models";
|
import { Bin, Component, Device } from "models";
|
||||||
import { Controller } from "models/Controller";
|
import { Controller } from "models/Controller";
|
||||||
|
|
@ -42,6 +42,7 @@ export default function bin3dVisualizer(props: Props){
|
||||||
const [openNodeControls, setOpenNodeControls] = useState(false)
|
const [openNodeControls, setOpenNodeControls] = useState(false)
|
||||||
const [devMap, setDevMap] = useState<Map<number, Device>>(new Map())
|
const [devMap, setDevMap] = useState<Map<number, Device>>(new Map())
|
||||||
const [modeChangeInProgress, setModeChangeInProgress] = useState(false)
|
const [modeChangeInProgress, setModeChangeInProgress] = useState(false)
|
||||||
|
const [showLabels, setShowLabels] = useState(true)
|
||||||
const isMobile = useMobile()
|
const isMobile = useMobile()
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
|
|
@ -102,7 +103,21 @@ export default function bin3dVisualizer(props: Props){
|
||||||
const heatmapDisplay = () => {
|
const heatmapDisplay = () => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography sx={{fontWeight: 650, fontSize: isMobile ? 13 : 20}}>Heatmap Display</Typography>
|
<Box display="flex" alignContent="center" alignItems={"center"} justifyContent={"space-between"}>
|
||||||
|
<Typography noWrap sx={{fontWeight: 650, fontSize: isMobile ? 13 : 20}}>Heatmap Display</Typography>
|
||||||
|
<FormControlLabel
|
||||||
|
labelPlacement="start"
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={showLabels}
|
||||||
|
onChange={(e, checked) => {
|
||||||
|
setShowLabels(checked)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={<Typography noWrap sx={{fontSize: 11}}>Show Values</Typography>}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
{/* <InputLabel>Display</InputLabel> */}
|
{/* <InputLabel>Display</InputLabel> */}
|
||||||
<Select
|
<Select
|
||||||
|
|
@ -380,8 +395,8 @@ export default function bin3dVisualizer(props: Props){
|
||||||
scale={100}
|
scale={100}
|
||||||
showTempHeatmap={showHeatmap}
|
showTempHeatmap={showHeatmap}
|
||||||
showMoistureHeatmap={showMoistureHeatmap}
|
showMoistureHeatmap={showMoistureHeatmap}
|
||||||
showTemp={showTemp}
|
showTemp={showTemp && showLabels}
|
||||||
showMoisture={showMoisture}
|
showMoisture={showMoisture && showLabels}
|
||||||
// showGrain={showFill}
|
// showGrain={showFill}
|
||||||
// showHotspots={showHotspots}
|
// showHotspots={showHotspots}
|
||||||
xOffset={isMobile ? undefined : -150}
|
xOffset={isMobile ? undefined : -150}
|
||||||
|
|
|
||||||
|
|
@ -402,8 +402,9 @@ export default function BinSummary(props: Props){
|
||||||
binPrefs={binPrefs}
|
binPrefs={binPrefs}
|
||||||
updateBinCallback={updateBinCallback}
|
updateBinCallback={updateBinCallback}
|
||||||
/>
|
/>
|
||||||
{setBin &&
|
{/* only show the player on desktop */}
|
||||||
<Box position="absolute" bottom={10}>
|
{setBin && !isMobile &&
|
||||||
|
<Box paddingX={2} position="absolute" bottom={10} width="100%">
|
||||||
<BinPlayer bin={bin} componentDevices={componentDevices} setBin={setBin}/>
|
<BinPlayer bin={bin} componentDevices={componentDevices} setBin={setBin}/>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue