Fixed some resizing issues, added more dial options

This commit is contained in:
Jan Zípek 2022-08-24 22:06:05 +02:00
parent a1f29e66c2
commit 52469eda4d
Signed by: kamen
GPG Key ID: A17882625B33AC31
9 changed files with 104 additions and 28 deletions

View File

@ -142,14 +142,15 @@ form.horizontal .input label {
margin-right: 0.25rem; margin-right: 0.25rem;
} }
.filters .inner { .dashboard-head .inner {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end;
padding: 0.5rem 0.5rem; padding: 0.5rem 0.5rem;
background-color: #fff; background-color: #fff;
} }
.filters .shadow { .dashboard-head .shadow {
position: absolute; position: absolute;
background: linear-gradient(0deg, rgba(255,255,255,0) 5%, rgba(190,190,190,1) 100%); background: linear-gradient(0deg, rgba(255,255,255,0) 5%, rgba(190,190,190,1) 100%);
width: 100%; width: 100%;
@ -157,12 +158,6 @@ form.horizontal .input label {
z-index: 1; z-index: 1;
} }
.filters .actions {
margin-left: auto;
display: flex;
align-items: center;
}
.checkbox-label { .checkbox-label {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -251,6 +246,12 @@ form.horizontal .input label {
display: flex; display: flex;
align-items: center; align-items: center;
padding-right: 0.4rem; padding-right: 0.4rem;
opacity: 0;
transition: opacity 0.1s;
}
.grid-sensors .grid-box .box:hover .header .actions {
opacity: 1;
} }
.grid-sensors .grid-box .box .header .actions .action { .grid-sensors .grid-box .box .header .actions .action {

View File

@ -1,5 +1,6 @@
import { getLatestSensorValue } from '@/api/sensorValues' import { getLatestSensorValue } from '@/api/sensorValues'
import { DashboardDialData } from '@/utils/parseDashboard' import { DashboardDialData } from '@/utils/parseDashboard'
import { useMemo } from 'preact/hooks'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
import { useDashboardContext } from '../contexts/DashboardContext' import { useDashboardContext } from '../contexts/DashboardContext'
import { BoxDefinition } from '../types' import { BoxDefinition } from '../types'
@ -21,12 +22,29 @@ export const BoxDialContent = ({ box, data }: Props) => {
getLatestSensorValue(valuesQuery) getLatestSensorValue(valuesQuery)
) )
const displayValue = useMemo(() => {
if (!value.data) {
return ''
}
const decimals = parseInt(data.decimals ?? '', 10)
const multiplier = parseFloat(data.multiplier ?? '')
const numericValue = value.data.value * (isNaN(multiplier) ? 1 : multiplier)
if (!isNaN(decimals)) {
return numericValue.toFixed(decimals)
}
return numericValue
}, [value.data, data])
return ( return (
<div className="dial"> <div className="dial">
{value.data && ( {value.data && (
<> <>
{value.data.value} {displayValue}
{data.unit} {` ${data.unit}`}
</> </>
)} )}
</div> </div>

View File

@ -7,7 +7,11 @@ type Props = {
} }
export const DialSettings = ({ value, onChange }: Props) => { export const DialSettings = ({ value, onChange }: Props) => {
const [formState, setFormState] = useState(() => ({ unit: value?.unit })) const [formState, setFormState] = useState(() => ({
unit: value?.unit,
decimals: value?.decimals,
multiplier: value?.multiplier,
}))
const handleChange = (e: Event) => { const handleChange = (e: Event) => {
const target = e.target as HTMLSelectElement | HTMLInputElement const target = e.target as HTMLSelectElement | HTMLInputElement
@ -28,6 +32,25 @@ export const DialSettings = ({ value, onChange }: Props) => {
<label>Unit</label> <label>Unit</label>
<input name="unit" value={formState.unit} onChange={handleChange} /> <input name="unit" value={formState.unit} onChange={handleChange} />
</div> </div>
<div className="input">
<label>Decimals</label>
<input
type="number"
name="decimals"
value={formState.decimals}
onChange={handleChange}
/>
</div>
<div className="input">
<label>Multiplier</label>
<input
type="number"
name="multiplier"
value={formState.multiplier}
onChange={handleChange}
step="any"
/>
</div>
</> </>
) )
} }

View File

@ -7,7 +7,7 @@ type Props = {
export const DashboardHeader = ({ onNewBox, onRefresh }: Props) => { export const DashboardHeader = ({ onNewBox, onRefresh }: Props) => {
return ( return (
<div className="filters"> <div className="dashboard-head">
<div className="inner"> <div className="inner">
<button onClick={onNewBox}>Add box</button> <button onClick={onNewBox}>Add box</button>
<Filters /> <Filters />

View File

@ -1,5 +1,5 @@
import { useWindowEvent } from '@/utils/hooks/useWindowEvent' import { useWindowEvent } from '@/utils/hooks/useWindowEvent'
import { useRef, useState } from 'preact/hooks' import { useState } from 'preact/hooks'
import { GRID_WIDTH } from '../constants' import { GRID_WIDTH } from '../constants'
import { useDashboardContext } from '../contexts/DashboardContext' import { useDashboardContext } from '../contexts/DashboardContext'
import { useDragging } from '../hooks/useDragging' import { useDragging } from '../hooks/useDragging'
@ -10,6 +10,7 @@ import { BoxGraphContent } from './BoxGraphContent'
import { BoxSettings } from './BoxSettings/BoxSettings' import { BoxSettings } from './BoxSettings/BoxSettings'
import { ReactComponent as SettingsIcon } from '@/assets/icons/settings.svg' import { ReactComponent as SettingsIcon } from '@/assets/icons/settings.svg'
import { ReactComponent as RefreshIcon } from '@/assets/icons/refresh.svg' import { ReactComponent as RefreshIcon } from '@/assets/icons/refresh.svg'
import { useElementOffsets } from '@/utils/hooks/useElementOffsets'
type Props = { type Props = {
box: BoxDefinition box: BoxDefinition
@ -26,12 +27,13 @@ export const EditableBox = ({
onResize, onResize,
onEdit, onEdit,
}: Props) => { }: Props) => {
const boxRef = useRef<HTMLDivElement>(null) const [boxRef, setBoxRef] = useState<HTMLDivElement>()
const { verticalMode } = useDashboardContext() const { verticalMode } = useDashboardContext()
const [editing, setEditing] = useState(false) const [editing, setEditing] = useState(false)
const outerWidth = boxRef.current?.parentElement?.offsetWidth ?? 100 const parentOffsets = useElementOffsets(boxRef?.parentElement)
const outerWidth = parentOffsets?.width ?? 100
const cellWidth = outerWidth / GRID_WIDTH const cellWidth = outerWidth / GRID_WIDTH
const { dragging, setDragging, draggingPosition } = useDragging({ const { dragging, setDragging, draggingPosition } = useDragging({
@ -49,10 +51,10 @@ export const EditableBox = ({
const handleMouseDown = (e: MouseEvent) => { const handleMouseDown = (e: MouseEvent) => {
e.preventDefault() e.preventDefault()
if (!dragging.active && boxRef.current) { if (!dragging.active && boxRef) {
const pos = { const pos = {
top: boxRef.current.offsetTop, top: boxRef.offsetTop,
left: boxRef.current.offsetLeft, left: boxRef.offsetLeft,
} }
setDragging({ setDragging({
@ -72,7 +74,7 @@ export const EditableBox = ({
if (resizing.mode === ResizingMode.NONE) { if (resizing.mode === ResizingMode.NONE) {
setResizing({ setResizing({
mode: target, mode: target,
w: (box.w / GRID_WIDTH) * outerWidth, w: box.w * cellWidth,
h: box.h, h: box.h,
offsetX: e.clientX, offsetX: e.clientX,
offsetY: e.clientY, offsetY: e.clientY,
@ -95,7 +97,7 @@ export const EditableBox = ({
return ( return (
<> <>
<div <div
ref={boxRef} ref={(e) => setBoxRef(e ?? undefined)}
className={`grid-box${dragging ? ' dragging' : ''}`} className={`grid-box${dragging ? ' dragging' : ''}`}
style={ style={
verticalMode verticalMode

View File

@ -28,6 +28,7 @@ export const useResize = ({ cellWidth, box, boxes }: Props) => {
const isResizing = state.mode !== ResizingMode.NONE const isResizing = state.mode !== ResizingMode.NONE
// TODO: This is most likely incorrect?
const maxHeights = isResizing const maxHeights = isResizing
? Array(GRID_WIDTH) ? Array(GRID_WIDTH)
.fill(null) .fill(null)
@ -48,12 +49,15 @@ export const useResize = ({ cellWidth, box, boxes }: Props) => {
: [] : []
const actualHeight = isResizing const actualHeight = isResizing
? Math.min( ? Math.max(
...Array(box.w) GRID_H_SNAP * 5,
.fill(null) Math.min(
.map((_, x) => maxHeights[box.x + x]) ...Array(box.w)
.filter((x) => x > 0), .fill(null)
Math.round(state.h / GRID_H_SNAP) * GRID_H_SNAP .map((_, x) => maxHeights[box.x + x])
.filter((x) => x > 0),
Math.round(state.h / GRID_H_SNAP) * GRID_H_SNAP
)
) )
: 0 : 0
@ -70,7 +74,10 @@ export const useResize = ({ cellWidth, box, boxes }: Props) => {
.reduce((acc, item) => (item < acc ? item : acc), GRID_WIDTH) .reduce((acc, item) => (item < acc ? item : acc), GRID_WIDTH)
: 0 : 0
const actualWidth = Math.min(maxWidth, Math.round(state.w / cellWidth)) const actualWidth = Math.max(
1,
Math.min(maxWidth, Math.round(state.w / cellWidth))
)
useWindowEvent('mousemove', (e) => { useWindowEvent('mousemove', (e) => {
if (isResizing) { if (isResizing) {

View File

@ -31,7 +31,7 @@ export function normalizeBoxes(boxes: BoxDefinition[]) {
sorted = false sorted = false
break break
} else { } else {
const newY = above[0].h + above[0].y const newY = Math.max(...above.map((a) => a.h + a.y))
if (box.y !== newY) { if (box.y !== newY) {
box.y = newY box.y = newY

View File

@ -0,0 +1,23 @@
import { useEffect, useState } from 'preact/hooks'
import { useWindowEvent } from './useWindowEvent'
const getOffsets = (e: HTMLElement) => ({
width: e.offsetWidth,
height: e.offsetHeight,
left: e.offsetLeft,
top: e.offsetTop,
})
export const useElementOffsets = (e: HTMLElement | undefined | null) => {
const [offsets, setOffsets] = useState(() => (e ? getOffsets(e) : undefined))
useEffect(() => {
e && setOffsets(getOffsets(e))
}, [e])
useWindowEvent('resize', () => {
e && setOffsets(getOffsets(e))
})
return offsets
}

View File

@ -25,6 +25,8 @@ export type DashboardGraphData = {
export type DashboardDialData = { export type DashboardDialData = {
type: 'dial' type: 'dial'
unit?: string unit?: string
decimals?: string
multiplier?: string
} }
export const parseDashboard = (input: string) => { export const parseDashboard = (input: string) => {