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.
222 lines
5.6 KiB
222 lines
5.6 KiB
import {CurrentWorkout, WorkoutState, WorkoutStatus} from "../models/Workouts";
|
|
import {createContext, useCallback, useEffect, useState} from "react";
|
|
import {unimplemented} from "../helpers/misc";
|
|
import {WithChildren} from "../primitives/Shared";
|
|
import {Values} from "../models/Shared";
|
|
import runtimeRepo from "../actions/runtime";
|
|
import workoutRepo, {CreateWorkoutOptions} from "../actions/workouts";
|
|
import {Milestone} from "../models/Milestone";
|
|
|
|
interface RuntimeContextValue {
|
|
workout: CurrentWorkout | null
|
|
states: WorkoutState[]
|
|
error: string | null
|
|
|
|
lastEvent: SocketOutput | null
|
|
|
|
active: boolean
|
|
ready: boolean
|
|
ended: boolean
|
|
|
|
connect(): void
|
|
disconnect(): void
|
|
start(): void
|
|
stop(): void
|
|
setLevel(level: number): void
|
|
skip(): void
|
|
|
|
reset(): void
|
|
resume(): void
|
|
create(options: CreateWorkoutOptions): void
|
|
}
|
|
|
|
const RuntimeContext = createContext<RuntimeContextValue>({
|
|
workout: null,
|
|
states: [],
|
|
error: null,
|
|
lastEvent: null,
|
|
active: false,
|
|
ready: false,
|
|
ended: false,
|
|
connect: unimplemented,
|
|
disconnect: unimplemented,
|
|
start: unimplemented,
|
|
stop: unimplemented,
|
|
setLevel: unimplemented,
|
|
skip: unimplemented,
|
|
reset: unimplemented,
|
|
resume: unimplemented,
|
|
create: unimplemented,
|
|
});
|
|
|
|
interface SocketInput {
|
|
start?: true
|
|
stop?: true
|
|
connect?: true
|
|
disconnect?: true
|
|
skip?: true
|
|
setValue?: Values
|
|
}
|
|
|
|
function socketInput(obj: SocketInput): string {
|
|
return JSON.stringify(obj);
|
|
}
|
|
|
|
interface SocketOutput {
|
|
sentAt: string
|
|
workout: CurrentWorkout | null
|
|
workoutStates: WorkoutState[] | null
|
|
milestone: Milestone | null
|
|
oldMilestones: Milestone[] | null
|
|
event: { name: string } | null
|
|
error: { message: string } | null
|
|
}
|
|
|
|
function socketOutput(str: string): SocketOutput {
|
|
return JSON.parse(str);
|
|
}
|
|
|
|
export function RuntimeContextProvider({children}: WithChildren): JSX.Element {
|
|
const [workout, setWorkout] = useState<CurrentWorkout | null>(null);
|
|
const [states, setStates] = useState<WorkoutState[]>([]);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [lastEvent, setLastEvent] = useState<SocketOutput | null>(null);
|
|
const [active, setActive] = useState<boolean>(false);
|
|
const [ready, setReady] = useState<boolean>(false);
|
|
const [ended, setEnded] = useState<boolean>(false);
|
|
const [socket, setSocket] = useState<WebSocket | null>(null);
|
|
|
|
const sendCommand = useCallback((input: SocketInput) => {
|
|
if (socket && socket.readyState === socket.OPEN && workout) {
|
|
socket.send(socketInput(input));
|
|
}
|
|
}, [socket, workout]);
|
|
|
|
const connect = useCallback(() => {
|
|
sendCommand({connect: true});
|
|
}, [sendCommand]);
|
|
|
|
const disconnect = useCallback(() => {
|
|
sendCommand({disconnect: true});
|
|
}, [sendCommand]);
|
|
|
|
const start = useCallback(() => {
|
|
sendCommand({start: true});
|
|
}, [sendCommand]);
|
|
|
|
const stop = useCallback(() => {
|
|
sendCommand({stop: true});
|
|
}, [sendCommand]);
|
|
|
|
const setLevel = useCallback((level: number) => {
|
|
sendCommand({
|
|
setValue: {level},
|
|
});
|
|
}, [sendCommand]);
|
|
|
|
const skip = useCallback(() => {
|
|
sendCommand({skip: true});
|
|
}, [sendCommand]);
|
|
|
|
const reset = useCallback(() => {
|
|
setActive(false);
|
|
setEnded(false);
|
|
setReady(false);
|
|
}, []);
|
|
|
|
const resume = useCallback(() => {
|
|
setWorkout(null);
|
|
setStates([]);
|
|
setError(null);
|
|
setReady(false);
|
|
setActive(true);
|
|
setEnded(false);
|
|
setLastEvent(null);
|
|
|
|
const newSocket = runtimeRepo().openWebsocket();
|
|
newSocket.onmessage = event => {
|
|
const data = socketOutput(event.data);
|
|
setLastEvent(data);
|
|
setReady(true);
|
|
|
|
if (data.workout) {
|
|
setWorkout(data.workout);
|
|
}
|
|
|
|
if (data.workoutStates && data.workoutStates.length > 0) {
|
|
setStates(prev => {
|
|
if (prev.length === 0) {
|
|
return data.workoutStates!;
|
|
} else {
|
|
const lastOld = prev[prev.length - 1];
|
|
const firstNew = data.workoutStates![0];
|
|
if (lastOld.time <= firstNew.time) {
|
|
return [
|
|
...prev.filter(p => p.time < firstNew.time),
|
|
...data.workoutStates!,
|
|
];
|
|
} else {
|
|
return [...prev, ...data.workoutStates!];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (data.event) {
|
|
const eventNameMap: Record<string, WorkoutStatus> = {
|
|
"Connected": WorkoutStatus.Connected,
|
|
"Started": WorkoutStatus.Started,
|
|
"Stopped": WorkoutStatus.Stopped,
|
|
"Disconnected": WorkoutStatus.Disconnected,
|
|
};
|
|
|
|
const newStatus = eventNameMap[data.event.name];
|
|
if (newStatus) {
|
|
setWorkout(prev => prev ? ({...prev, status: newStatus}) : prev);
|
|
}
|
|
}
|
|
|
|
if (data.error) {
|
|
setError(data.error.message);
|
|
setReady(true);
|
|
}
|
|
};
|
|
|
|
newSocket.onclose = () => {
|
|
setEnded(true);
|
|
};
|
|
|
|
setSocket(newSocket);
|
|
}, []);
|
|
|
|
const create = useCallback((options: CreateWorkoutOptions) => {
|
|
workoutRepo().createWorkout(options).then(success => {
|
|
if (success) {
|
|
resume();
|
|
} else {
|
|
setError("Kunne ikke opprette økten!");
|
|
setEnded(true);
|
|
}
|
|
});
|
|
}, [resume]);
|
|
|
|
useEffect(() => {
|
|
if (ready && workout?.status === WorkoutStatus.Created) {
|
|
connect();
|
|
}
|
|
}, [ready, workout]);
|
|
|
|
return (
|
|
<RuntimeContext.Provider value={{
|
|
workout, states, error,
|
|
lastEvent,
|
|
active, ready, ended,
|
|
connect, disconnect, start, stop, setLevel, skip,
|
|
reset, resume, create,
|
|
}}>
|
|
{children}
|
|
</RuntimeContext.Provider>
|
|
);
|
|
}
|
|
|
|
export default RuntimeContext;
|