Skip to content

Commit

Permalink
Merge pull request #632 from Thorium-Sim/develop
Browse files Browse the repository at this point in the history
Alpha 15
  • Loading branch information
alexanderson1993 authored Sep 6, 2024
2 parents 8d9061a + 24ceadb commit 7bcb1a8
Show file tree
Hide file tree
Showing 99 changed files with 4,908 additions and 3,684 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ coverage
.turbo

# Copied docs images
client/public/docs
client/public/docs
icons/name.d.ts
icons/sprite.svg
123 changes: 123 additions & 0 deletions client/app/cards/Objectives/core.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { q } from "@client/context/AppContext";
import Button from "@thorium/ui/Button";
import Checkbox from "@thorium/ui/Checkbox";
import Input from "@thorium/ui/Input";
import Select from "@thorium/ui/Select";
import { useState } from "react";

export function ObjectivesCore() {
const [objectives] = q.objectives.get.useNetRequest({});
const [adding, setAdding] = useState(false);
return (
<div className="flex flex-col gap-1 h-full">
<div className="flex-1 overflow-y-auto">
{objectives.map((objective) => (
<div
key={objective.id}
className="flex items-center gap-1 last-of-type:border-transparent border-b border-b-white/50 py-1"
>
<div className="flex-1">
<div className="font-bold">{objective.title}</div>
<div className="text-sm">{objective.description}</div>
</div>
<div>
<Select
label="State"
labelHidden
size="xs"
items={[
{ id: "active", label: "Active" },
{ id: "complete", label: "Complete" },
{ id: "cancelled", label: "Cancelled" },
]}
selected={objective.state}
setSelected={async (state) => {
if (Array.isArray(state)) return;
await q.objectives.setState.netSend({
objectiveId: objective.id,
state,
});
}}
/>
<Checkbox
label="Crew Complete"
checked={objective.crewComplete}
onChange={(e) =>
q.objectives.setCrewComplete.netSend({
objectiveId: objective.id,
crewComplete: e.target.checked,
})
}
/>
</div>
</div>
))}
</div>
{adding ? (
<form
onSubmit={(e) => {
e.preventDefault();
const form = e.currentTarget;
const title = form.objective.value;
const description = form.description.value;
const priority = Number(form.priority.value) || 1;

q.objectives.add.netSend({ title, description, priority });
e.currentTarget.reset();
setAdding(false);
}}
>
<div className="flex gap-1 flex-wrap items-start">
<div className="flex-1">
<Input
label="Title"
name="objective"
required
className="input-sm"
/>
</div>
<div className="flex-1">
<Input
as="textarea"
name="description"
label="Description"
className="input-sm"
/>
</div>
<div className="flex-1">
<Input
label="Priority"
className="input-sm"
name="priority"
type="number"
defaultValue={1}
/>
<p className="text-sm">Higher number = higher priority</p>
</div>
</div>
<div className="flex gap-1 flex-wrap">
<Button
type="reset"
className="flex-1 btn-xs btn-error"
onClick={() => setAdding(false)}
>
Cancel
</Button>
<Button type="submit" className="flex-1 btn-xs btn-success">
Add Objective
</Button>
</div>
</form>
) : (
<div>
<Button
className="btn-xs btn-success"
onClick={() => setAdding(true)}
>
Add Objective
</Button>
</div>
)}
</div>
);
}
146 changes: 146 additions & 0 deletions client/app/cards/Objectives/data.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { t } from "@server/init/t";
import { pubsub } from "@server/init/pubsub";
import { z } from "zod";
import { Entity } from "@server/utils/ecs";

export const objectives = t.router({
get: t.procedure
.input(z.object({ shipId: z.number().optional() }))
.filter((publish: { shipId: number }, { ctx, input }) => {
if (publish && (input.shipId || ctx.ship?.id) !== publish.shipId)
return false;
return true;
})
.request(({ ctx, input }) => {
const ship = input.shipId
? ctx.flight?.ecs.getEntityById(input.shipId)
: ctx.ship;
if (!ship) return [];

const objectives: {
id: number;
title: string;
description?: string;
state: "active" | "complete" | "cancelled";
crewComplete: boolean;
priority: number;
}[] = [];
const objectiveEntities =
ctx.flight?.ecs.componentCache.get("isObjective") || [];
for (const objective of objectiveEntities) {
if (
objective.components.isObjective?.shipId === ship.id &&
objective.components.identity
) {
const { state, crewComplete, priority } =
objective.components.isObjective;
const { name, description } = objective.components.identity;
objectives.push({
id: objective.id,
state,
crewComplete,
priority,
title: name,
description,
});
}
}
return objectives;
}),
add: t.procedure
.meta({ action: true, event: true })
.input(
z.object({
shipId: z.number().optional(),
title: z.string(),
description: z.string().optional(),
crewComplete: z.boolean().optional(),
priority: z.number().optional(),
}),
)
.send(({ ctx, input }) => {
const ship = input.shipId
? ctx.flight?.ecs.getEntityById(input.shipId)
: ctx.ship;
if (!ship) return;

const objective = new Entity();
objective.addComponent("isObjective", {
shipId: ship.id,
state: "active",
crewComplete: input.crewComplete || false,
priority: input.priority || 0,
});
objective.addComponent("identity", {
name: input.title,
description: input.description || "",
});
ctx.flight?.ecs.addEntity(objective);

pubsub.publish.objectives.get({ shipId: ship.id });
}),
setState: t.procedure
.meta({ action: true, event: true })
.input(
z.object({
objectiveId: z.number(),
state: z.enum(["active", "complete", "cancelled"]),
}),
)
.send(({ ctx, input }) => {
const objective = ctx.flight?.ecs.getEntityById(input.objectiveId);
if (!objective) return;

objective.updateComponent("isObjective", { state: input.state });

if (objective.components.isObjective?.shipId) {
pubsub.publish.objectives.get({
shipId: objective.components.isObjective.shipId,
});
}
}),
setCrewComplete: t.procedure
.meta({ action: true, event: true })
.input(
z.object({
objectiveId: z.number(),
crewComplete: z.boolean(),
}),
)
.send(({ ctx, input }) => {
const objective = ctx.flight?.ecs.getEntityById(input.objectiveId);
if (!objective) return;

objective.updateComponent("isObjective", {
crewComplete: input.crewComplete,
});

if (objective.components.isObjective?.shipId) {
pubsub.publish.objectives.get({
shipId: objective.components.isObjective.shipId,
});
}
}),
setPriority: t.procedure
.meta({ action: true, event: true })
.input(
z.object({
objectiveId: z.number(),
priority: z.number(),
}),
)
.send(({ ctx, input }) => {
const objective = ctx.flight?.ecs.getEntityById(input.objectiveId);
if (!objective) return;

objective.updateComponent("isObjective", {
priority: input.priority,
});

if (objective.components.isObjective?.shipId) {
pubsub.publish.objectives.get({
shipId: objective.components.isObjective.shipId,
});
}
}),
});
71 changes: 71 additions & 0 deletions client/app/cards/Objectives/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { q } from "@client/context/AppContext";
import { cn } from "@client/utils/cn";

export function Objectives() {
const [objectives] = q.objectives.get.useNetRequest({});
const sortedObjectives = objectives
.concat()
.sort((a, b) => {
if (a.state === "complete" && b.state !== "complete") return -1;
if (a.state !== "complete" && b.state === "complete") return 1;
if (a.priority > b.priority) return -1;
if (a.priority < b.priority) return 1;
return 0;
})
.reverse();
return (
<div>
<h1 className="font-black text-lg @2xl:text-4xl">Mission Objectives</h1>
<div className="@2xl:panel @2xl:panel-alert flex flex-col h-full max-w-screen-md mx-auto gap-4 @2xl:p-4">
{sortedObjectives.map((objective) => (
<div
key={objective.id}
className={cn("flex items-start gap-2", {
"text-white/70":
objective.state === "cancelled" ||
objective.state === "complete",
})}
>
<button
type="button"
className={cn(
"mt-1 cursor-default flex items-center justify-center w-8 h-8 @2xl:w-10 @2xl:h-10 rounded-full border border-white",
{
"border-red-500 border-2": objective.state === "cancelled",
"relative after:block after:absolute after:inset-1 after:bg-white/40 after:hover:bg-white/60 cursor-pointer after:rounded-full":
objective.crewComplete,
"border-green-500 border-2 after:hidden":
objective.state === "complete",
},
)}
onClick={
objective.crewComplete && objective.state !== "cancelled"
? () =>
q.objectives.setState.netSend({
objectiveId: objective.id,
state:
objective.state === "active" ? "complete" : "active",
})
: () => null
}
>
{objective.state === "complete" ? (
<div className="w-6 @2xl:w-8 aspect-square rounded-full bg-green-500" />
) : null}
</button>
<div className="flex-1">
<h3
className={cn("@2xl:text-lg font-bold", {
"line-through": objective.state === "cancelled",
})}
>
{objective.title}
</h3>
<p className="text-sm @2xl:text-base">{objective.description}</p>
</div>
</div>
))}
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion client/app/cards/Pilot/CircleGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
UNSAFE_NavigationContext,
UNSAFE_RouteContext,
} from "react-router-dom";
import { CircleGeometry, type Group, type OrthographicCamera } from "three";
import type { Group, OrthographicCamera } from "three";
import { cameraQuaternionMultiplier, forwardQuaternion } from "./constants";
import { DistanceCircle } from "./DistanceCircle";
import { PlayerArrow } from "./PlayerArrow";
Expand Down
Loading

0 comments on commit 7bcb1a8

Please sign in to comment.