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

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;