give Prototype its own page
This commit is contained in:
parent
9ea8956284
commit
b881ef1d62
@ -3,16 +3,15 @@ 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="right-screen" element={<RightScreen />} />
|
||||
|
60
src/components/Home.tsx
Normal file
60
src/components/Home.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import Spinner from 'src/ui/Spinner';
|
||||
|
||||
const Home = () => {
|
||||
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 className="flex items-center justify-center gap-4">
|
||||
<span className="text-xl">Running</span>
|
||||
<Spinner />
|
||||
</div>
|
||||
|
||||
<div className="mt-10">
|
||||
<p className="text-2xl 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;
|
@ -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;
|
||||
|
32
src/pages/Layout.tsx
Normal file
32
src/pages/Layout.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import Gaze from 'src/ui/Gaze';
|
||||
import { useMousePosition } from 'src/hooks/useMousePosition';
|
||||
|
||||
const Layout = () => {
|
||||
// ~~~~~ React Router ~~~~~~
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const mousePosition = useMousePosition();
|
||||
|
||||
// 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,14 +1,14 @@
|
||||
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,
|
||||
@ -31,6 +31,11 @@ import assimilator from 'src/prototype/assimilator';
|
||||
import selector from 'src/prototype/selector';
|
||||
// ~~~~~~~ Constants ~~~~~~~
|
||||
import { GAZE_RADIUS } from 'src/utils/constants';
|
||||
import { ownship, drones, initialShips } from 'src/utils/initialShips';
|
||||
import { initialSections } from 'src/utils/initialSections';
|
||||
import Spinner from 'src/ui/Spinner';
|
||||
import Home from 'src/components/Home';
|
||||
|
||||
const CIRCLE_PERCENTAGE_THRESH = 0.1;
|
||||
const ELEMENT_PERCENTAGE_THRESH = 0.1;
|
||||
|
||||
@ -51,6 +56,21 @@ const Prototype = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
// Intiailize minimap state
|
||||
const initialState: InitialMinimapState = {
|
||||
visualComplexity: 0,
|
||||
audioComplexity: 0,
|
||||
ownship,
|
||||
drones,
|
||||
widgets: { ...initialShips },
|
||||
messages: [],
|
||||
sections: [...initialSections],
|
||||
};
|
||||
|
||||
dispatch(initializeState(initialState));
|
||||
}, [dispatch]);
|
||||
|
||||
// on mouse position move, check for elements in gaze
|
||||
useEffect(() => {
|
||||
const elementsInGaze = findElementsInGaze(
|
||||
@ -63,13 +83,13 @@ const Prototype = () => {
|
||||
);
|
||||
dispatch(setElementsInGaze(elementsInGaze));
|
||||
if (elementsInGaze.length > 0) {
|
||||
console.log('elements in gaze: ', elemsInGaze);
|
||||
// console.log('elements in gaze: ', elemsInGaze);
|
||||
}
|
||||
}, [mousePosition]);
|
||||
|
||||
// print out the gazes and keys
|
||||
useEffect(() => {
|
||||
console.log('gazesAndKeys', gazesAndKeys);
|
||||
// console.log('gazesAndKeys', gazesAndKeys);
|
||||
}, [gazesAndKeys]);
|
||||
|
||||
// on key or mouse press, log the press and what elements are in the gaze to state
|
||||
@ -175,7 +195,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;
|
@ -1,32 +1,46 @@
|
||||
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 { initialSections } from 'src/redux/utils/initialSections';
|
||||
import { ownship, drones, initialShips } from 'src/utils/initialShips';
|
||||
|
||||
type InitialState = {
|
||||
export type InitialMinimapState = {
|
||||
visualComplexity: number;
|
||||
audioComplexity: number;
|
||||
ownship: VehicleWidget | null;
|
||||
drones: VehicleWidget[];
|
||||
widgets: WidgetMap;
|
||||
messages: Message[];
|
||||
sections: Section[];
|
||||
};
|
||||
|
||||
const initialState: InitialState = {
|
||||
const initialState: InitialMinimapState = {
|
||||
visualComplexity: 0,
|
||||
audioComplexity: 0,
|
||||
widgets: initialShips,
|
||||
ownship: null,
|
||||
drones: [],
|
||||
widgets: {},
|
||||
messages: [],
|
||||
sections: initialSections,
|
||||
sections: [],
|
||||
};
|
||||
|
||||
export const minimapSlice = createSlice({
|
||||
name: 'minimap',
|
||||
initialState,
|
||||
reducers: {
|
||||
// This is needed for compatibility with redux-state-sync
|
||||
initializeState: (state, action: PayloadAction<InitialMinimapState>) => {
|
||||
console.log('Initializing minimap state with: ', action.payload);
|
||||
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 +53,38 @@ 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];
|
||||
|
||||
if (!ship) {
|
||||
console.error(`Ship with id ${shipId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
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];
|
||||
},
|
||||
@ -155,8 +201,6 @@ export const minimapSlice = createSlice({
|
||||
(id) => state.widgets[id].screen === 'left',
|
||||
);
|
||||
|
||||
console.log('minimapWidgets:', minimapWidgets);
|
||||
|
||||
return minimapWidgets.map((id) => state.widgets[id]);
|
||||
},
|
||||
getMinimapWidgets: (state) => {
|
||||
@ -181,27 +225,29 @@ 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) => state.ownship,
|
||||
getDrones: (state) => state.drones,
|
||||
},
|
||||
});
|
||||
|
||||
// action creators (automatically generated by createSlice for each reducer)
|
||||
export const {
|
||||
initializeState,
|
||||
|
||||
addMapSection,
|
||||
addMessage,
|
||||
addWidget,
|
||||
updateWidget,
|
||||
removeWidget,
|
||||
addElementToWidget,
|
||||
addWidgetToSection,
|
||||
deleteElementFromWidget,
|
||||
|
||||
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;
|
||||
|
@ -34,6 +34,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
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',
|
||||
vehicleId,
|
||||
|
||||
x,
|
||||
y,
|
||||
@ -42,6 +44,7 @@ const ownshipElement: IconElement = {
|
||||
|
||||
export const ownship: VehicleWidget = {
|
||||
id: uuid(),
|
||||
vehicleId: 0,
|
||||
|
||||
x: 400,
|
||||
y: 950,
|
||||
@ -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