Skip to content

Commit

Permalink
feat(Navigation): Add search field for finding solar systems and plan…
Browse files Browse the repository at this point in the history
…ets. Closes #388
  • Loading branch information
alexanderson1993 committed Aug 2, 2022
1 parent 33d1989 commit 75e6143
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 28 deletions.
54 changes: 53 additions & 1 deletion client/src/cards/ComponentDemo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import SearchableList from "@thorium/ui/SearchableList";
import InfoTip from "@thorium/ui/InfoTip";
import TagInput from "@thorium/ui/TagInput";
import Button from "@thorium/ui/Button";
import SearchableInput, {DefaultResultLabel} from "@thorium/ui/SearchableInput";
import {QueryFunctionContext} from "@tanstack/react-query";

const ModalDemo = ({title, children}: {title: string; children: ReactNode}) => {
const [isOpen, setIsOpen] = useState(false);
Expand Down Expand Up @@ -78,7 +80,38 @@ const TagInputDemo = () => {
/>
);
};

async function searchableInputQuery({
queryKey,
}: QueryFunctionContext<[string, string]>) {
await new Promise(res => setTimeout(res, 1000 + Math.random() * 500));
const [key, query] = queryKey;
const people = [
{id: 1, name: "Wade Cooper"},
{id: 2, name: "Arlene Mccoy"},
{id: 3, name: "Devon Webb"},
{id: 4, name: "Tom Cook"},
{id: 5, name: "Tanya Fox"},
{id: 6, name: "Hellen Schmidt"},
];

const filteredPeople =
query === ""
? people
: people.filter(person =>
person.name
.toLowerCase()
.replace(/\s+/g, "")
.includes(query.toLowerCase().replace(/\s+/g, ""))
);

return filteredPeople;
}

export default function ComponentDemo() {
const [selected, setSelected] = useState<null | {id: number; name: string}>(
null
);
return (
<div className="flex flex-col gap-8 text-white h-full overflow-y-auto">
<div className="flex flex-col gap-4">
Expand Down Expand Up @@ -114,7 +147,26 @@ export default function ComponentDemo() {
</div>
</div>
</div>

<div>
<h2 className="text-3xl">Searchable Input</h2>
<SearchableInput
key="demo"
selected={selected}
setSelected={val => {
setSelected(val);
console.log(val);
}}
getOptions={searchableInputQuery}
displayValue={result => result?.name}
ResultLabel={({result, active, selected}) => (
<DefaultResultLabel
name={result.name}
active={active}
selected={selected}
/>
)}
/>
</div>
<div>
<h2 className="text-3xl">Badges</h2>
<div className="flex gap-4">
Expand Down
4 changes: 4 additions & 0 deletions client/src/cards/Navigation/InterstellarWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import {useGetStarmapStore} from "client/src/components/Starmap/starmapStore";
import {useNetRequest} from "client/src/context/useNetRequest";
import {InterstellarMap} from "client/src/components/Starmap/InterstellarMap";
import SystemMarker from "client/src/components/Starmap/SystemMarker";
import {useEffect} from "react";

export function InterstellarWrapper() {
const useStarmapStore = useGetStarmapStore();
// This netRequest comes from the starmap core.
const starmapSystems = useNetRequest("starmapSystems");

useEffect(() => {
useStarmapStore.getState().currentSystemSet?.(null);
}, []);
return (
<InterstellarMap>
{starmapSystems.map(sys =>
Expand Down
7 changes: 6 additions & 1 deletion client/src/cards/Navigation/SolarSystemWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useGetStarmapStore} from "client/src/components/Starmap/starmapStore";
import {useNetRequest} from "client/src/context/useNetRequest";
import {useRef} from "react";
import {useEffect, useRef} from "react";
import {SolarSystemMap} from "client/src/components/Starmap/SolarSystemMap";
import {Suspense} from "react";
import {Color, Group, Vector3} from "three";
Expand All @@ -23,6 +23,11 @@ export function SolarSystemWrapper() {
const currentSystem = useStarmapStore(store => store.currentSystem);

if (currentSystem === null) throw new Error("No current system");

useEffect(() => {
useStarmapStore.getState().currentSystemSet?.(currentSystem);
}, []);

const system = useNetRequest("starmapSystem", {systemId: currentSystem});
const starmapEntities = useNetRequest("starmapSystemEntities", {
systemId: currentSystem,
Expand Down
115 changes: 114 additions & 1 deletion client/src/cards/Navigation/data.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,116 @@
import {matchSorter} from "match-sorter";
import {DataContext} from "server/src/utils/DataContext";
import {Entity} from "server/src/utils/ecs";
import {getOrbitPosition} from "server/src/utils/getOrbitPosition";
import {Vector3} from "three";

export const requests = {};
export const requests = {
navigationSearch: async (
context: DataContext,
params: {query: string},
publishParams: null
) => {
if (publishParams !== null) throw null;
const {query} = params;

// Get all of the planet, star, and solar system entities that match the query.
const matchItems = matchSorter(
context.flight?.ecs.entities
.filter(
e =>
e.components.isStar ||
e.components.isPlanet ||
e.components.isSolarSystem
)
.map(m => {
let position = m.components.position;
if (!position) {
const {x, y, z} = getCompletePositionFromOrbit(m);
const parentId = getObjectSystem(m)?.id || null;
position = {
x,
y,
z,
type: m.components.isSolarSystem ? "interstellar" : "solar",
parentId: m.components.isSolarSystem ? null : parentId,
};
}
return {
...m,
type: m.components.isSolarSystem
? "solar"
: m.components.isPlanet
? "planet"
: m.components.isShip
? "ship"
: "star",
name: m.components.identity!.name,
description: m.components.identity?.description,
temperature: m.components.temperature?.temperature,
spectralType: m.components.isStar?.spectralType,
classification: m.components.isPlanet?.classification,
mass:
m.components.isStar?.solarMass ||
m.components.isPlanet?.terranMass,
population: m.components.population?.count,
position,
} as const;
}) || [],
query,
{
keys: [
"name",
"description",
"temperature",
"spectralType",
"classification",
"mass",
"population",
],
}
).map(m => ({
// TODO Aug 1 2022 - Add in a distance calculation.
id: m.id,
name: m.name,
position: m.position,
type: m.type,
}));

return matchItems;
},
};

function getCompletePositionFromOrbit(object: Entity) {
const origin = new Vector3(0, 0, 0);
if (object.components.satellite) {
if (object.components.satellite.parentId) {
const parent = object.ecs?.entities.find(
e => e.id === object.components.satellite?.parentId
);
if (parent?.components?.satellite) {
const parentPosition = getOrbitPosition(parent.components.satellite);
origin.copy(parentPosition);
}
}
const position = getOrbitPosition({
...object.components.satellite,
origin,
});
return position;
}
return new Vector3();
}

function getObjectSystem(obj: Entity): Entity | null {
const objSystemId = obj.components.position?.parentId;
if (objSystemId) {
const parentObject = obj.ecs?.entities.find(e => e.id === objSystemId);
if (parentObject) return parentObject;
}

if (obj.components.isSolarSystem) return obj;
const parentObjId = obj.components?.satellite?.parentId;
const parent = obj.ecs?.entities.find(e => e.id === parentObjId);
if (!parent) return null;
return getObjectSystem(parent);
}
60 changes: 54 additions & 6 deletions client/src/cards/Navigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ import {CardProps} from "client/src/components/Station/CardProps";
import {MapControls} from "./MapControls";
import {InterstellarWrapper} from "./InterstellarWrapper";
import {SolarSystemWrapper} from "./SolarSystemWrapper";
import SearchableInput, {DefaultResultLabel} from "@thorium/ui/SearchableInput";
import {netRequest} from "client/src/context/useNetRequest";
import {capitalCase} from "change-case";

export function Navigation(props: CardProps) {
return (
<StarmapStoreProvider>
<div className="mx-auto h-full bg-black/70 border border-white/50 relative">
<CanvasWrapper shouldRender={props.cardLoaded} />
<div className="grid grid-cols-2 grid-rows-2 absolute inset-0 pointer-events-none p-4">
<Input
label="Search"
labelHidden
placeholder="Search..."
className="pointer-events-all max-w-sm"
/>
<div className="pointer-events-auto max-w-sm">
<StarmapSearch />
</div>
<div></div>
<MapControls />
</div>
Expand All @@ -31,6 +31,54 @@ export function Navigation(props: CardProps) {
);
}

function StarmapSearch() {
const useStarmapStore = useGetStarmapStore();
return (
<SearchableInput<{id: number; name: string; type: string; position: any}>
queryKey="nav"
getOptions={async ({queryKey, signal}) => {
const result = await netRequest(
"navigationSearch",
{query: queryKey[1]},
{signal}
);
return result;
}}
ResultLabel={({active, result, selected}) => (
<DefaultResultLabel active={active} selected={selected}>
<p>{result.name}</p>
<p>
<small>
{result.type === "solar"
? "Solar System"
: capitalCase(result.type)}
</small>
</p>
</DefaultResultLabel>
)}
setSelected={async item => {
if (!item) return;
if (
useStarmapStore.getState().currentSystem !== item?.position.parentId
) {
await useStarmapStore
.getState()
.setCurrentSystem(item?.position.parentId);
}
useStarmapStore.setState({selectedObjectId: item.id});
const controls = useStarmapStore.getState().cameraControls;
controls?.current?.moveTo(
item.position.x,
item.position.y,
item.position.z,
true
);
}}
placeholder="Search..."
/>
);
}

function CanvasWrapper({shouldRender}: {shouldRender: boolean}) {
const useStarmapStore = useGetStarmapStore();
const currentSystem = useStarmapStore(store => store.currentSystem);
Expand Down
18 changes: 14 additions & 4 deletions client/src/components/Starmap/starmapStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ interface StarmapStore {
setCameraView: (view: "2d" | "3d") => void;
/** Used for core */
currentSystem: number | null;
setCurrentSystem: (systemId: number | null) => void;
setCurrentSystem: (systemId: number | null) => Promise<void>;
currentSystemSet?: (value: any) => void;
cameraVerticalDistance: number;
cameraControls?: MutableRefObject<CameraControls | null>;
}
let storeCount = 0;
const createStarmapStore = () =>
create<StarmapStore>(set => ({
create<StarmapStore>((set, get) => ({
storeCount: storeCount++,
skyboxKey: "blank",
viewingMode: "editor",
Expand All @@ -40,8 +41,17 @@ const createStarmapStore = () =>
cameraView: "3d",
setCameraView: (view: "2d" | "3d") => set({cameraView: view}),
currentSystem: null,
setCurrentSystem: (systemId: number | null) =>
set({currentSystem: systemId}),
setCurrentSystem: async (systemId: number | null) => {
// Resolve the previous promise if it exists
if (get().currentSystemSet) {
get().currentSystemSet?.(null);
}

let currentSystemSet = (value: any) => {};
let promise = new Promise(res => (currentSystemSet = res));
set({currentSystemSet, currentSystem: systemId});
await promise;
},
cameraVerticalDistance: 0,
}));

Expand Down
Loading

0 comments on commit 75e6143

Please sign in to comment.