Skip to content
Merged
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
11 changes: 10 additions & 1 deletion src/factories/Factory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import type { JSONContent } from '@tiptap/react';
import type { LogisticType } from '@/recipes/logistics/LogisticTypes';

export type FactoryProgressStatus = 'draft' | 'todo' | 'in_progress' | 'done';
export type FactoryProgressStatus =
| 'draft'
| 'todo'
| 'in_progress'
| 'done'
| 'disabled';

export const isFactoryDisabled = (
factory: Pick<Factory, 'progress'> | null | undefined,
) => factory?.progress === 'disabled';

export interface Factory {
id: string;
Expand Down
15 changes: 11 additions & 4 deletions src/factories/charts/graph/useFactoriesGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@ export function useFactoriesGraph() {
const nodes: Node<IFactoryNodeData>[] = [];
const edges: Edge<IInputEdgeData>[] = [];

const disabledIds = new Set(
factories.filter(f => f?.progress === 'disabled').map(f => f!.id),
);

const maxInputAmount =
max(
factories.flatMap(
factory => factory?.inputs?.map(input => input.amount ?? 0) ?? [],
),
factories
.filter(f => f && f.progress !== 'disabled')
.flatMap(
factory => factory?.inputs?.map(input => input.amount ?? 0) ?? [],
),
) ?? 1;

for (const factory of factories) {
if (!factory) continue;
if (!factory || factory.progress === 'disabled') continue;

nodes.push({
id: factory.id,
Expand All @@ -38,6 +44,7 @@ export function useFactoriesGraph() {
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i];
if (!input.factoryId) continue;
if (disabledIds.has(input.factoryId)) continue;

edges.push({
id: `${factory.id}-i${i}`,
Expand Down
33 changes: 21 additions & 12 deletions src/factories/charts/sankey/FactoriesSankeyChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ export function FactoriesSankeyChart(props: IFactoriesSankeyChartProps) {
nodes: SankeyNode[];
links: SankeyLink[];
} = useMemo(() => {
const disabledIds = new Set(
factories.filter(f => f.progress === 'disabled').map(f => f.id),
);

const nodes: SankeyNode[] = factories
.filter(f => f.name)
.filter(f => f.name && f.progress !== 'disabled')
.map(f => ({
id: f.name!,
_originalId: f.id,
Expand All @@ -55,17 +59,22 @@ export function FactoriesSankeyChart(props: IFactoriesSankeyChartProps) {
_originalId: 'WORLD',
});

const links: SankeyLink[] = factories.flatMap(target => {
return (target.inputs ?? [])
.filter(i => i.factoryId && target.name)
.map(input => ({
source: nodes.find(n => n._originalId === input.factoryId)?.id ?? '',
target: target.name!,
value: input.amount ?? 0,
resourceLabel: getResourceName(input.resource ?? ''),
}))
.filter(l => l.source !== l.target);
});
const links: SankeyLink[] = factories
.filter(target => target.progress !== 'disabled')
.flatMap(target => {
return (target.inputs ?? [])
.filter(
i => i.factoryId && target.name && !disabledIds.has(i.factoryId),
)
.map(input => ({
source:
nodes.find(n => n._originalId === input.factoryId)?.id ?? '',
target: target.name!,
value: input.amount ?? 0,
resourceLabel: getResourceName(input.resource ?? ''),
}))
.filter(l => l.source !== l.target);
});

return { nodes, links };
}, [factories]);
Expand Down
8 changes: 8 additions & 0 deletions src/factories/components/peek/OutputDependenciesPeekModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ActionIcon, Modal, Tooltip } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconEye } from '@tabler/icons-react';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import type { FactoryOutput } from '@/factories/Factory';
import { OutputDependenciesTable } from './OutputDependenciesTable';

Expand All @@ -13,6 +15,12 @@ export function OutputDependenciesPeekModal(
props: IOutputDependenciesPeekModalProps,
) {
const [opened, { open, close }] = useDisclosure(false);
const { pathname } = useLocation();

// biome-ignore lint/correctness/useExhaustiveDependencies: close modal on route change
useEffect(() => {
close();
}, [pathname]);

return (
<div>
Expand Down
10 changes: 6 additions & 4 deletions src/factories/components/peek/OutputDependenciesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ export function OutputDependenciesTable(props: IOutputDependenciesTableProps) {
const factoriesUsingOutput = useShallowStore(state =>
state.games.games[state.games.selected ?? '']?.factoriesIds
.map(id => state.factories.factories[id])
.filter(factory =>
factory?.inputs?.some(
i => i.resource === output.resource && i.factoryId === factoryId,
),
.filter(
factory =>
factory?.progress !== 'disabled' &&
factory?.inputs?.some(
i => i.resource === output.resource && i.factoryId === factoryId,
),
),
);

Expand Down
6 changes: 6 additions & 0 deletions src/factories/components/progressProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { MantineColor } from '@mantine/core';
import {
IconCircle,
IconCircleCheck,
IconPlayerPause,
IconProgress,
IconProgressHelp,
type IconProps,
Expand Down Expand Up @@ -37,4 +38,9 @@ export const progressProperties: Record<
label: 'Done',
Icon: IconCircleCheck,
},
disabled: {
color: 'red',
label: 'Disabled',
Icon: IconPlayerPause,
},
};
27 changes: 16 additions & 11 deletions src/factories/components/usage/useOutputUsage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,26 @@ import type { IFactoryUsageProps } from './FactoryUsage';
export function useOutputUsage(
options: Pick<IFactoryUsageProps, 'factoryId' | 'output'>,
) {
const producedAmount = useStore(state =>
options.factoryId === WORLD_SOURCE_ID
? getWorldResourceMax(options.output)
: Math.max(
state.factories.factories[options.factoryId ?? '']?.outputs
?.filter(o => o?.resource === options.output)
.reduce((sum, o) => sum + (o?.amount ?? 0), 0) ?? 0,
0,
),
);
const producedAmount = useStore(state => {
if (options.factoryId === WORLD_SOURCE_ID) {
return getWorldResourceMax(options.output);
}
const source = state.factories.factories[options.factoryId ?? ''];
if (source?.progress === 'disabled') return 0;
return Math.max(
source?.outputs
?.filter(o => o?.resource === options.output)
.reduce((sum, o) => sum + (o?.amount ?? 0), 0) ?? 0,
0,
);
});

const usedAmount = useStore(
state =>
state.games.games[state.games.selected ?? '']?.factoriesIds
.flatMap(id => state.factories.factories[id]?.inputs)
.map(id => state.factories.factories[id])
.filter(f => f && f.progress !== 'disabled')
.flatMap(f => f!.inputs)
.filter(
i =>
i?.resource === options.output &&
Expand Down
88 changes: 68 additions & 20 deletions src/factories/details/ProductionView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type Path, setByPath } from '@clickbar/dot-diver';
import {
ActionIcon,
Alert,
Button,
Container,
Expand All @@ -10,7 +11,12 @@
Text,
TextInput,
} from '@mantine/core';
import { IconBulb, IconCalculator } from '@tabler/icons-react';
import {
IconBulb,
IconCalculator,
IconPlayerPause,
IconX,
} from '@tabler/icons-react';
import { useCallback } from 'react';
import { Link } from 'react-router-dom';
import { useFormOnChange } from '@/core/form/useFormOnChange';
Expand Down Expand Up @@ -46,6 +52,10 @@
value: 'done',
label: 'Done',
},
{
value: 'disabled',
label: 'Disabled',
},
];

export const ProductionView = ({ id }: { id: string }) => {
Expand All @@ -63,6 +73,9 @@
const hasSolverLayout = useStore(
state => !!state.solvers.instances[id]?.layout,
);
const readyToPlanHintDismissed = useStore(
state => state.factoryView.readyToPlanHintDismissed ?? false,
);
const hasConfiguredOutputs = outputs.some(
o => o.resource != null && (o.amount ?? 0) > 0,
);
Expand All @@ -72,36 +85,71 @@
<Container size="lg" data-tutorial-id="factory-detail">
<Group gap="xl" align="start" py="xl">
<Stack gap="lg" style={{ flexGrow: 1 }}>
{hasConfiguredOutputs && !hasSolverLayout && (
{factory.progress === 'disabled' && (
<Alert
icon={<IconBulb size={18} />}
color="cyan"
icon={<IconPlayerPause size={18} />}
color="red"
variant="light"
title="Ready to plan?"
title="Factory disabled"
>
<Group gap="xs" align="center">
<Text size="sm">
Use the Calculator to compute your optimal production chain.
</Text>
<Button
component={Link}
to={`/factories/${id}/calculator`}
size="xs"
color="cyan"
variant="filled"
leftSection={<IconCalculator size={14} />}
>
Open Calculator
</Button>
</Group>
This factory is disabled. Its inputs and outputs are excluded from
global usage totals, dependency tables, and charts. Change the
Progress to re-enable it.
</Alert>
)}
{hasConfiguredOutputs &&
!hasSolverLayout &&
!readyToPlanHintDismissed && (
<Alert
icon={<IconBulb size={18} />}
color="cyan"
variant="light"
title="Ready to plan?"
withCloseButton={false}
>
<Group
gap="xs"
align="center"
justify="space-between"
wrap="nowrap"
>
<Group gap="xs" align="center">
<Text size="sm">
Use the Calculator to compute your optimal production
chain.
</Text>
<Button
component={Link}
to={`/factories/${id}/calculator`}
size="xs"
color="cyan"
variant="filled"
leftSection={<IconCalculator size={14} />}
>
Open Calculator
</Button>
</Group>
<ActionIcon
variant="subtle"
color="cyan"
aria-label="Dismiss"
onClick={() =>
useStore.getState().updateFactoryView(s => {
s.readyToPlanHintDismissed = true;
})
}
>
<IconX size={16} />
</ActionIcon>
</Group>
</Alert>
)}
<Stack gap="sm" data-tutorial-id="factory-inputs">
<Text size="lg">Inputs</Text>
<Stack gap="xs">
{inputs?.map((input, i) => (
<FactoryInputRow
key={i}

Check warning on line 152 in src/factories/details/ProductionView.tsx

View workflow job for this annotation

GitHub Actions / build

lint/suspicious/noArrayIndexKey

Avoid using the index of an array as key property in an element.
index={i}
input={input}
factoryId={id!}
Expand Down Expand Up @@ -129,7 +177,7 @@
<Stack gap="xs">
{outputs?.map((output, i) => (
<FactoryOutputRow
key={i}

Check warning on line 180 in src/factories/details/ProductionView.tsx

View workflow job for this annotation

GitHub Actions / build

lint/suspicious/noArrayIndexKey

Avoid using the index of an array as key property in an element.
index={i}
output={output}
factoryId={id!}
Expand Down
9 changes: 8 additions & 1 deletion src/factories/list/FactoryGridCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@
}

return (
<Card key={id} withBorder component={Link} to={id} className={classes.card}>
<Card
key={id}
withBorder
component={Link}
to={id}
className={classes.card}
style={{ opacity: factory.progress === 'disabled' ? 0.55 : 1 }}
>
<Group justify="space-between" align="center" wrap="nowrap">
<Stack
justify="space-between"
Expand All @@ -57,7 +64,7 @@
className={classes.outputsList}
>
{factory.outputs.map((output, outputIndex) => (
<Group gap={6} wrap={'nowrap'} key={outputIndex}>

Check warning on line 67 in src/factories/list/FactoryGridCard.tsx

View workflow job for this annotation

GitHub Actions / build

lint/suspicious/noArrayIndexKey

Avoid using the index of an array as key property in an element.
<FactoryItemImage id={output.resource} size={24} />
<Text size="xs">&times;</Text>
<Text size="xs">{output.amount}</Text>
Expand Down
6 changes: 5 additions & 1 deletion src/factories/list/FactoryRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@
}

return (
<Card key={id} withBorder>
<Card
key={id}
withBorder
style={{ opacity: factory.progress === 'disabled' ? 0.55 : 1 }}
>
<Group gap="sm" align="flex-start" justify="space-between">
<Group gap="sm" align="flex-start">
<Group gap={2}>
Expand All @@ -82,7 +86,7 @@
{(factory.outputs ?? [{ resource: null, amount: null }]).map(
(output, index) => (
<FactoryOutputRow
key={index}

Check warning on line 89 in src/factories/list/FactoryRow.tsx

View workflow job for this annotation

GitHub Actions / build

lint/suspicious/noArrayIndexKey

Avoid using the index of an array as key property in an element.
index={index}
output={output}
factoryId={factory.id}
Expand Down Expand Up @@ -145,7 +149,7 @@
<Stack gap="xs">
{factory.inputs?.map((input, index) => (
<FactoryInputRow
key={index}

Check warning on line 152 in src/factories/list/FactoryRow.tsx

View workflow job for this annotation

GitHub Actions / build

lint/suspicious/noArrayIndexKey

Avoid using the index of an array as key property in an element.
index={index}
input={input}
factoryId={factory.id}
Expand Down
2 changes: 2 additions & 0 deletions src/factories/store/factoryViewSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface FactoryViewSlice {
filterResource: string | null;
sortBy: 'name';
viewMode: 'spreadsheet' | 'kanban' | 'grid';
readyToPlanHintDismissed?: boolean;
}

export const factoryViewSlice = createSlice({
Expand All @@ -15,6 +16,7 @@ export const factoryViewSlice = createSlice({
filterResource: null,
sortBy: 'name',
viewMode: 'spreadsheet',
readyToPlanHintDismissed: false,
} as FactoryViewSlice,
actions: {
updateFactoryView:
Expand Down
2 changes: 1 addition & 1 deletion src/tutorial/chapters/factoryBasicsChapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const factoryBasicsChapter: TutorialChapter = {
element: '[data-tutorial-id="factory-properties"]',
popover: {
title: 'Name & build status',
description: `Every factory has a name and a build status. I named this one "${DEMO_NAME}" and set its status to “Todo”. The status tracks whether a factory is just planned, being built, or already running, and it is also what powers the columns in the Kanban view of the Factories list.`,
description: `Every factory has a name and a build status. I named this one "${DEMO_NAME}" and set its status to “Todo”. The status tracks whether a factory is just planned, being built, or already running, and it is also what powers the columns in the Kanban view of the Factories list. Set a factory to “Disabled” to temporarily power it down — it stays here but is excluded from global usage totals and charts, and is hidden from the Kanban board.`,
side: 'left',
},
onHighlightStarted: () => {
Expand Down
Loading