Implement basic ui state machine

main
lambda 3 months ago
parent b3ad6815ad
commit 2022ebe38c

@ -2,23 +2,20 @@ mod device;
mod ble;
mod device_repo;
use crate::{ble::scan_devices, device::Device, device_repo::{repo_from_devices, try_from_file, Repo}};
use crate::{ble::scan_devices, device_repo::{repo_from_devices, try_from_file, Repo}};
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
async fn read_repo() -> Vec<Device> {
vec![]
}
#[tauri::command]
async fn scan() -> Repo {
async fn read_repo() -> Repo {
match try_from_file() {
Ok(repo) => repo,
Err(_) => {
let devices = (scan_devices(20).await).unwrap_or_default();
repo_from_devices(devices)
Err(_) => Repo::new()
}
}
#[tauri::command]
async fn scan() -> Repo {
repo_from_devices((scan_devices(5).await).unwrap_or_default())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]

@ -11,9 +11,7 @@
<body>
<main class="container">
<h1>Welcome to RideInn</h1>
<div class="row">
<span id="msg">Подключаем устройства...</span>
<button id="find">поиск</button>
<div id="app">
</div>
</main>
</body>

@ -1,8 +1,7 @@
import { EVENTS } from "./state.js";
import { fsm } from "./ui.js";
const { invoke } = window.__TAURI__.core;
let greetInputEl;
let greetMsgEl;
async function readRepo() {
return await invoke("read_repo");
}
@ -11,18 +10,38 @@ async function scan() {
return await invoke("scan");
}
const isArray = (maybeArray) =>
maybeArray?.constructor && maybeArray?.constructor === Array;
const isEmptyArray = (maybeArray) =>
isArray(maybeArray) && maybeArray.length === 0;
const getClosestDevices = () => {
console.warn("getClosestDevices NOT IMPLEMENTED YET");
return [];
};
const isRepoEmpty = (repo) =>
(!repo.cad_sensors || isEmptyArray(repo.cad_sensors)) &&
(!repo.hr_sensors || isEmptyArray(repo.hr_sensors)) &&
(!repo.trainers || isEmptyArray(repo.trainers));
window.addEventListener("DOMContentLoaded", async () => {
const messageContainer = document.getElementById("msg");
const findButton = document.getElementById("find");
const [savedDevices, scannedDevices] = await Promise.all([
readRepo(),
scan(),
]);
const connectedDeviced = await readRepo();
console.log(savedDevices);
if (connectedDeviced.length === 0) {
messageContainer.innerText = "Ищем устройства...";
if (isRepoEmpty(savedDevices)) {
const closest = getClosestDevices(scannedDevices);
if (closest.length === 0) fsm.dispatch(EVENTS.TO_ERROR);
} else {
if (isRepoEmpty(scannedDevices)) {
fsm.dispatch(EVENTS.TO_MANUAL_CONNECT);
} else {
fsm.dispatch(EVENTS.TO_READY);
}
}
findButton.addEventListener("click", async () => {
const lol = await scan();
console.log("devices", lol);
});
});

@ -0,0 +1,58 @@
const STATES = Object.freeze({
INITIAL: "initial",
SEARCH: "search",
MANUAL_CONNECT: "manualConnect",
READY_TO_ROLL: "ready",
ERROR: "error",
});
const EVENTS = Object.freeze({
TO_SEARCH: "toSearch",
TO_MANUAL_CONNECT: "toManualConnect",
TO_READY: "toReady",
TO_ERROR: "toError",
});
const transitions = new Map([
[[STATES.INITIAL, EVENTS.TO_SEARCH], STATES.SEARCH],
[[STATES.INITIAL, EVENTS.TO_ERROR], STATES.ERROR],
[[STATES.SEARCH, EVENTS.TO_MANUAL_CONNECT], STATES.MANUAL_CONNECT],
[[STATES.SEARCH, EVENTS.TO_READY], STATES.READY_TO_ROLL],
[[STATES.MANUAL_CONNECT, EVENTS.TO_READY], STATES.READY_TO_ROLL],
[[STATES.MANUAL_CONNECT, EVENTS.TO_ERROR], STATES.ERROR],
]);
function createFSM(initialState = STATES.INITIAL) {
const fsm = new EventTarget();
let state = initialState;
fsm.getState = () => state;
fsm.dispatch = (eventName) => {
const key = JSON.stringify([state, eventName]);
const next = [...transitions.entries()].find(
([k]) => JSON.stringify(k) === key,
)?.[1];
if (next) {
const prev = state;
state = next;
fsm.dispatchEvent(
new CustomEvent("statechange", {
detail: { prev, next, trigger: eventName },
}),
);
return true;
}
console.warn(`No transition from "${state}" on "${eventName}"`);
return false;
};
return fsm;
}
export { createFSM, STATES, EVENTS };

@ -1,6 +1,3 @@
.logo.vanilla:hover {
filter: drop-shadow(0 0 2em #ffe21c);
}
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
@ -17,80 +14,6 @@
-webkit-text-size-adjust: 100%;
}
.container {
margin: 0;
padding-top: 10vh;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
}
.logo.tauri:hover {
filter: drop-shadow(0 0 2em #24c8db);
}
.row {
display: flex;
justify-content: center;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
h1 {
text-align: center;
}
input,
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
}
button {
cursor: pointer;
}
button:hover {
border-color: #396cd8;
}
button:active {
border-color: #396cd8;
background-color: #e8e8e8;
}
input,
button {
outline: none;
}
#greet-input {
margin-right: 5px;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;

@ -0,0 +1,32 @@
import { createFSM, STATES } from "./state.js";
const fsm = createFSM();
const container = document.getElementById("app");
const pages = {
[STATES.INITIAL]: `
<h1>INITIAL</h1>
`,
[STATES.SEARCH]: `
<h1>SEARCH</h1>
`,
[STATES.MANUAL_CONNECT]: `
<h1>MANUAL CONNECT</h1>
`,
[STATES.ERROR]: `
<h1>ERROR</h1>
`,
};
function render(state) {
container.innerHTML = pages[state] ?? "<p>Unknown state</p>";
}
fsm.addEventListener("statechange", (e) => {
const { next } = e.detail;
render(next);
});
render(fsm.getState());
export { fsm };
Loading…
Cancel
Save