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.

212 lines
7.0 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. import Page, {PageFlexRow} from "../primitives/page/Page";
  2. import {useCallback, useContext, useEffect, useMemo, useState} from "react";
  3. import RuntimeContext from "../contexts/RuntimeContext";
  4. import {useNavigate} from "react-router";
  5. import LoadingPage from "./LoadingPage";
  6. import DeviceContext from "../contexts/DeviceContext";
  7. import ProgramContext from "../contexts/ProgramContext";
  8. import {Device} from "../models/Devices";
  9. import {Program, subTitleOfProgram} from "../models/Programs";
  10. import {useKey, usePlusMinus} from "../hooks/keyboard";
  11. import {FluffyValue, TitleLine, Value} from "../primitives/misc/Misc";
  12. import Blob, {BlobText, BlobTextLine} from "../primitives/blob/Blob";
  13. import {Icon} from "../primitives/Shared";
  14. import {faClose, faPlay} from "@fortawesome/free-solid-svg-icons";
  15. import {stateString, WorkoutStatus} from "../models/Workouts";
  16. import {Boi} from "../primitives/boi/Boi";
  17. import {useLastState} from "./runtime/hooks";
  18. import {ControlsBoi} from "./runtime/ControlsBoi";
  19. import MessageBoi from "./runtime/MessageBoi";
  20. import ProgramBoi from "./runtime/ProgramBoi";
  21. import MilestoneBoi from "./runtime/MilestoneBoi";
  22. import {useKpm} from "../hooks/kpm";
  23. function PlayPage(): JSX.Element {
  24. const {active, ready, ended, workout, reset, resume} = useContext(RuntimeContext);
  25. const navigate = useNavigate();
  26. useEffect(() => {
  27. if (!active) {
  28. resume();
  29. }
  30. }, [active, resume]);
  31. useEffect(() => {
  32. if (active && ended && workout) {
  33. navigate(`/workouts/${workout.id}`, {replace: true});
  34. reset();
  35. }
  36. }, [active, ready, ended, workout]);
  37. if (active && ready && workout === null) {
  38. return <CreatePlayPage/>;
  39. }
  40. if (active && workout !== null) {
  41. return <RunPlayPage/>;
  42. }
  43. return <LoadingPage minimal text="Starter økt"/>;
  44. }
  45. const noProgram: Program = {
  46. id: "",
  47. name: "Uten program",
  48. steps: [{index: 0, values: {}, duration: undefined}],
  49. }
  50. function CreatePlayPage(): JSX.Element {
  51. const {devices} = useContext(DeviceContext);
  52. const {programs} = useContext(ProgramContext);
  53. const {create} = useContext(RuntimeContext);
  54. const programWithFake = useMemo(() => programs ? [noProgram, ...programs] : null, [programs]);
  55. const navigate = useNavigate();
  56. const [device, setDevice] = useState<Device | null>(null);
  57. const [program, setProgram] = useState<Program | null>(null);
  58. const [sel, setSel] = usePlusMinus((device ? (program ? 2 : programWithFake?.length) : devices?.length) || 1);
  59. const confirmSelection = useCallback((idx: number) => {
  60. if (program && device) {
  61. if (idx === 0) {
  62. create({deviceId: device.id, programId: program.id || undefined, test: false})
  63. } else {
  64. navigate("/");
  65. }
  66. } else if (device && programWithFake) {
  67. setProgram(programWithFake[idx] || null);
  68. setSel(0);
  69. } else if (devices !== null) {
  70. setDevice(devices[idx] || null);
  71. setSel(0);
  72. }
  73. }, [create, device, program, devices, programWithFake]);
  74. useKey("Enter", () => {
  75. confirmSelection(sel);
  76. }, [confirmSelection, sel]);
  77. useKey("Escape", () => {
  78. navigate("/");
  79. }, []);
  80. useEffect(() => {
  81. if (devices && devices.length === 0) {
  82. navigate("/");
  83. }
  84. }, [devices]);
  85. if (devices === null) {
  86. return <LoadingPage minimal text="Henter enheter"/>
  87. } else if (programWithFake === null) {
  88. return <LoadingPage minimal text="Henter programmer"/>
  89. }
  90. return (
  91. <Page background={"2046"}>
  92. {device === null && (
  93. <Boi vertical="center" horizontal="center" style={{fontSize: undefined}}>
  94. <TitleLine>Velg enhet</TitleLine>
  95. {devices.map((d, i) => (
  96. <Blob key={d.id} onClick={() => confirmSelection(i)} color={sel === i ? "indigo" : "gray"}>
  97. <BlobText>
  98. <BlobTextLine>{d.name}</BlobTextLine>
  99. <BlobTextLine secondary>{d.connectionString}</BlobTextLine>
  100. </BlobText>
  101. </Blob>
  102. ))}
  103. </Boi>
  104. )}
  105. {device !== null && program === null && (
  106. <Boi vertical="center" horizontal="center" style={{fontSize: undefined}}>
  107. <TitleLine>Velg program</TitleLine>
  108. {programWithFake.map((p, i) => (
  109. <Blob key={p.id} onClick={() => confirmSelection(i)} color={sel === i ? "indigo" : "gray"}>
  110. <BlobText>
  111. <BlobTextLine>{p.name}</BlobTextLine>
  112. <BlobTextLine secondary>{subTitleOfProgram(p)}</BlobTextLine>
  113. </BlobText>
  114. </Blob>
  115. ))}
  116. </Boi>
  117. )}
  118. {device && program && (
  119. <Boi vertical="center" horizontal="center" style={{fontSize: undefined}}>
  120. <TitleLine>Oppsumering</TitleLine>
  121. {device && (
  122. <Blob>
  123. <BlobText>
  124. <BlobTextLine>{device.name}</BlobTextLine>
  125. <BlobTextLine secondary>{device.connectionString}</BlobTextLine>
  126. </BlobText>
  127. </Blob>
  128. )}
  129. {program && (
  130. <Blob>
  131. <BlobText>
  132. <BlobTextLine>{program.name}</BlobTextLine>
  133. <BlobTextLine secondary>{subTitleOfProgram(program)}</BlobTextLine>
  134. </BlobText>
  135. </Blob>
  136. )}
  137. <PageFlexRow>
  138. {device && program && (
  139. <>
  140. <Blob onClick={() => confirmSelection(0)} color={sel === 0 ? "indigo" : "gray"}>
  141. <BlobText>
  142. <Icon value={faPlay}/> Start
  143. </BlobText>
  144. </Blob>
  145. <Blob onClick={() => confirmSelection(1)} color={sel === 1 ? "indigo" : "gray"}>
  146. <BlobText>
  147. <Icon value={faClose}/> Avbryt
  148. </BlobText>
  149. </Blob>
  150. </>
  151. )}
  152. </PageFlexRow>
  153. </Boi>
  154. )}
  155. </Page>
  156. );
  157. }
  158. function RunPlayPage(): JSX.Element {
  159. const {workout} = useContext(RuntimeContext);
  160. const lastState = useLastState();
  161. const kpm = useKpm();
  162. if (!workout || workout.status === WorkoutStatus.Created) {
  163. return <LoadingPage minimal/>;
  164. }
  165. return (
  166. <Page title="YKonsole" background={"2046"}>
  167. <ControlsBoi/>
  168. {lastState && (
  169. <Boi vertical="center" horizontal="left" style={{padding: "0.5vmax", paddingBottom: "0"}}>
  170. <span style={{fontSize: "125%"}}>
  171. <FluffyValue raw={lastState} valueKey="time"/>
  172. </span>
  173. <FluffyValue raw={lastState} valueKey="calories"/>
  174. <FluffyValue raw={lastState} valueKey="distance"/>
  175. <FluffyValue raw={lastState} valueKey="level"/>
  176. <FluffyValue raw={lastState} valueKey="rpmSpeed"/>
  177. {kpm > 0 && <FluffyValue raw={kpm} valueKey="kpm"/>}
  178. <FluffyValue raw={lastState} valueKey="pulse"/>
  179. </Boi>
  180. )}
  181. {workout?.status === WorkoutStatus.Connected && <MessageBoi text="Trykk Enter for å begynne"/>}
  182. {workout?.status === WorkoutStatus.Stopped && <MessageBoi text="Pause"/>}
  183. {workout.program && workout.program.steps.length > 0 && <ProgramBoi/>}
  184. <MilestoneBoi/>
  185. </Page>
  186. );
  187. }
  188. export default PlayPage;