feat(ui): automation bar with recipe export import

This commit is contained in:
ginnoir
2026-06-12 00:57:42 -05:00
parent 29bc4d4bc5
commit ddbf9c0045
3 changed files with 107 additions and 0 deletions
+16
View File
@@ -148,6 +148,22 @@ export function ActionCard({ action }: ActionCardProps) {
) : null} ) : null}
</div> </div>
) : null} ) : null}
{action.automationUnlocked ? (
<button
type="button"
aria-label={`${action.inAutomationQueue ? 'Disable' : 'Enable'} automation for ${action.name}`}
aria-pressed={action.inAutomationQueue}
title={`${action.inAutomationQueue ? 'Disable' : 'Enable'} automation for ${action.name}`}
onClick={() => gameRuntime.toggleAutomation(action.id)}
className={`shrink-0 rounded-lg border px-2 text-xs font-semibold transition-colors ${
action.inAutomationQueue
? 'border-amber-500 bg-amber-500/15 text-amber-300 hover:bg-amber-500/20'
: 'border-slate-700 bg-slate-800/70 text-slate-400 hover:border-amber-500/60 hover:text-amber-300'
}`}
>
Auto
</button>
) : null}
</div> </div>
); );
} }
+88
View File
@@ -0,0 +1,88 @@
import { useState } from 'react';
import { gameRuntime } from '../state/runtime';
import { useGameStore } from '../state/store';
export function AutomationBar() {
const automationQueueIds = useGameStore((s) => s.automationQueueIds);
const automationQueueNames = useGameStore((s) => s.automationQueueNames);
const [importText, setImportText] = useState('');
async function copyRecipe() {
const text = gameRuntime.exportAutomationRecipe();
try {
await navigator.clipboard?.writeText(text);
return;
} catch {
// Fall through to the legacy selection path.
}
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', 'true');
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.append(textarea);
textarea.select();
document.execCommand('copy');
textarea.remove();
}
function importRecipeText() {
gameRuntime.importAutomationRecipe(importText);
setImportText('');
}
return (
<section
aria-label="Automation"
className="rounded-xl border border-slate-800 bg-slate-900/30 p-4"
>
<div className="mb-3 flex items-center justify-between gap-3">
<h3 className="font-semibold text-slate-400 text-xs uppercase tracking-wider">
Automation
</h3>
<button
type="button"
onClick={copyRecipe}
className="rounded border border-slate-700 px-2.5 py-1 text-slate-300 text-xs transition-colors hover:border-amber-500/60 hover:text-amber-300"
>
Copy recipe
</button>
</div>
{automationQueueNames.length > 0 ? (
<ol aria-label="Automation queue" className="mb-3 flex flex-col gap-1.5">
{automationQueueNames.map((name, index) => (
<li
// biome-ignore lint/suspicious/noArrayIndexKey: indices distinguish repeated automation entries in display order
key={`${automationQueueIds[index]}-${index}`}
className="rounded-lg border border-slate-700 bg-slate-900/60 px-3 py-2 text-slate-300 text-sm"
>
{index + 1}. {name}
</li>
))}
</ol>
) : (
<p className="mb-3 text-slate-500 text-sm">No automated processes.</p>
)}
<div className="flex flex-col gap-2">
<textarea
aria-label="Recipe text"
className="min-h-24 w-full resize-y rounded-lg border border-slate-700 bg-slate-950/70 p-2 text-slate-200 text-xs outline-none transition-colors placeholder:text-slate-600 focus:border-amber-500/70"
placeholder="Paste recipe text..."
value={importText}
onChange={(event) => setImportText(event.target.value)}
/>
<button
type="button"
disabled={importText.trim().length === 0}
onClick={importRecipeText}
className="self-start rounded border border-slate-700 px-3 py-1.5 text-slate-300 text-xs transition-colors hover:border-amber-500/60 hover:text-amber-300 disabled:cursor-not-allowed disabled:opacity-40"
>
Import recipe
</button>
</div>
</section>
);
}
+3
View File
@@ -1,6 +1,7 @@
import { gameRuntime } from '../state/runtime'; import { gameRuntime } from '../state/runtime';
import { useGameStore } from '../state/store'; import { useGameStore } from '../state/store';
import { ActionColumn } from './ActionColumn'; import { ActionColumn } from './ActionColumn';
import { AutomationBar } from './AutomationBar';
import { EventLog } from './EventLog'; import { EventLog } from './EventLog';
import { ResourceBar } from './ResourceBar'; import { ResourceBar } from './ResourceBar';
@@ -29,6 +30,8 @@ export function PlayPanel() {
))} ))}
</section> </section>
<AutomationBar />
{queuedActionNames.length > 0 ? ( {queuedActionNames.length > 0 ? (
<div className="border border-slate-800 bg-slate-900/30 rounded-xl p-4"> <div className="border border-slate-800 bg-slate-900/30 rounded-xl p-4">
<h3 className="font-semibold text-slate-400 text-xs uppercase tracking-wider mb-2"> <h3 className="font-semibold text-slate-400 text-xs uppercase tracking-wider mb-2">