You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
358 lines
10 KiB
358 lines
10 KiB
import Page, {PageBody, PageFlexColumn, PageFlexRow} from "../primitives/page/Page";
|
|
import Header, {HeaderButton, HeaderTitle} from "../primitives/header/Header";
|
|
import {TitleLine} from "../primitives/misc/Misc";
|
|
import {Size} from "../models/Shared";
|
|
import Blob, {BlobInput, BlobText} from "../primitives/blob/Blob";
|
|
import {Icon} from "../primitives/Shared";
|
|
import {
|
|
faChain,
|
|
faCheck, faChevronDown,
|
|
faChevronLeft,
|
|
faChevronUp, faMessage,
|
|
faPencilAlt,
|
|
faTag,
|
|
faTrashCan
|
|
} from "@fortawesome/free-solid-svg-icons";
|
|
import {useCallback, useContext, useEffect, useMemo, useState} from "react";
|
|
import LoadingPage from "./LoadingPage";
|
|
import {useNavigate, useParams} from "react-router";
|
|
import DeviceContext from "../contexts/DeviceContext";
|
|
import {useSearchParams} from "react-router-dom";
|
|
import {Device} from "../models/Devices";
|
|
import deviceRepo from "../actions/devices";
|
|
import RuntimeContext from "../contexts/RuntimeContext";
|
|
|
|
interface DevicePageProps {
|
|
edit?: boolean
|
|
}
|
|
|
|
export default function DevicePage({edit}: DevicePageProps): JSX.Element {
|
|
const {devices} = useContext(DeviceContext);
|
|
const navigate = useNavigate();
|
|
const {id} = useParams();
|
|
const [search] = useSearchParams();
|
|
|
|
const device = useMemo(() => devices?.find(d => d.id === id) || null, [devices]);
|
|
|
|
if (edit && id === undefined) {
|
|
return <NewDevicePage/>;
|
|
} else if (edit && device) {
|
|
return <EditDevicePage device={device}/>;
|
|
} else if (device !== null) {
|
|
return <ViewDevicePage device={device}/>;
|
|
} else {
|
|
if (devices !== null) {
|
|
navigate("/");
|
|
}
|
|
|
|
return <LoadingPage text="Henter enhet"/>;
|
|
}
|
|
}
|
|
|
|
function NewDevicePage(): JSX.Element {
|
|
const navigate = useNavigate();
|
|
const {refreshDevices} = useContext(DeviceContext);
|
|
|
|
const [name, setName] = useState<string>("");
|
|
const [connectionString, setConnectionString] = useState<string>("");
|
|
const [wait, setWait] = useState<boolean>();
|
|
|
|
const onSave = useCallback(async () => {
|
|
setWait(true);
|
|
if (await deviceRepo().save({name, connectionString})) {
|
|
refreshDevices();
|
|
navigate("/");
|
|
} else {
|
|
setWait(false);
|
|
}
|
|
}, [name, connectionString]);
|
|
|
|
if (wait) {
|
|
return <LoadingPage text="Legger til enhet"/>;
|
|
}
|
|
|
|
return (
|
|
<Page>
|
|
<Header>
|
|
<HeaderButton onClick={() => navigate("/")}>
|
|
<Icon value={faChevronLeft}/>
|
|
</HeaderButton>
|
|
<HeaderTitle>Ny enhet</HeaderTitle>
|
|
</Header>
|
|
<PageBody>
|
|
<PageFlexRow collapseOn={Size.Mobile}>
|
|
<PageFlexColumn flex={1}>
|
|
<TitleLine>Enhet</TitleLine>
|
|
<Blob fillOn={Size.Any}>
|
|
<BlobText>Navn</BlobText>
|
|
<BlobInput flex={1} type="text" value={name} onChange={setName}/>
|
|
</Blob>
|
|
<Blob fillOn={Size.Any}>
|
|
<BlobText>Adresse</BlobText>
|
|
<BlobInput flex={1} type="text" value={connectionString} onChange={setConnectionString}/>
|
|
</Blob>
|
|
<Blob color="green" onClick={onSave}>
|
|
<BlobText>
|
|
<Icon value={faCheck}/> Lagre
|
|
</BlobText>
|
|
</Blob>
|
|
</PageFlexColumn>
|
|
<PageFlexColumn flex={1}/>
|
|
</PageFlexRow>
|
|
</PageBody>
|
|
</Page>
|
|
);
|
|
}
|
|
|
|
|
|
interface EditDevicePageProps {
|
|
device: Device
|
|
}
|
|
|
|
function EditDevicePage({device}: EditDevicePageProps): JSX.Element {
|
|
const navigate = useNavigate();
|
|
const {refreshDevices} = useContext(DeviceContext);
|
|
|
|
const [name, setName] = useState<string>(device.name);
|
|
const [connectionString, setConnectionString] = useState<string>(device.connectionString);
|
|
const [wait, setWait] = useState<boolean>();
|
|
|
|
const onSave = useCallback(async () => {
|
|
setWait(true);
|
|
if (await deviceRepo().save({id: device.id, name, connectionString})) {
|
|
refreshDevices();
|
|
navigate(`/devices/${device.id}`);
|
|
} else {
|
|
setWait(false);
|
|
}
|
|
}, [device, name, connectionString]);
|
|
|
|
if (wait) {
|
|
return <LoadingPage text="Oppdaterer enhet"/>;
|
|
}
|
|
|
|
return (
|
|
<Page>
|
|
<Header>
|
|
<HeaderButton onClick={() => navigate(`/devices/${device.id}`)}>
|
|
<Icon value={faChevronLeft}/>
|
|
</HeaderButton>
|
|
<HeaderTitle>Endre «{device.name}»</HeaderTitle>
|
|
</Header>
|
|
<PageBody>
|
|
<PageFlexRow collapseOn={Size.Mobile}>
|
|
<PageFlexColumn flex={1}>
|
|
<TitleLine>Enhet</TitleLine>
|
|
<Blob fillOn={Size.Any}>
|
|
<BlobText>Navn</BlobText>
|
|
<BlobInput flex={1} type="text" value={name} onChange={setName}/>
|
|
</Blob>
|
|
<Blob fillOn={Size.Any}>
|
|
<BlobText>Adresse</BlobText>
|
|
<BlobInput flex={1} type="text" value={connectionString} onChange={setConnectionString}/>
|
|
</Blob>
|
|
<Blob color="green" onClick={onSave}>
|
|
<BlobText>
|
|
<Icon value={faCheck}/> Lagre
|
|
</BlobText>
|
|
</Blob>
|
|
</PageFlexColumn>
|
|
<PageFlexColumn flex={1}/>
|
|
</PageFlexRow>
|
|
</PageBody>
|
|
</Page>
|
|
);
|
|
}
|
|
|
|
interface ViewDevicePageProps {
|
|
device: Device
|
|
}
|
|
|
|
function ViewDevicePage({device}: ViewDevicePageProps): JSX.Element {
|
|
const navigate = useNavigate();
|
|
const {refreshDevices} = useContext(DeviceContext);
|
|
const {active, ended, error, create} = useContext(RuntimeContext);
|
|
|
|
const onDelete = useCallback(() => {
|
|
if (!window.confirm("Vil du fjerne denne enheten?")) return;
|
|
|
|
deviceRepo().delete(device.id).then(() => {
|
|
refreshDevices();
|
|
navigate("/");
|
|
})
|
|
}, [device, navigate, refreshDevices]);
|
|
|
|
return (
|
|
<Page>
|
|
<Header>
|
|
<HeaderButton onClick={() => navigate("/")}>
|
|
<Icon value={faChevronLeft}/>
|
|
</HeaderButton>
|
|
<HeaderTitle>{device.name}</HeaderTitle>
|
|
</Header>
|
|
<PageBody>
|
|
<PageFlexRow collapseOn={Size.Mobile}>
|
|
<PageFlexColumn flex={1}>
|
|
<TitleLine>Enhet</TitleLine>
|
|
<Blob>
|
|
<BlobText>
|
|
<Icon value={faTag}/> {device.name}
|
|
</BlobText>
|
|
</Blob>
|
|
<Blob>
|
|
<BlobText>
|
|
<Icon value={faChain}/> {device.connectionString}
|
|
</BlobText>
|
|
</Blob>
|
|
<Blob color="indigo" onClick={() => navigate(`/devices/${device.id}/edit`)}>
|
|
<BlobText>
|
|
<Icon value={faPencilAlt}/>
|
|
</BlobText>
|
|
</Blob>
|
|
<Blob color="red" onClick={onDelete}>
|
|
<BlobText>
|
|
<Icon value={faTrashCan}/>
|
|
</BlobText>
|
|
</Blob>
|
|
</PageFlexColumn>
|
|
<PageFlexColumn flex={1}>
|
|
<TitleLine>Test</TitleLine>
|
|
{active && !ended ? <TestSection/> : (
|
|
<Blob
|
|
color={error ? "red" : (ended ? "green" : "yellow")}
|
|
onClick={() => create({deviceId: device.id, test: true})}
|
|
>
|
|
{ended && error && <BlobText>Prøv igjen</BlobText>}
|
|
{ended && !error && <BlobText>Vellykket</BlobText>}
|
|
{!ended && <BlobText>Kjør</BlobText>}
|
|
</Blob>
|
|
)}
|
|
</PageFlexColumn>
|
|
</PageFlexRow>
|
|
</PageBody>
|
|
</Page>
|
|
);
|
|
}
|
|
|
|
interface Event {
|
|
createdAt: string,
|
|
type: "up" | "down" | "log",
|
|
message: string
|
|
}
|
|
|
|
function currentTime(): string {
|
|
return (new Date().toTimeString().substring(0, 8));
|
|
}
|
|
|
|
function TestSection(): JSX.Element {
|
|
const {lastEvent, connect, disconnect, start, stop} = useContext(RuntimeContext);
|
|
const [events, setEvents] = useState<Event[]>([]);
|
|
|
|
useEffect(() => {
|
|
const timeouts: number[] = [];
|
|
|
|
if (lastEvent) {
|
|
const createdAt = (new Date().toTimeString().substring(0, 8));
|
|
|
|
if (lastEvent.workout) {
|
|
setEvents(prev => [
|
|
...prev,
|
|
{createdAt, type: "down", message: `Opprettet økt ${lastEvent.workout!.id}`},
|
|
{createdAt, type: "log", message: "Vil koble til umiddelbart"},
|
|
]);
|
|
}
|
|
if (lastEvent.workoutStates && lastEvent.workoutStates.length > 0) {
|
|
const last = lastEvent.workoutStates[lastEvent.workoutStates.length - 1];
|
|
|
|
setEvents(prev => [...prev, {
|
|
createdAt, type: "down", message: `Ny tilstand: ${last.time} s, ${last.calories} kcal`,
|
|
}])
|
|
}
|
|
if (lastEvent.event) {
|
|
setEvents(prev => [
|
|
...prev,
|
|
{createdAt, type: "down", message: `Ny hendelse: ${lastEvent.event!.name}`},
|
|
]);
|
|
|
|
if (lastEvent.event.name === "Connected") {
|
|
setEvents(prev => [
|
|
...prev,
|
|
{createdAt, type: "log", message: "Vil starte om 5 sekunder..."},
|
|
]);
|
|
|
|
timeouts.push(setTimeout(() => {
|
|
setEvents(prev => [
|
|
...prev,
|
|
{createdAt: currentTime(), type: "up", message: "Starter"},
|
|
]);
|
|
start();
|
|
}, 5000));
|
|
}
|
|
|
|
if (lastEvent.event.name === "Started") {
|
|
setEvents(prev => [
|
|
...prev,
|
|
{createdAt, type: "log", message: "Vil stoppe om 30 sekunder..."},
|
|
]);
|
|
|
|
timeouts.push(setTimeout(() => {
|
|
setEvents(prev => [
|
|
...prev,
|
|
{createdAt: currentTime(), type: "up", message: "Stopper"},
|
|
]);
|
|
stop();
|
|
}, 30000));
|
|
}
|
|
|
|
if (lastEvent.event.name === "Stopped") {
|
|
setEvents(prev => [
|
|
...prev,
|
|
{createdAt, type: "log", message: "Vil koble fra om 5 sekunder..."},
|
|
]);
|
|
|
|
timeouts.push(setTimeout(() => {
|
|
setEvents(prev => [
|
|
...prev,
|
|
{createdAt: currentTime(), type: "up", message: "Kobler fra"},
|
|
]);
|
|
disconnect();
|
|
}, 5000));
|
|
}
|
|
|
|
|
|
if (lastEvent.event.name === "Disconnected") {
|
|
setEvents(prev => [
|
|
...prev,
|
|
{createdAt, type: "log", message: "Frakoblet"},
|
|
]);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
setEvents([]);
|
|
}
|
|
}, [lastEvent]);
|
|
|
|
return (
|
|
<>
|
|
{events.map((e, i) => (
|
|
<PageFlexRow key={i}>
|
|
<Blob>
|
|
<BlobText>{e.createdAt}</BlobText>
|
|
</Blob>
|
|
<Blob>
|
|
<BlobText>
|
|
{e.type === "up" && <Icon value={faChevronUp}/>}
|
|
{e.type === "down" && <Icon value={faChevronDown}/>}
|
|
{e.type === "log" && <Icon value={faMessage}/>}
|
|
</BlobText>
|
|
</Blob>
|
|
<Blob flex={1}>
|
|
<BlobText>{e.message}</BlobText>
|
|
</Blob>
|
|
</PageFlexRow>
|
|
))}
|
|
<div style={{height: "100px"}}/>
|
|
</>
|
|
);
|
|
}
|