MQTT Button works

This commit is contained in:
Jan Zípek 2024-04-01 17:57:41 +02:00
parent 5e9e0cccc3
commit 6579a3ba52
Signed by: kamen
GPG Key ID: A17882625B33AC31
9 changed files with 171 additions and 5 deletions

21
client/src/api/mqtt.ts Normal file
View File

@ -0,0 +1,21 @@
import { request } from './request'
export const publishMqttMessage = (body: {
server: string
clientId?: string
username?: string
password?: string
qos?: number
retain?: boolean
message: string
topic: string
}) =>
request(
`/api/mqtt/publish`,
{
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
},
'void'
)

View File

@ -84,6 +84,10 @@
overflow: hidden;
text-overflow: ellipsis;
&.hidden-title {
border-bottom: none;
}
.drag-handle {
flex: 1;
cursor: move;
@ -133,6 +137,21 @@
justify-content: center;
height: 100%;
}
.mqtt-button {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
&.apply-stretch {
align-items: stretch;
> button {
flex: 1;
}
}
}
}
}

View File

@ -4,6 +4,7 @@ import { useConfirmModal } from '@/contexts/ConfirmModalsContext'
import {
DashboardDialData,
DashboardGraphData,
DashboardMQTTButtonData,
} from '@/utils/dashboard/parseDashboard'
import { useForm } from '@/utils/hooks/useForm'
import { useState } from 'preact/hooks'
@ -11,6 +12,7 @@ import { useQuery } from 'react-query'
import { BoxDefinition } from '../../types'
import { DialSettings } from './components/DialSettings'
import { GraphSettings } from './components/GraphSettings'
import { MQTTButtonSettings } from './components/MQTTButtonSettings'
type Props = {
value: BoxDefinition
@ -63,6 +65,7 @@ export const BoxSettings = ({ value, onSave, onClose, onRemove }: Props) => {
<select {...register('type')} required>
<option value="graph">Graph</option>
<option value="dial">Dial</option>
<option value="mqttButton">MQTT Button</option>
</select>
</div>
@ -81,6 +84,13 @@ export const BoxSettings = ({ value, onSave, onClose, onRemove }: Props) => {
sensors={sensors.data ?? []}
/>
)}
{type === 'mqttButton' && (
<MQTTButtonSettings
value={data as DashboardMQTTButtonData}
onChange={setData}
/>
)}
</>
}

View File

@ -0,0 +1,67 @@
import { FormCheckboxField } from '@/components/FormCheckboxField'
import { FormField } from '@/components/FormField'
import { DashboardMQTTButtonData } from '@/utils/dashboard/parseDashboard'
import { useForm } from '@/utils/hooks/useForm'
import { omit } from '@/utils/omit'
type Props = {
value?: DashboardMQTTButtonData
onChange: (data: DashboardMQTTButtonData) => void
}
export const MQTTButtonSettings = ({ value, onChange }: Props) => {
const { register } = useForm({
defaultValue: () => ({
server: '',
topic: '',
message: '',
retain: false,
...(value && omit(value, ['type'])),
}),
onChange: (v) => onChange({ ...v, type: 'mqttButton' }),
})
return (
<>
<FormField
name="server"
label="MQTT Broker Address"
hint="Example: tcp://10.10.1.1:1883"
>
<input required {...register('server')} />
</FormField>
<FormField name="clientId" label="Client ID">
<input {...register('clientId')} />
</FormField>
<FormField name="username" label="Username">
<input {...register('username')} />
</FormField>
<FormField name="password" label="Password">
<input type="password" {...register('password')} />
</FormField>
<FormField name="qoc" label="QoS">
<select {...register('qos', { type: 'integer' })}>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</FormField>
<FormCheckboxField name="retain" label="Retain">
<input type="checkbox" {...register('retain')} />
</FormCheckboxField>
<FormField name="topic" label="Topic">
<input required {...register('topic')} />
</FormField>
<FormField name="message" label="Payload">
<textarea required {...register('message')} rows={4} />
</FormField>
<FormCheckboxField
name="stretchButton"
label="Stretch button to full size"
hint="Makes button cover the whole area of the box"
>
<input type="checkbox" {...register('stretchButton')} />
</FormCheckboxField>
</>
)
}

View File

@ -0,0 +1,29 @@
import { DashboardMQTTButtonData } from '@/utils/dashboard/parseDashboard'
import { EditableBox, EditableBoxProps } from './EditableBox'
import { useMutation } from 'react-query'
import { publishMqttMessage } from '@/api/mqtt'
import { cn } from '@/utils/cn'
type Props = EditableBoxProps & {
data: DashboardMQTTButtonData
}
export const BoxMQTTButtonContent = ({ data, ...boxProps }: Props) => {
const pushMutation = useMutation(publishMqttMessage)
const onClick = () => {
pushMutation.mutate({
...data,
})
}
return (
<EditableBox {...boxProps} onRefresh={() => null} hiddenTitle>
<div className={cn('mqtt-button', data.stretchButton && 'apply-stretch')}>
<button onClick={onClick} disabled={pushMutation.isLoading}>
{boxProps.box.title ?? 'BUTTON'}
</button>
</div>
</EditableBox>
)
}

View File

@ -9,6 +9,7 @@ import { useWindowEvent } from '@/utils/hooks/useWindowEvent'
import { ComponentChild } from 'preact'
import { useState } from 'preact/hooks'
import { BoxSettings } from '../../BoxSettings/BoxSettings'
import { cn } from '@/utils/cn'
export type EditableBoxProps = {
box: BoxDefinition
@ -19,11 +20,13 @@ export type EditableBoxProps = {
}
type EditableBoxPropsWithExtra = EditableBoxProps & {
hiddenTitle?: boolean
children: ComponentChild
onRefresh: () => void
}
export const EditableBox = ({
hiddenTitle,
box,
children,
onPosition,
@ -135,9 +138,9 @@ export const EditableBox = ({
}
>
<div className="box" style={{ height: '100%' }}>
<div className="header">
<div className={cn('header', hiddenTitle && 'hidden-title')}>
<div className="drag-handle" onMouseDown={handleMouseDown}>
<div className="name">{box.title || ''}</div>
{!hiddenTitle && <div className="name">{box.title || ''}</div>}
</div>
<div className="actions">
<div className="action" onClick={onRefresh}>
@ -148,6 +151,7 @@ export const EditableBox = ({
</div>
</div>
</div>
<div className="body">{children}</div>
<div

View File

@ -1,5 +1,6 @@
import { BoxDialContent } from './BoxDialContent'
import { BoxGraphContent } from './BoxGraphContent'
import { BoxMQTTButtonContent } from './BoxMQTTButtonContent'
import { EditableBox, EditableBoxProps } from './EditableBox'
export const GeneralBox = (props: EditableBoxProps) => {
@ -8,10 +9,12 @@ export const GeneralBox = (props: EditableBoxProps) => {
return <BoxDialContent {...props} data={props.box.data} />
case 'graph':
return <BoxGraphContent {...props} data={props.box.data} />
case 'mqttButton':
return <BoxMQTTButtonContent {...props} data={props.box.data} />
default:
return (
<EditableBox {...props} onRefresh={() => null}>
<></>
<button onClick={() => props.onEdit(props.box)}>Pick a type</button>
</EditableBox>
)
}

View File

@ -12,7 +12,7 @@ export type DashboardContentBox = {
w: number
h: number
title?: string
data?: DashboardGraphData | DashboardDialData
data?: DashboardGraphData | DashboardDialData | DashboardMQTTButtonData
}
export type DashboardGraphData = {
@ -44,6 +44,19 @@ export type DashboardDialData = {
fontSize?: number
}
export type DashboardMQTTButtonData = {
type: 'mqttButton'
server: string
clientId?: string
username?: string
password?: string
qos?: number
retain?: boolean
message: string
topic: string
stretchButton?: boolean
}
export const parseDashboard = (input: string) => {
const data = JSON.parse(input) as DashboardContent
const migrations = getDashboardMigrations(data.version)

View File

@ -9,7 +9,7 @@ import (
type putMQTTPublishBody struct {
Server string `json:"server" binding:"required"`
ClientId string `json:"clientId" binding:"required"`
ClientId string `json:"clientId"`
Username string `json:"username"`
Password string `json:"password"`
Retain *bool `json:"retain"`