Merge branch 'demo4' into lpd
This commit is contained in:
commit
95bc2ff393
@ -10,3 +10,4 @@ and update these areas when we add or remove something.
|
||||
memory system, make sure we can store memory of how things have interacted. something like when a button was last pressed,
|
||||
why it was pressed, in response to what, etc. every single thing should connect and be traceable
|
||||
widgets should have either 1 or any number of there own existance
|
||||
widgets that can be more than one should have some parameters like "noUpdate" or "updateWhenFull" so we can control when more widgets can be placed and when they can be updated
|
||||
|
9
package-lock.json
generated
9
package-lock.json
generated
@ -17,6 +17,7 @@
|
||||
"react-icons": "^5.0.1",
|
||||
"react-redux": "^9.1.0",
|
||||
"react-router-dom": "^6.23.0",
|
||||
"react-timer-hook": "^3.0.7",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"typia": "^5.5.10",
|
||||
"uuid": "^9.0.1",
|
||||
@ -8847,6 +8848,14 @@
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-timer-hook": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/react-timer-hook/-/react-timer-hook-3.0.7.tgz",
|
||||
"integrity": "sha512-ATpNcU+PQRxxfNBPVqce2+REtjGAlwmfoNQfcEBMZFxPj0r3GYdKhyPHdStvqrejejEi0QvqaJZjy2lBlFvAsA==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"react-icons": "^5.0.1",
|
||||
"react-redux": "^9.1.0",
|
||||
"react-router-dom": "^6.23.0",
|
||||
"react-timer-hook": "^3.0.7",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"typia": "^5.5.10",
|
||||
"uuid": "^9.0.1",
|
||||
|
11
src/App.tsx
11
src/App.tsx
@ -3,18 +3,17 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import Minimap from 'src/pages/Minimap';
|
||||
import LeftScreen from 'src/pages/LeftScreen';
|
||||
import RightScreen from 'src/pages/RightScreen';
|
||||
import Root from 'src/pages/Root';
|
||||
import useMoveShips from 'src/hooks/useMoveShips';
|
||||
import Layout from 'src/pages/Layout';
|
||||
import Prototype from 'src/pages/Prototype';
|
||||
|
||||
const App = () => {
|
||||
useMoveShips();
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Root />}>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route path="/prototype" element={<Prototype />} />
|
||||
<Route path="minimap" element={<Minimap />} />
|
||||
<Route path="left-screen" element={<LeftScreen />} />
|
||||
<Route path="pearce-screen" element={<LeftScreen />} />
|
||||
<Route path="right-screen" element={<RightScreen />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
70
src/components/Home.tsx
Normal file
70
src/components/Home.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { useStopWatch } from 'src/hooks/useStopWatch';
|
||||
import Spinner from 'src/ui/Spinner';
|
||||
|
||||
const Home = () => {
|
||||
const { hours, minutes, seconds } = useStopWatch();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-screen flex items-center
|
||||
justify-center flex-col gap-10"
|
||||
>
|
||||
<p className="text-5xl text-blue-700">Conversation Manager</p>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-center gap-4 mb-2">
|
||||
<span className="text-2xl">Running</span>
|
||||
<Spinner />
|
||||
</div>
|
||||
|
||||
<span className="text-md">
|
||||
Time elpased: {hours}:{minutes}:{seconds}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-10">
|
||||
<p className="text-3xl text-center">Pages:</p>
|
||||
<div className="flex items-center justify-center gap-2 mt-4">
|
||||
<div>
|
||||
<NavLink
|
||||
to="/left-screen"
|
||||
target="_blank"
|
||||
className="w-24 bg-transparent hover:bg-blue-500
|
||||
text-blue-700 font-semibold hover:text-white border border-blue-500 hover:border-transparent
|
||||
rounded text-sm px-2 py-1 text-center"
|
||||
>
|
||||
Left Screen
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<NavLink
|
||||
to="/minimap"
|
||||
target="_blank"
|
||||
className="w-24 bg-transparent hover:bg-blue-500
|
||||
text-blue-700 font-semibold hover:text-white border border-blue-500 hover:border-transparent
|
||||
rounded text-sm px-2 py-1 text-center"
|
||||
>
|
||||
Minimap
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<NavLink
|
||||
to="/right-screen"
|
||||
target="_blank"
|
||||
className="w-24 bg-transparent hover:bg-blue-500
|
||||
text-blue-700 font-semibold hover:text-white border border-blue-500 hover:border-transparent
|
||||
rounded text-sm px-2 py-1 text-center"
|
||||
>
|
||||
Right Screen
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@ -22,6 +22,7 @@ export function findElementsInGaze(
|
||||
radius: number,
|
||||
inCirclePercentageThresh: number,
|
||||
elementPercentageThesh: number,
|
||||
screen: string,
|
||||
) {
|
||||
const elemsInGaze: ElementInGaze[] = [];
|
||||
|
||||
@ -34,22 +35,25 @@ export function findElementsInGaze(
|
||||
Object.keys(widgets).forEach((widgetId) => {
|
||||
const widget = widgets[widgetId];
|
||||
|
||||
//find the widgets that are within our circle
|
||||
let isIn = false;
|
||||
for (let x = widget.x; x < widget.x + widget.w; x++) {
|
||||
//find the number of pixels within the element that are in the gaze circle
|
||||
if (!isIn) {
|
||||
for (let y = widget.y; y < widget.y + widget.h; y++) {
|
||||
if (distance(x, y, mousePosition.x, mousePosition.y) < radius) {
|
||||
widgetsInGaze.push(widget);
|
||||
isIn = true;
|
||||
console.log('isin!');
|
||||
break;
|
||||
if (widget.screen === screen){ //make sure the widget we are on is in the screen we are interacting with and not a different screen
|
||||
|
||||
//find the widgets that are within our circle
|
||||
let isIn = false;
|
||||
for (let x = widget.x; x < widget.x + widget.w; x++) {
|
||||
//find the number of pixels within the element that are in the gaze circle
|
||||
if (!isIn) {
|
||||
for (let y = widget.y; y < widget.y + widget.h; y++) {
|
||||
if (distance(x, y, mousePosition.x, mousePosition.y) < radius) {
|
||||
widgetsInGaze.push(widget);
|
||||
isIn = true;
|
||||
//console.log('isin!');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// const topLeft: Position = {x:widget.x, y:widget.y};
|
||||
// const topLeft: Position = {x:widget.x, y:widget.y}; //old bad way without distance formula
|
||||
// const topRight: Position = {x:widget.x+widget.w, y:widget.y};
|
||||
// const bottomLeft: Position = {x:widget.x, y:widget.y+widget.h};
|
||||
// const bottomRight: Position = {x:widget.x+widget.w, y:widget.y+widget.h};
|
||||
@ -64,9 +68,9 @@ export function findElementsInGaze(
|
||||
// }
|
||||
});
|
||||
|
||||
if (widgetsInGaze.length > 0) {
|
||||
//if (widgetsInGaze.length > 0) {
|
||||
//console.log("widgets in gaze:", widgetsInGaze)
|
||||
}
|
||||
//}
|
||||
|
||||
widgetsInGaze.forEach(function (widget, widgetIndex) {
|
||||
widget.elements.forEach(function (element, elementIndex) {
|
||||
|
@ -12,7 +12,7 @@ export function useKeyDown() {
|
||||
useEffect(() => {
|
||||
function handleKeyDown(ev: KeyboardEvent) {
|
||||
setKeyDown(ev.code);
|
||||
console.log('Key pressed: ' + ev.code);
|
||||
//console.log('Key pressed: ' + ev.code);
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
@ -12,7 +12,7 @@ export function useKeyUp() {
|
||||
useEffect(() => {
|
||||
function handleKeyUp(ev: KeyboardEvent) {
|
||||
setKeyUp(ev.code);
|
||||
console.log('Key pressed: ' + ev.code);
|
||||
//console.log('Key pressed: ' + ev.code);
|
||||
}
|
||||
|
||||
document.addEventListener('keyup', handleKeyUp);
|
||||
|
@ -15,7 +15,7 @@ export function useMouseButtonDown() {
|
||||
|
||||
useEffect(() => {
|
||||
function handleMouseButtonDown(ev: MouseEvent) {
|
||||
console.log('Mouse button pressed: ' + ev.button);
|
||||
//console.log('Mouse button pressed: ' + ev.button);
|
||||
if (ev.button === 0 || ev.button === 1 || ev.button === 2 || ev.button === 3 ) {
|
||||
setMouseButtDown(ev.button.toString());
|
||||
//console.log('Mouse button pressed: ' + ev.button);
|
||||
|
@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'src/redux/hooks';
|
||||
import {
|
||||
getDrones,
|
||||
getOwnship,
|
||||
updateWidget,
|
||||
updateShipPosition,
|
||||
} from 'src/redux/slices/minimapSlice';
|
||||
import { OWNSHIP_TRAJECTORY } from 'src/utils/constants';
|
||||
|
||||
@ -12,6 +12,10 @@ const useMoveShips = () => {
|
||||
const ownship = useAppSelector(getOwnship);
|
||||
const drones = useAppSelector(getDrones);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('drones', drones);
|
||||
}, [drones.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ownship) return;
|
||||
|
||||
@ -23,11 +27,11 @@ const useMoveShips = () => {
|
||||
) {
|
||||
// only update ownship position if within bounds
|
||||
dispatch(
|
||||
updateWidget({
|
||||
...ownship,
|
||||
x: ownship.x + OWNSHIP_TRAJECTORY.xSpeed,
|
||||
y: ownship.y - OWNSHIP_TRAJECTORY.ySpeed,
|
||||
}),
|
||||
updateShipPosition(
|
||||
ownship.id,
|
||||
ownship.x + OWNSHIP_TRAJECTORY.xSpeed,
|
||||
ownship.y - OWNSHIP_TRAJECTORY.ySpeed,
|
||||
),
|
||||
);
|
||||
}
|
||||
}, 500);
|
||||
@ -72,11 +76,11 @@ const useMoveShips = () => {
|
||||
}
|
||||
|
||||
dispatch(
|
||||
updateWidget({
|
||||
...drone,
|
||||
x: drone.x + droneMove.x,
|
||||
y: drone.y + droneMove.y,
|
||||
}),
|
||||
updateShipPosition(
|
||||
drone.id,
|
||||
drone.x + droneMove.x,
|
||||
drone.y + droneMove.y,
|
||||
),
|
||||
);
|
||||
});
|
||||
}, 1500);
|
||||
@ -86,7 +90,7 @@ const useMoveShips = () => {
|
||||
// dependencies omitted because drones array is changing too frequently
|
||||
// some warning/issue of selector returning different values despite same parameters
|
||||
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
||||
}, []);
|
||||
}, [drones.length]);
|
||||
};
|
||||
|
||||
export default useMoveShips;
|
||||
|
20
src/hooks/useStopWatch.ts
Normal file
20
src/hooks/useStopWatch.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { useStopwatch } from 'react-timer-hook';
|
||||
|
||||
export type StopWatch = {
|
||||
hours: string;
|
||||
minutes: string;
|
||||
seconds: string;
|
||||
};
|
||||
|
||||
export const useStopWatch = (): StopWatch => {
|
||||
const { hours, minutes, seconds } = useStopwatch({ autoStart: true });
|
||||
let hoursStr = hours.toString();
|
||||
let minutesStr = minutes.toString();
|
||||
let secondsStr = seconds.toString();
|
||||
|
||||
if (hours / 10 < 1) hoursStr = `0${hours}`;
|
||||
if (minutes / 10 < 1) minutesStr = `0${minutes}`;
|
||||
if (seconds / 10 < 1) secondsStr = `0${seconds}`;
|
||||
|
||||
return { hours: hoursStr, minutes: minutesStr, seconds: secondsStr };
|
||||
};
|
134
src/pages/Layout.tsx
Normal file
134
src/pages/Layout.tsx
Normal file
@ -0,0 +1,134 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import Gaze from 'src/ui/Gaze';
|
||||
// ~~~~~~~ Redux ~~~~~~~
|
||||
import { useAppDispatch, useAppSelector } from 'src/redux/hooks';
|
||||
import { getSections, getWidgets } from 'src/redux/slices/minimapSlice';
|
||||
import {
|
||||
addKeyDown,
|
||||
getElementsInGaze,
|
||||
getGazesAndKeys,
|
||||
removeKeyDown,
|
||||
setElementsInGaze,
|
||||
type ElementInGaze,
|
||||
} from 'src/redux/slices/gazeSlice';
|
||||
// ~~~~~~~ Cusdom Hooks ~~~~~~~
|
||||
import { useKeyDown } from 'src/hooks/useKeyDown';
|
||||
import { useMousePosition } from 'src/hooks/useMousePosition';
|
||||
import { useKeyUp } from 'src/hooks/useKeyUp';
|
||||
import { useMouseButtonDown } from 'src/hooks/useMouseButtonDown';
|
||||
import { useMouseButtonUp } from 'src/hooks/useMouseButtonUp';
|
||||
import { findElementsInGaze } from 'src/hooks/findElementsInGaze';
|
||||
// ~~~~~~~ Constants ~~~~~~~
|
||||
import { GAZE_RADIUS } from 'src/utils/constants';
|
||||
|
||||
const CIRCLE_PERCENTAGE_THRESH = 0.1;
|
||||
const ELEMENT_PERCENTAGE_THRESH = 0.1;
|
||||
|
||||
const Layout = () => {
|
||||
// ~~~~~ React Router ~~~~~~
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// ~~~~~ Custom Hooks ~~~~~~
|
||||
const mousePosition = useMousePosition();
|
||||
const keyDown = useKeyDown();
|
||||
const keyUp = useKeyUp();
|
||||
const mouseButtonDown = useMouseButtonDown();
|
||||
const mouseButtonUp = useMouseButtonUp();
|
||||
|
||||
// ~~~~~ Selectors ~~~~~~
|
||||
const sections = useAppSelector(getSections);
|
||||
const widgets = useAppSelector(getWidgets);
|
||||
const gazesAndKeys = useAppSelector(getGazesAndKeys);
|
||||
const elemsInGaze: ElementInGaze[] = useAppSelector(getElementsInGaze);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// on mouse position move, check for elements in gaze
|
||||
useEffect(() => {
|
||||
const elementsInGaze = findElementsInGaze(
|
||||
mousePosition,
|
||||
dispatch,
|
||||
widgets,
|
||||
GAZE_RADIUS,
|
||||
CIRCLE_PERCENTAGE_THRESH,
|
||||
ELEMENT_PERCENTAGE_THRESH,
|
||||
pathname,
|
||||
);
|
||||
dispatch(setElementsInGaze(elementsInGaze));
|
||||
if (elementsInGaze.length > 0) {
|
||||
// console.log('elements in gaze: ', elemsInGaze);
|
||||
}
|
||||
}, [mousePosition]);
|
||||
|
||||
// print out the gazes and keys
|
||||
useEffect(() => {
|
||||
// console.log('gazesAndKeys', gazesAndKeys);
|
||||
}, [gazesAndKeys]);
|
||||
|
||||
// on key or mouse press, log the press and what elements are in the gaze to state
|
||||
useEffect(() => {
|
||||
if (keyDown !== '') {
|
||||
const time = new Date().toISOString();
|
||||
dispatch(
|
||||
addKeyDown({
|
||||
elemsInGaze: elemsInGaze,
|
||||
keyPress: keyDown.toString(),
|
||||
timeEnteredGaze: time,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [keyDown]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mouseButtonDown.toString() !== '3') {
|
||||
const time = new Date().toISOString();
|
||||
dispatch(
|
||||
addKeyDown({
|
||||
elemsInGaze: elemsInGaze,
|
||||
keyPress: mouseButtonDown.toString(),
|
||||
timeEnteredGaze: time,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [mouseButtonDown]);
|
||||
|
||||
// on key or mouse release, delete the press that was logged to state and ensure the key/mouse is reset so we can accept the same key/mouse again
|
||||
useEffect(() => {
|
||||
console.log(keyUp);
|
||||
if (keyUp !== '') {
|
||||
dispatch(removeKeyDown(keyUp.toString()));
|
||||
document.dispatchEvent(new KeyboardEvent('keyup', { key: '_' }));
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: '_' }));
|
||||
}
|
||||
}, [keyUp]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mouseButtonUp !== '3') {
|
||||
dispatch(removeKeyDown(mouseButtonUp.toString()));
|
||||
document.dispatchEvent(new MouseEvent('mouseup', { button: 3 }));
|
||||
document.dispatchEvent(new MouseEvent('mousedown', { button: 3 }));
|
||||
}
|
||||
}, [mouseButtonUp]);
|
||||
|
||||
// Redirect to /prototype if the user is on the root path
|
||||
useEffect(() => {
|
||||
if (pathname === '/') {
|
||||
navigate('/prototype');
|
||||
}
|
||||
}, [pathname, navigate]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* {pathname !== '/prototype' && <Navigation />} */}
|
||||
{pathname !== '/prototype' && <Gaze mousePosition={mousePosition} />}
|
||||
|
||||
<main>
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
@ -1,11 +1,17 @@
|
||||
import Widget from 'src/components/Widget/Widget';
|
||||
import { useAppSelector } from 'src/redux/hooks';
|
||||
import { getMinimapWidgets } from 'src/redux/slices/minimapSlice';
|
||||
import useMoveShips from 'src/hooks/useMoveShips';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const Minimap = () => {
|
||||
const widgets = useAppSelector(getMinimapWidgets);
|
||||
/* If this is here, then ships only move if this page is being rendered */
|
||||
useMoveShips();
|
||||
|
||||
// console.log('minimap widgets:', widgets);
|
||||
useEffect(() => {
|
||||
console.log('Minimap widgets:', widgets);
|
||||
}, [widgets.length]);
|
||||
|
||||
return (
|
||||
<div className="absolute top-0 left-0 bg-stone-200 w-[1920px] h-[1080px] hover:cursor-pointer">
|
||||
|
@ -1,47 +1,34 @@
|
||||
import { useEffect } from 'react';
|
||||
// ~~~~~~~ Components ~~~~~~~
|
||||
import Gaze from 'src/ui/Gaze';
|
||||
// ~~~~~~~ Redux ~~~~~~~
|
||||
import { useAppDispatch, useAppSelector } from 'src/redux/hooks';
|
||||
import {
|
||||
type InitialMinimapState,
|
||||
addElementToWidget,
|
||||
addWidget,
|
||||
addWidgetToSection,
|
||||
getSections,
|
||||
getWidgets,
|
||||
initializeState,
|
||||
} from 'src/redux/slices/minimapSlice';
|
||||
import {
|
||||
addKeyDown,
|
||||
getElementsInGaze,
|
||||
getGazesAndKeys,
|
||||
removeKeyDown,
|
||||
setElementsInGaze,
|
||||
type ElementInGaze,
|
||||
} from 'src/redux/slices/gazeSlice';
|
||||
// ~~~~~~~ Cusdom Hooks ~~~~~~~
|
||||
import { useKeyDown } from 'src/hooks/useKeyDown';
|
||||
import { useMousePosition } from 'src/hooks/useMousePosition';
|
||||
import { useKeyUp } from 'src/hooks/useKeyUp';
|
||||
import { useMouseButtonDown } from 'src/hooks/useMouseButtonDown';
|
||||
import { useMouseButtonUp } from 'src/hooks/useMouseButtonUp';
|
||||
import useWorldSim from 'src/hooks/useWorldSim';
|
||||
import { findElementsInGaze } from 'src/hooks/findElementsInGaze';
|
||||
// ~~~~~~~ Prototype ~~~~~~~
|
||||
import assimilator from 'src/prototype/assimilator';
|
||||
import selector from 'src/prototype/selector';
|
||||
// ~~~~~~~ Constants ~~~~~~~
|
||||
import { GAZE_RADIUS } from 'src/utils/constants';
|
||||
const CIRCLE_PERCENTAGE_THRESH = 0.1;
|
||||
const ELEMENT_PERCENTAGE_THRESH = 0.1;
|
||||
import { ownship, drones, initialShips } from 'src/utils/initialShips';
|
||||
import { initialSections } from 'src/utils/initialSections';
|
||||
import Home from 'src/components/Home';
|
||||
import monitor from 'src/prototype/monitor';
|
||||
|
||||
const Prototype = () => {
|
||||
// ~~~~~ Custom Hooks ~~~~~~
|
||||
const { messages, stressLevel } = useWorldSim();
|
||||
const mousePosition = useMousePosition();
|
||||
const keyDown = useKeyDown();
|
||||
const keyUp = useKeyUp();
|
||||
const mouseButtonDown = useMouseButtonDown();
|
||||
const mouseButtonUp = useMouseButtonUp();
|
||||
|
||||
// ~~~~~ Selectors ~~~~~~
|
||||
const sections = useAppSelector(getSections);
|
||||
@ -51,64 +38,29 @@ const Prototype = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// on mouse position move, check for elements in gaze
|
||||
useEffect(() => {
|
||||
const elementsInGaze = findElementsInGaze(
|
||||
mousePosition,
|
||||
dispatch,
|
||||
widgets,
|
||||
GAZE_RADIUS,
|
||||
CIRCLE_PERCENTAGE_THRESH,
|
||||
ELEMENT_PERCENTAGE_THRESH,
|
||||
);
|
||||
dispatch(setElementsInGaze(elementsInGaze));
|
||||
if (elementsInGaze.length > 0) {
|
||||
console.log('elements in gaze: ', elemsInGaze);
|
||||
}
|
||||
}, [mousePosition]);
|
||||
// Intiailize minimap state
|
||||
const initialState: InitialMinimapState = {
|
||||
visualComplexity: 0,
|
||||
audioComplexity: 0,
|
||||
ownship,
|
||||
drones,
|
||||
widgets: { ...initialShips },
|
||||
messages: [],
|
||||
sections: [...initialSections],
|
||||
};
|
||||
|
||||
// print out the gazes and keys
|
||||
useEffect(() => {
|
||||
console.log('gazesAndKeys', gazesAndKeys);
|
||||
}, [gazesAndKeys]);
|
||||
dispatch(initializeState(initialState));
|
||||
}, [dispatch]);
|
||||
|
||||
// on key or mouse press, log the press and what elements are in the gaze to state
|
||||
//call the monitor
|
||||
useEffect(() => {
|
||||
if (keyDown !== '') {
|
||||
dispatch(
|
||||
addKeyDown({ elemsInGaze: elemsInGaze, keyPress: keyDown.toString() }),
|
||||
);
|
||||
}
|
||||
}, [keyDown]);
|
||||
const intervalID = setInterval(() => {
|
||||
monitor({ dispatch });
|
||||
}, 100);
|
||||
|
||||
useEffect(() => {
|
||||
if (mouseButtonDown.toString() !== '') {
|
||||
dispatch(
|
||||
addKeyDown({
|
||||
elemsInGaze: elemsInGaze,
|
||||
keyPress: mouseButtonDown.toString(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [mouseButtonDown]);
|
||||
|
||||
// on key or mouse release, delete the press that was logged to state and ensure the key/mouse is reset so we can accept the same key/mouse again
|
||||
useEffect(() => {
|
||||
console.log(keyUp);
|
||||
if (keyUp !== '') {
|
||||
dispatch(removeKeyDown(keyUp.toString()));
|
||||
document.dispatchEvent(new KeyboardEvent('keyup', { key: '_' }));
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: '_' }));
|
||||
}
|
||||
}, [keyUp]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mouseButtonUp !== '') {
|
||||
dispatch(removeKeyDown(mouseButtonUp.toString()));
|
||||
document.dispatchEvent(new KeyboardEvent('mouseup', { key: '_' }));
|
||||
document.dispatchEvent(new KeyboardEvent('mousedown', { key: '_' }));
|
||||
}
|
||||
}, [mouseButtonUp]);
|
||||
return () => clearInterval(intervalID);
|
||||
}, []);
|
||||
|
||||
// run whenever messages array changes
|
||||
useEffect(() => {
|
||||
@ -175,7 +127,7 @@ const Prototype = () => {
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
return <Gaze mousePosition={mousePosition} />;
|
||||
return <Home />;
|
||||
};
|
||||
|
||||
export default Prototype;
|
@ -1,30 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import Prototype from 'src/components/Prototype';
|
||||
import Navigation from 'src/ui/Navigation';
|
||||
|
||||
const Root = () => {
|
||||
// ~~~~~ React Router ~~~~~~
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Redirect to /minimap if the user is on the root path
|
||||
useEffect(() => {
|
||||
if (pathname === '/') {
|
||||
navigate('/minimap');
|
||||
}
|
||||
}, [pathname, navigate]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navigation />
|
||||
<Prototype />
|
||||
|
||||
<main>
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Root;
|
@ -2,8 +2,11 @@ import type { AppDispatch } from 'src/redux/store';
|
||||
import {
|
||||
removeWidget,
|
||||
deleteElementFromWidget,
|
||||
updateElementExpiration,
|
||||
} from 'src/redux/slices/minimapSlice';
|
||||
import store from 'src/redux/store';
|
||||
import type { ElementInGaze, GazeAndKey } from 'src/redux/slices/gazeSlice';
|
||||
import type { BaseElement } from 'src/types/element';
|
||||
|
||||
type MonitorProps = {
|
||||
// define expected input here and it's type (number, string, etc.)
|
||||
@ -16,26 +19,60 @@ type MonitorProps = {
|
||||
* @returns ???
|
||||
*/
|
||||
const monitor = ({ dispatch }: MonitorProps) => {
|
||||
//human visual recognition is about 100 ms: https://www.cell.com/neuron/fulltext/S0896-6273(09)00171-8?_returnURL=https%3A%2F%2Flinkinghub.elsevier.com%2Fretrieve%2Fpii%2FS0896627309001718%3Fshowall%3Dtrue#secd20967130e327
|
||||
const widgets = store.getState().minimap.widgets;
|
||||
const elementsInGaze = store.getState().gaze.elementsInGaze;
|
||||
const gazesAndKeys = store.getState().gaze.gazesAndKeys;
|
||||
|
||||
Object.keys(widgets).forEach((widgetId) => {
|
||||
|
||||
/*
|
||||
*
|
||||
*detect and handle interactions with elements
|
||||
*
|
||||
*/
|
||||
|
||||
//detect interactions via gaze
|
||||
const timeSomeMsAgo = new Date();
|
||||
timeSomeMsAgo.setMilliseconds(
|
||||
timeSomeMsAgo.getMilliseconds()-100, //<- 100 should be in constants file, but just testing now
|
||||
);//set timeSomeMsAgo to the time it was 100 ms ago
|
||||
elementsInGaze.forEach(function(elementInGaze: ElementInGaze, elementInGazeIndex:number){
|
||||
if(timeSomeMsAgo.toISOString() >= elementInGaze.timeEnteredGaze){ //has been in gaze for at least 100 ms
|
||||
console.log('interacted with element '+elementInGaze.id+' using gaze');
|
||||
dispatch(updateElementExpiration(elementInGaze.widgetId, elementInGaze.id)); //update the time until expiration
|
||||
}
|
||||
});
|
||||
|
||||
//detect interactions via key press
|
||||
gazesAndKeys.forEach(function(gazeAndKey:GazeAndKey, gazeAndKeyIndex:number){
|
||||
gazeAndKey.elemsInGaze.forEach(function(elementInGaze, elementInGazeIndex){
|
||||
dispatch(updateElementExpiration(elementInGaze.widgetId, elementInGaze.id)); //update the time until expiration
|
||||
console.log('interacted with element '+elementInGaze.id+' using '+gazeAndKey.keyPress);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
Object.keys(widgets).forEach((widgetId) => { //update widgets and elements that haven't been interacted with
|
||||
const widget = widgets[widgetId];
|
||||
|
||||
widget.elements.forEach((element, elementIndex) => {
|
||||
widget.elements.forEach((element:BaseElement, elementIndex:number) => {
|
||||
//go through each element
|
||||
if (element.expiration && !element.interacted) {
|
||||
if (element.expiration && !element.interacted) {//if it has an expiration and has not been interacted with
|
||||
const time = new Date().toISOString();
|
||||
|
||||
if (element.expiration <= time) {
|
||||
console.log('element ' + element.id + ' expired! deleting...');
|
||||
|
||||
if (element.onExpiration === 'delete') {
|
||||
if (widget.elements.length === 1) {
|
||||
console.log('widget length 1');
|
||||
dispatch(removeWidget(widget.id));
|
||||
} else {
|
||||
dispatch(deleteElementFromWidget(widgetId, element.id));
|
||||
}
|
||||
switch(element.onExpiration){
|
||||
case 'delete':
|
||||
console.log('element ' + element.id + ' expired! deleting...');
|
||||
if (widget.elements.length === 1) { //if this is the last element, delete the whole widget
|
||||
dispatch(removeWidget(widget.id));
|
||||
} else {
|
||||
dispatch(deleteElementFromWidget(widgetId, element.id)); //delete the widget
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,159 @@ const selector = ({ message, stressLevel }: SelectorProps = {}) => {
|
||||
} else {
|
||||
// If no message is provided, return the initial LPD
|
||||
return initialLPD;
|
||||
}
|
||||
// const selector = ({ message }: SelectorProps) => {
|
||||
// const possibleWidgets: Widget[] = [];
|
||||
|
||||
// const expirationTime = new Date();
|
||||
// expirationTime.setSeconds(
|
||||
// expirationTime.getSeconds() + (Math.floor(Math.random() * 10) + 5),
|
||||
// ); //set the time to expire to a time between 5 and 15 seconds
|
||||
|
||||
// const expiration = expirationTime.toISOString();
|
||||
|
||||
// const onExpiration = 'delete';
|
||||
|
||||
// // Only doing a single widget for Demo3
|
||||
// const widget: Widget = {
|
||||
// // static ID for Demo3
|
||||
// id: 'list',
|
||||
// sectionType: 'tinder',
|
||||
// type: 'list',
|
||||
// screen: '/pearce-screen',
|
||||
// elements: [],
|
||||
// x: 50,
|
||||
// y: 40,
|
||||
// w: 300,
|
||||
// h: 800,
|
||||
// canOverlap: false,
|
||||
// useElementLocation: false,
|
||||
// maxAmount: 1,
|
||||
// };
|
||||
|
||||
// let elements: Element[] = [];
|
||||
|
||||
// switch (message.kind) {
|
||||
// case 'RequestApprovalToAttack':
|
||||
// elements.push({
|
||||
// id: uuid(),
|
||||
// type: 'request-approval',
|
||||
// modality: 'visual',
|
||||
// h: 100,
|
||||
// w: 200,
|
||||
// xWidget: 0,
|
||||
// yWidget: 0,
|
||||
// message,
|
||||
// collapsed: true,
|
||||
// priority: message.priority,
|
||||
// icon: {
|
||||
// id: uuid(),
|
||||
// modality: 'visual',
|
||||
// type: 'icon',
|
||||
// h: 30,
|
||||
// w: 30,
|
||||
// xWidget: 0,
|
||||
// yWidget: 0,
|
||||
// src: DRONE_ICON,
|
||||
// },
|
||||
// leftButton: {
|
||||
// id: uuid(),
|
||||
// modality: 'visual',
|
||||
// h: 30,
|
||||
// w: 80,
|
||||
// xWidget: 0,
|
||||
// yWidget: 0,
|
||||
// text: 'Deny',
|
||||
// type: 'button',
|
||||
// },
|
||||
// rightButton: {
|
||||
// id: uuid(),
|
||||
// modality: 'visual',
|
||||
// h: 30,
|
||||
// w: 80,
|
||||
// xWidget: 0,
|
||||
// yWidget: 0,
|
||||
// text: 'Approve',
|
||||
// type: 'button',
|
||||
// },
|
||||
// } satisfies RequestApprovalElement);
|
||||
// break;
|
||||
|
||||
// case 'MissileToOwnshipDetected':
|
||||
// elements.push({
|
||||
// id: uuid(),
|
||||
// type: 'missile-incoming',
|
||||
// modality: 'visual',
|
||||
// xWidget: 0,
|
||||
// yWidget: 0,
|
||||
// h: 80,
|
||||
// w: 80,
|
||||
// message,
|
||||
// priority: message.priority,
|
||||
// icon: {
|
||||
// id: uuid(),
|
||||
// modality: 'visual',
|
||||
// type: 'icon',
|
||||
// src: DANGER_ICON,
|
||||
// h: 30,
|
||||
// w: 30,
|
||||
// xWidget: 0,
|
||||
// yWidget: 0,
|
||||
// },
|
||||
// } satisfies MissileIncomingElement);
|
||||
// break;
|
||||
|
||||
// case 'AcaHeadingToBase':
|
||||
// elements.push({
|
||||
// id: uuid(),
|
||||
// type: 'text',
|
||||
// modality: 'visual',
|
||||
// xWidget: 0,
|
||||
// yWidget: 0,
|
||||
// h: 30,
|
||||
// w: 200,
|
||||
// text: 'Aircraft heading to base',
|
||||
// priority: message.priority,
|
||||
// } satisfies TextElement);
|
||||
// break;
|
||||
|
||||
// case 'AcaFuelLow':
|
||||
// elements.push({
|
||||
// id: uuid(),
|
||||
// type: 'table',
|
||||
// modality: 'visual',
|
||||
// xWidget: 0,
|
||||
// yWidget: 0,
|
||||
// h: 50,
|
||||
// w: 200,
|
||||
// rows: 2,
|
||||
// cols: 2,
|
||||
// tableData: [
|
||||
// ['Fuel', 'Low'],
|
||||
// ['Altitude', 'Low'],
|
||||
// ],
|
||||
// priority: message.priority,
|
||||
// } satisfies TableElement);
|
||||
// break;
|
||||
|
||||
// case 'AcaDefect':
|
||||
// elements.push({
|
||||
// id: uuid(),
|
||||
// type: 'table',
|
||||
// modality: 'visual',
|
||||
// xWidget: 0,
|
||||
// yWidget: 0,
|
||||
// h: 50,
|
||||
// w: 200,
|
||||
// rows: 2,
|
||||
// cols: 2,
|
||||
// tableData: [
|
||||
// ['Defect', 'Engine'],
|
||||
// ['Altitude', 'Low'],
|
||||
// ],
|
||||
// priority: message.priority,
|
||||
// } satisfies TableElement);
|
||||
// break;
|
||||
// }
|
||||
// const possibleWidgets: Widget[] = [];
|
||||
|
||||
// const expirationTime = new Date();
|
||||
|
@ -65,10 +65,10 @@ export const gazeSlice = createSlice({
|
||||
// ]
|
||||
// }
|
||||
},
|
||||
removeKeyDown: (state, action: PayloadAction<string>) => {
|
||||
state.gazesAndKeys.map(function (gazeAndKey, gazeAndKeyIndex) {
|
||||
// console.log('equality toAdd: '+action.payload+' inStorage: '+gazeAndKey.keyPress)
|
||||
if (action.payload === gazeAndKey.keyPress) {
|
||||
removeKeyDown: (state, action:PayloadAction<string>) => {
|
||||
state.gazesAndKeys.map(function(gazeAndKey, gazeAndKeyIndex){
|
||||
//console.log('equality toAdd: '+action.payload+' inStorage: '+gazeAndKey.keyPress)
|
||||
if(action.payload === gazeAndKey.keyPress){
|
||||
//we found the key that was released
|
||||
state.gazesAndKeys = [
|
||||
...state.gazesAndKeys.slice(0, gazeAndKeyIndex),
|
||||
|
@ -1,21 +1,26 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { Widget, WidgetMap } from 'src/types/widget';
|
||||
import type { Widget, VehicleWidget, WidgetMap } from 'src/types/widget';
|
||||
import type { Message } from 'src/types/schema-types';
|
||||
import type { Element } from 'src/types/element';
|
||||
import type { LinkedSectionWidget, Section } from 'src/types/support-types';
|
||||
import selector from 'src/prototype/selector';
|
||||
import { ownship, drones } from 'src/prototype/lpd/initialLPD';
|
||||
|
||||
type InitialState = {
|
||||
export type InitialMinimapState = {
|
||||
visualComplexity: number;
|
||||
audioComplexity: number;
|
||||
|
||||
// read-only
|
||||
ownship: VehicleWidget | null;
|
||||
drones: VehicleWidget[];
|
||||
|
||||
widgets: WidgetMap;
|
||||
messages: Message[];
|
||||
sections: Section[];
|
||||
};
|
||||
|
||||
const initialState: InitialState = {
|
||||
const initialState: InitialMinimapState = {
|
||||
visualComplexity: 0,
|
||||
audioComplexity: 0,
|
||||
messages: [],
|
||||
@ -27,6 +32,22 @@ export const minimapSlice = createSlice({
|
||||
name: 'minimap',
|
||||
initialState,
|
||||
reducers: {
|
||||
// This is needed for compatibility with redux-state-sync
|
||||
initializeState: (state, action: PayloadAction<InitialMinimapState>) => {
|
||||
// don't initialize if we already have data (one-time initialization)
|
||||
if (Object.keys(state.widgets).length > 0 || state.sections.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.visualComplexity = action.payload.visualComplexity;
|
||||
state.audioComplexity = action.payload.audioComplexity;
|
||||
state.ownship = action.payload.ownship;
|
||||
state.drones = action.payload.drones;
|
||||
state.widgets = action.payload.widgets;
|
||||
state.messages = action.payload.messages;
|
||||
state.sections = action.payload.sections;
|
||||
},
|
||||
|
||||
addMapSection: (state, action) => {
|
||||
state.sections.push(action.payload); //add it to our sections as well
|
||||
},
|
||||
@ -39,6 +60,40 @@ export const minimapSlice = createSlice({
|
||||
state.widgets[action.payload.id] = action.payload;
|
||||
},
|
||||
|
||||
updateShipPosition: {
|
||||
prepare(shipId: string, x: number, y: number) {
|
||||
return {
|
||||
payload: { shipId, x, y },
|
||||
};
|
||||
},
|
||||
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadAction<{ shipId: string; x: number; y: number }>,
|
||||
) => {
|
||||
const { shipId, x, y } = action.payload;
|
||||
const ship = state.widgets[shipId];
|
||||
|
||||
// check if ship exists
|
||||
if (!ship) {
|
||||
console.error(`Ship with id ${shipId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the ship is a vehicle
|
||||
if (ship.type !== 'vehicle') {
|
||||
console.error(`Widget with id ${shipId} is not a vehicle`);
|
||||
return;
|
||||
}
|
||||
|
||||
state.widgets[shipId] = {
|
||||
...ship,
|
||||
x,
|
||||
y,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
removeWidget: (state, action: PayloadAction<string>) => {
|
||||
delete state.widgets[action.payload];
|
||||
},
|
||||
@ -69,6 +124,43 @@ export const minimapSlice = createSlice({
|
||||
});
|
||||
},
|
||||
|
||||
updateElementExpiration: {
|
||||
//update the time until window of interaction expires
|
||||
prepare(widgetId: string, elementId: string) {
|
||||
return {
|
||||
payload: { widgetId, elementId },
|
||||
};
|
||||
},
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadAction<{ widgetId: string; elementId: string }>,
|
||||
) => {
|
||||
const { widgetId, elementId } = action.payload;
|
||||
const widget = state.widgets[widgetId];
|
||||
|
||||
// if widget exists
|
||||
if (widget) {
|
||||
const tempElements = state.widgets[widgetId].elements;
|
||||
tempElements.forEach(function (element, elementIndex) {
|
||||
if (element.id === elementId && element.expirationInterval) {
|
||||
const newExpiration = new Date();
|
||||
newExpiration.setSeconds(
|
||||
newExpiration.getSeconds() + element.expirationInterval,
|
||||
);
|
||||
tempElements[elementIndex].expiration =
|
||||
newExpiration.toISOString();
|
||||
}
|
||||
});
|
||||
state.widgets[widgetId] = {
|
||||
...widget,
|
||||
elements: tempElements,
|
||||
};
|
||||
} else {
|
||||
console.error(`Widget with id ${widgetId} not found`);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
deleteElementFromWidget: {
|
||||
prepare(widgetId: string, elementId: string) {
|
||||
return {
|
||||
@ -154,23 +246,21 @@ export const minimapSlice = createSlice({
|
||||
getWidgets: (state) => state.widgets,
|
||||
getLeftScreenWidgets: (state) => {
|
||||
const minimapWidgets = Object.keys(state.widgets).filter(
|
||||
(id) => state.widgets[id].screen === 'left',
|
||||
(id) => state.widgets[id].screen === '/pearce-screen',
|
||||
);
|
||||
|
||||
console.log('minimapWidgets:', minimapWidgets);
|
||||
|
||||
return minimapWidgets.map((id) => state.widgets[id]);
|
||||
},
|
||||
getMinimapWidgets: (state) => {
|
||||
const minimapWidgets = Object.keys(state.widgets).filter(
|
||||
(id) => state.widgets[id].screen === 'minimap',
|
||||
(id) => state.widgets[id].screen === '/minimap',
|
||||
);
|
||||
|
||||
return minimapWidgets.map((id) => state.widgets[id]);
|
||||
},
|
||||
getRightScreenWidgets: (state) => {
|
||||
const minimapWidgets = Object.keys(state.widgets).filter(
|
||||
(id) => state.widgets[id].screen === 'right',
|
||||
(id) => state.widgets[id].screen === '/right-screen',
|
||||
);
|
||||
|
||||
return minimapWidgets.map((id) => state.widgets[id]);
|
||||
@ -183,27 +273,40 @@ export const minimapSlice = createSlice({
|
||||
getMessages: (state) => state.messages,
|
||||
|
||||
// ~~~~~ selectors for ships ~~~~~
|
||||
getOwnship: (state) =>
|
||||
state.widgets[ownship.id] ? state.widgets[ownship.id] : null,
|
||||
getDrones: (state) =>
|
||||
drones.map((drone) =>
|
||||
state.widgets[drone.id] ? state.widgets[drone.id] : null,
|
||||
),
|
||||
getOwnship: (state) => {
|
||||
if (state.ownship) {
|
||||
return state.widgets[state.ownship.id];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getDrones: (state) => {
|
||||
if (state.drones.length > 0) {
|
||||
return state.drones.map((drone) => state.widgets[drone.id]);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// action creators (automatically generated by createSlice for each reducer)
|
||||
export const {
|
||||
initializeState,
|
||||
|
||||
addMapSection,
|
||||
addMessage,
|
||||
addWidget,
|
||||
updateWidget,
|
||||
removeWidget,
|
||||
addElementToWidget,
|
||||
addWidgetToSection,
|
||||
deleteElementFromWidget,
|
||||
updateElementExpiration,
|
||||
|
||||
updateWidget,
|
||||
updateShipPosition,
|
||||
updateVisualComplexity,
|
||||
updateAudioComplexity,
|
||||
|
||||
removeWidget,
|
||||
deleteElementFromWidget,
|
||||
|
||||
toggleElementInteraction,
|
||||
} = minimapSlice.actions;
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { combineSlices, configureStore } from '@reduxjs/toolkit';
|
||||
import {
|
||||
createStateSyncMiddleware,
|
||||
initMessageListener,
|
||||
initStateWithPrevTab,
|
||||
withReduxStateSync,
|
||||
type Config,
|
||||
} from 'redux-state-sync';
|
||||
import { minimapSlice } from './slices/minimapSlice';
|
||||
@ -16,18 +17,20 @@ const rootReducer = combineSlices(minimapSlice, gazeSlice);
|
||||
type RootState = ReturnType<typeof rootReducer>;
|
||||
|
||||
const reduxStateSyncConfig: Config = {
|
||||
prepareState: (state: RootState) => state,
|
||||
// blacklist actions that should not be synced
|
||||
blacklist: [minimapSlice.actions.initializeState.type],
|
||||
};
|
||||
const stateSyncMiddleware = createStateSyncMiddleware(reduxStateSyncConfig);
|
||||
|
||||
const store = configureStore({
|
||||
reducer: rootReducer,
|
||||
reducer: withReduxStateSync(rootReducer),
|
||||
// @ts-ignore (TODO: fix this type error)
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(stateSyncMiddleware),
|
||||
});
|
||||
|
||||
initMessageListener(store);
|
||||
// Initialize the state with the previous tab's state
|
||||
initStateWithPrevTab(store);
|
||||
|
||||
// Infer the type of `store`
|
||||
type AppStore = typeof store;
|
||||
|
@ -5,7 +5,7 @@ export type Cell = {
|
||||
color?: string;
|
||||
};
|
||||
|
||||
export type ScreenType = 'pearce' | 'minimap' | 'boring-right';
|
||||
export type ScreenType = '/pearce-screen' | '/minimap' | '/right-screen';
|
||||
|
||||
export type SectionType =
|
||||
| 'free'
|
||||
|
@ -36,6 +36,8 @@ export type GridWidget = BaseWidget & {
|
||||
|
||||
export type VehicleWidget = BaseWidget & {
|
||||
type: 'vehicle';
|
||||
// this corresponds to the id in the schema-types defined by the world-sim team
|
||||
vehicleId: number;
|
||||
// additonal properties...
|
||||
};
|
||||
|
||||
|
@ -10,11 +10,11 @@ const Navigation = () => {
|
||||
dark:text-gray-300"
|
||||
>
|
||||
<NavLink
|
||||
to="/left-screen"
|
||||
to="/pearce-screen"
|
||||
className={`text-gray-800 dark:text-gray-200 border-b-2
|
||||
${pathname === '/left-screen' ? 'border-blue-500' : ''} mx-1.5 sm:mx-6 text-3xl`}
|
||||
${pathname === '/pearce-screen' ? 'border-blue-500' : ''} mx-1.5 sm:mx-6 text-3xl`}
|
||||
>
|
||||
Left Screen
|
||||
Pearce Screen
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/minimap"
|
||||
|
10
src/ui/Spinner.tsx
Normal file
10
src/ui/Spinner.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
const Spinner = () => {
|
||||
return (
|
||||
<div
|
||||
className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-e-transparent align-[-0.125em] text-primary motion-reduce:animate-[spin_1.5s_linear_infinite] text-red-600"
|
||||
role="status"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Spinner;
|
@ -2,19 +2,21 @@ import { v4 as uuid } from 'uuid';
|
||||
import type { IconElement } from 'src/types/element';
|
||||
import OWNSHIP_LOGO from 'src/icons/currentPosition.svg';
|
||||
import DRONE_LOGO from 'src/icons/drone.svg';
|
||||
import type { Widget, VehicleWidget, WidgetMap } from 'src/types/widget';
|
||||
import type { VehicleWidget, WidgetMap } from 'src/types/widget';
|
||||
|
||||
const createDroneWidget = (
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
vehicleId: number,
|
||||
): VehicleWidget => ({
|
||||
elements: [droneElement],
|
||||
id: uuid(),
|
||||
sectionType: 'free',
|
||||
type: 'vehicle',
|
||||
screen: 'minimap',
|
||||
screen: '/minimap',
|
||||
vehicleId,
|
||||
|
||||
x,
|
||||
y,
|
||||
@ -42,13 +44,14 @@ const ownshipElement: IconElement = {
|
||||
|
||||
export const ownship: VehicleWidget = {
|
||||
id: uuid(),
|
||||
vehicleId: 0,
|
||||
|
||||
x: 400,
|
||||
y: 950,
|
||||
w: 50,
|
||||
h: 50,
|
||||
|
||||
screen: 'minimap',
|
||||
screen: '/minimap',
|
||||
sectionType: 'free',
|
||||
type: 'vehicle',
|
||||
elements: [ownshipElement],
|
||||
@ -72,18 +75,19 @@ const droneElement: IconElement = {
|
||||
yWidget: 0,
|
||||
};
|
||||
|
||||
export const drones = [
|
||||
createDroneWidget(500, 200, 50, 50),
|
||||
createDroneWidget(1500, 550, 50, 50),
|
||||
createDroneWidget(1500, 350, 50, 50),
|
||||
createDroneWidget(200, 900, 50, 50),
|
||||
createDroneWidget(1150, 750, 50, 50),
|
||||
];
|
||||
const drone1 = createDroneWidget(500, 200, 50, 50, 1);
|
||||
const drone2 = createDroneWidget(1500, 550, 50, 50, 2);
|
||||
const drone3 = createDroneWidget(1500, 350, 50, 50, 3);
|
||||
const drone4 = createDroneWidget(200, 900, 50, 50, 4);
|
||||
const drone5 = createDroneWidget(1150, 750, 50, 50, 5);
|
||||
|
||||
export const drones = [drone1, drone2, drone3, drone4, drone5];
|
||||
|
||||
export const initialShips: WidgetMap = {
|
||||
[ownship.id]: ownship,
|
||||
...drones.reduce((acc, drone) => {
|
||||
acc[drone.id] = drone;
|
||||
return acc;
|
||||
}, {} as WidgetMap),
|
||||
[drone1.id]: drone1,
|
||||
[drone2.id]: drone2,
|
||||
[drone3.id]: drone3,
|
||||
[drone4.id]: drone4,
|
||||
[drone5.id]: drone5,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user