Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions src/components/Factory/Canvas/GameCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import {
useNodesState,
} from "@xyflow/react";
import type { ComponentType, DragEvent } from "react";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";

import { BlockStack } from "@/components/ui/layout";

import { setup } from "../data/setup";
import { useGlobalResources } from "../providers/GlobalResourcesProvider";
import { processDay } from "../simulation/processDay";
import type { DayStatistics } from "../types/statistics";
import { createIsValidConnection } from "./callbacks/isValidConnection";
import { createOnConnect } from "./callbacks/onConnect";
import { createOnDrop } from "./callbacks/onDrop";
Expand All @@ -33,33 +35,58 @@ const edgeTypes: Record<string, ComponentType<any>> = {
};

interface GameCanvasProps extends ReactFlowProps {
onDayAdvance?: (globalOutputs: { coins: number; knowledge: number }) => void;
onDayAdvance?: (
globalOutputs: Record<string, number>,
statistics: DayStatistics,
) => void;
triggerAdvance?: number;
currentDay: number;
}

const GameCanvas = ({
children,
onDayAdvance,
triggerAdvance,
currentDay,
...rest
}: GameCanvasProps) => {
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
const [reactFlowInstance, setReactFlowInstance] =
useState<ReactFlowInstance>();

const { resources, updateResources } = useGlobalResources();
const prevTriggerRef = useRef(0);

useEffect(() => {
setNodes(setup.buildings);
}, [setNodes]);

// Process day advancement
useEffect(() => {
if (triggerAdvance === undefined || triggerAdvance === 0) return;
if (triggerAdvance === prevTriggerRef.current) return;

prevTriggerRef.current = triggerAdvance;

const { updatedNodes, globalOutputs, statistics } = processDay(
nodes,
edges,
currentDay,
resources,
);

const { updatedNodes, globalOutputs } = processDay(nodes, edges);
setNodes(updatedNodes);
onDayAdvance?.(globalOutputs);
}, [triggerAdvance]);
updateResources(globalOutputs);
onDayAdvance?.(globalOutputs, statistics);
}, [
triggerAdvance,
currentDay,
resources,
onDayAdvance,
setNodes,
updateResources,
]);

const onInit: OnInit = (instance) => {
setReactFlowInstance(instance);
Expand All @@ -70,14 +97,6 @@ const GameCanvas = ({
const onDrop = createOnDrop(reactFlowInstance, setNodes);
const isValidConnection = createIsValidConnection(edges);

const onNodesDelete = (deleted: Node[]) => {
console.log("Nodes deleted:", deleted);
};

const onEdgesDelete = (deleted: Edge[]) => {
console.log("Edges deleted:", deleted);
};

const onDragOver = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
Expand All @@ -92,8 +111,6 @@ const GameCanvas = ({
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodesDelete={onNodesDelete}
onEdgesDelete={onEdgesDelete}
onInit={onInit}
onDragOver={onDragOver}
onDrop={onDrop}
Expand Down
42 changes: 25 additions & 17 deletions src/components/Factory/Canvas/Nodes/Building.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
useReactFlow,
useUpdateNodeInternals,
} from "@xyflow/react";
import { useEffect, useMemo } from "react";
import { useEffect } from "react";

import { cn } from "@/lib/utils";
import { useContextPanel } from "@/providers/ContextPanelProvider";

import BuildingContext from "../../Context/BuildingContext";
import { RESOURCES } from "../../data/resources";
import { isBuildingData } from "../../types/buildings";
import { rotateBuilding } from "../../utils/rotation";
import BuildingInput from "../Handles/BuildingInput";
Expand Down Expand Up @@ -50,7 +51,7 @@

if (!isBuildingData(currentData)) return;

setContent(<BuildingContext building={currentData} />);
setContent(<BuildingContext building={currentData} nodeId={id} />);
setContextPanelOpen(true);
}

Expand Down Expand Up @@ -81,21 +82,8 @@
const { icon, name, description, color, inputs = [], outputs = [] } = data;

// Calculate position counts
const inputCounts = useMemo(() => {
const counts: Record<string, number> = {};
inputs.forEach((input) => {
counts[input.position] = (counts[input.position] || 0) + 1;
});
return counts;
}, [inputs]);

const outputCounts = useMemo(() => {
const counts: Record<string, number> = {};
outputs.forEach((output) => {
counts[output.position] = (counts[output.position] || 0) + 1;
});
return counts;
}, [outputs]);
const inputCounts = countBuildingIO(inputs);
const outputCounts = countBuildingIO(outputs);

// Track index at each position
const inputIndexAtPosition: Record<string, number> = {};
Expand All @@ -106,6 +94,11 @@
className={cn("bg-white rounded-lg", selected && "ring-2 ring-selected")}
>
{inputs.map((input, globalIndex) => {
if (!input.position) return null;

const isGlobal = RESOURCES[input.resource]?.global;
if (isGlobal) return;

const posIndex = inputIndexAtPosition[input.position] || 0;
inputIndexAtPosition[input.position] = posIndex + 1;

Expand Down Expand Up @@ -135,6 +128,11 @@
</div>

{outputs.map((output, globalIndex) => {
if (!output.position) return null;

const isGlobal = RESOURCES[output.resource]?.global;
if (isGlobal) return;

const posIndex = outputIndexAtPosition[output.position] || 0;
outputIndexAtPosition[output.position] = posIndex + 1;

Expand All @@ -155,3 +153,13 @@
};

export default Building;

function countBuildingIO(ios: (BuildingInput | BuildingOutput)[]) {

Check failure on line 157 in src/components/Factory/Canvas/Nodes/Building.tsx

View workflow job for this annotation

GitHub Actions / Type checking

'BuildingOutput' refers to a value, but is being used as a type here. Did you mean 'typeof BuildingOutput'?

Check failure on line 157 in src/components/Factory/Canvas/Nodes/Building.tsx

View workflow job for this annotation

GitHub Actions / Type checking

'BuildingInput' refers to a value, but is being used as a type here. Did you mean 'typeof BuildingInput'?

Check failure on line 157 in src/components/Factory/Canvas/Nodes/Building.tsx

View workflow job for this annotation

GitHub Actions / Type checking

'BuildingOutput' refers to a value, but is being used as a type here. Did you mean 'typeof BuildingOutput'?

Check failure on line 157 in src/components/Factory/Canvas/Nodes/Building.tsx

View workflow job for this annotation

GitHub Actions / Type checking

'BuildingInput' refers to a value, but is being used as a type here. Did you mean 'typeof BuildingInput'?

Check failure on line 157 in src/components/Factory/Canvas/Nodes/Building.tsx

View workflow job for this annotation

GitHub Actions / Type checking

'BuildingOutput' refers to a value, but is being used as a type here. Did you mean 'typeof BuildingOutput'?

Check failure on line 157 in src/components/Factory/Canvas/Nodes/Building.tsx

View workflow job for this annotation

GitHub Actions / Type checking

'BuildingInput' refers to a value, but is being used as a type here. Did you mean 'typeof BuildingInput'?
const counts: Record<string, number> = {};
ios.forEach((io) => {
if (!io.position) return;

counts[io.position] = (counts[io.position] || 0) + 1;
});
return counts;
}
29 changes: 29 additions & 0 deletions src/components/Factory/Context/Building/BuildingDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Text } from "@/components/ui/typography";

import { RESOURCES } from "../../data/resources";

interface BuildingDescriptionProps {
description: string;
cost?: number;
}

export const BuildingDescription = ({
description,
cost,
}: BuildingDescriptionProps) => {
return (
<BlockStack gap="2">
<Text size="sm" tone="subdued">
{description}
</Text>

{cost !== undefined && (
<InlineStack gap="2" align="center">
{RESOURCES.money.icon}
<Text size="sm">Cost: {cost}</Text>
</InlineStack>
)}
</BlockStack>
);
};
28 changes: 28 additions & 0 deletions src/components/Factory/Context/Building/ConnectionsSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { BlockStack } from "@/components/ui/layout";
import { Text } from "@/components/ui/typography";

interface ConnectionsSectionProps {
inputCount: number;
outputCount: number;
}

export const ConnectionsSection = ({
inputCount,
outputCount,
}: ConnectionsSectionProps) => {
return (
<BlockStack gap="2">
<Text size="sm" weight="semibold">
Connections
</Text>
<BlockStack gap="1">
<Text size="xs" tone="subdued">
Inputs: {inputCount}
</Text>
<Text size="xs" tone="subdued">
Outputs: {outputCount}
</Text>
</BlockStack>
</BlockStack>
);
};
132 changes: 132 additions & 0 deletions src/components/Factory/Context/Building/ProductionMethodSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Icon } from "@/components/ui/icon";
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Progress } from "@/components/ui/progress";
import { Text } from "@/components/ui/typography";
import { pluralize } from "@/utils/string";

import { RESOURCES } from "../../data/resources";
import type { ProductionMethod, ProductionState } from "../../types/buildings";

interface ProductionMethodSectionProps {
productionMethod?: ProductionMethod;
productionState?: ProductionState;
}

export const ProductionMethodSection = ({
productionMethod,
productionState,
}: ProductionMethodSectionProps) => {
if (!productionMethod) {
return (
<BlockStack gap="2">
<Text size="sm" weight="semibold">
Production Method
</Text>
<Text size="sm" tone="subdued">
No production method defined
</Text>
</BlockStack>
);
}

const progressPercentage = productionState
? (productionState.progress / productionMethod.days) * 100
: 0;

return (
<BlockStack gap="3">
<Text size="sm" weight="semibold">
Production Method
</Text>

{productionMethod.name && (
<InlineStack gap="2">
<Icon name="Bookmark" size="sm" />
<Text size="sm">{productionMethod.name}</Text>
</InlineStack>
)}

<BlockStack gap="2">
{/* Inputs */}
{productionMethod.inputs.length > 0 && (
<BlockStack gap="1">
<Text size="xs" tone="subdued">
Inputs:
</Text>
{productionMethod.inputs.map((input, idx) => (
<InlineStack key={idx} gap="2">
{input.resource === "any" ? (
<Text size="sm">• any</Text>
) : (
<>
<Text size="sm">
• {input.amount}x {input.resource}
</Text>
<Text size="xs" tone="subdued">
({RESOURCES.money.icon}{" "}
{input.amount * RESOURCES[input.resource].value})
</Text>
</>
)}
</InlineStack>
))}
</BlockStack>
)}

{/* Outputs */}
{productionMethod.outputs.length > 0 && (
<BlockStack gap="1">
<Text size="xs" tone="subdued">
Outputs:
</Text>
{productionMethod.outputs.map((output, idx) => {
if (RESOURCES[output.resource].global) {
return (
<InlineStack key={idx} gap="2">
<Text size="sm">
{RESOURCES[output.resource].icon} {output.resource}
</Text>
</InlineStack>
);
}

return (
<InlineStack key={idx} gap="2">
<Text size="sm">
• {output.amount}x {output.resource}
</Text>
<Text size="xs" tone="subdued">
({RESOURCES.money.icon}{" "}
{output.amount * RESOURCES[output.resource].value})
</Text>
</InlineStack>
);
})}
</BlockStack>
)}

{/* Duration */}
<InlineStack gap="2">
<Icon name="Clock" size="sm" />
<Text size="sm">{`${productionMethod.days} ${pluralize(productionMethod.days, "day")}`}</Text>
</InlineStack>

{/* Progress */}
{productionState && (
<BlockStack gap="1">
<Text size="xs" tone="subdued">
{productionState.status === "idle" && "Idle"}
{productionState.status === "active" &&
`Progress: ${productionState.progress} / ${productionMethod.days} ${pluralize(productionMethod.days, "day")}`}
{productionState.status === "paused" &&
`Paused: ${productionState.progress} / ${productionMethod.days} ${pluralize(productionMethod.days, "day")}`}
{productionState.status === "complete" &&
`Complete: ${productionState.progress} / ${productionMethod.days} ${pluralize(productionMethod.days, "day")}`}
</Text>
<Progress value={progressPercentage} className="h-2" />
</BlockStack>
)}
</BlockStack>
</BlockStack>
);
};
Loading
Loading