209 lines
6.6 KiB
TypeScript
209 lines
6.6 KiB
TypeScript
import { useMemo } from "react";
|
|
import {
|
|
GffEditor,
|
|
GffFieldType,
|
|
getFieldValue,
|
|
getLocStringText,
|
|
type FieldOverrideProps,
|
|
} from "./GffEditor";
|
|
|
|
interface ItemEditorProps {
|
|
repo: string;
|
|
filePath: string;
|
|
content: string;
|
|
onSave?: (content: string) => void;
|
|
onSwitchToRaw?: () => void;
|
|
}
|
|
|
|
function BaseItemOverride({ value, onChange, field }: FieldOverrideProps) {
|
|
const num = typeof value === "number" ? value : 0;
|
|
return (
|
|
<div className="flex items-center gap-3">
|
|
<label className="w-44 shrink-0 text-sm" style={{ color: "var(--forge-text-secondary)" }}>
|
|
{field.displayName}
|
|
</label>
|
|
<input
|
|
type="number"
|
|
value={num}
|
|
onChange={(e) => onChange(field.label, parseInt(e.target.value, 10))}
|
|
className="w-24 rounded border px-2 py-1.5 text-sm"
|
|
style={{
|
|
backgroundColor: "var(--forge-bg)",
|
|
borderColor: "var(--forge-border)",
|
|
color: "var(--forge-text)",
|
|
}}
|
|
/>
|
|
<span className="text-xs" style={{ color: "var(--forge-text-secondary)" }}>
|
|
(baseitems.2da row)
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function CompactNumbersOverride({ value, onChange, field, data }: FieldOverrideProps) {
|
|
const stackSize = typeof getFieldValue(data, "StackSize") === "number"
|
|
? (getFieldValue(data, "StackSize") as number) : 0;
|
|
const cost = typeof getFieldValue(data, "Cost") === "number"
|
|
? (getFieldValue(data, "Cost") as number) : 0;
|
|
const charges = typeof getFieldValue(data, "Charges") === "number"
|
|
? (getFieldValue(data, "Charges") as number) : 0;
|
|
|
|
if (field.label !== "StackSize") return null;
|
|
|
|
return (
|
|
<div className="flex items-center gap-4">
|
|
{[
|
|
{ label: "StackSize", display: "Stack", value: stackSize, max: 99 },
|
|
{ label: "Cost", display: "Cost (gp)", value: cost, max: 999999 },
|
|
{ label: "Charges", display: "Charges", value: charges, max: 255 },
|
|
].map((item) => (
|
|
<div key={item.label} className="flex items-center gap-2">
|
|
<label className="text-sm" style={{ color: "var(--forge-text-secondary)" }}>
|
|
{item.display}
|
|
</label>
|
|
<input
|
|
type="number"
|
|
value={item.value}
|
|
min={0}
|
|
max={item.max}
|
|
onChange={(e) => onChange(item.label, parseInt(e.target.value, 10))}
|
|
className="w-24 rounded border px-2 py-1.5 text-sm"
|
|
style={{
|
|
backgroundColor: "var(--forge-bg)",
|
|
borderColor: "var(--forge-border)",
|
|
color: "var(--forge-text)",
|
|
}}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function BooleanFlagsOverride({ data, onChange }: FieldOverrideProps) {
|
|
const flags = [
|
|
{ label: "Identified", display: "Identified" },
|
|
{ label: "Plot", display: "Plot" },
|
|
{ label: "Stolen", display: "Stolen" },
|
|
{ label: "Cursed", display: "Cursed" },
|
|
];
|
|
|
|
return (
|
|
<div className="flex items-center gap-6">
|
|
{flags.map((flag) => {
|
|
const val = getFieldValue(data, flag.label);
|
|
const checked = typeof val === "number" ? val !== 0 : Boolean(val);
|
|
return (
|
|
<label key={flag.label} className="flex cursor-pointer items-center gap-2 text-sm">
|
|
<input
|
|
type="checkbox"
|
|
checked={checked}
|
|
onChange={(e) => onChange(flag.label, e.target.checked ? 1 : 0)}
|
|
className="h-4 w-4 rounded"
|
|
style={{ accentColor: "var(--forge-accent)" }}
|
|
/>
|
|
<span style={{ color: "var(--forge-text)" }}>{flag.display}</span>
|
|
</label>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PropertiesListOverride({ value }: FieldOverrideProps) {
|
|
const list = Array.isArray(value) ? value : [];
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-sm" style={{ color: "var(--forge-text)" }}>
|
|
Item Properties
|
|
</span>
|
|
<div className="flex gap-2">
|
|
<button
|
|
className="rounded px-2 py-1 text-xs"
|
|
style={{ backgroundColor: "var(--forge-bg)", color: "var(--forge-text-secondary)" }}
|
|
>
|
|
+ Add Property
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{list.map((prop, i) => (
|
|
<div
|
|
key={i}
|
|
className="flex items-center gap-2 rounded border px-3 py-2"
|
|
style={{ borderColor: "var(--forge-border)", backgroundColor: "var(--forge-bg)" }}
|
|
>
|
|
<span className="flex-1 font-mono text-xs" style={{ color: "var(--forge-text)" }}>
|
|
{typeof prop === "object" && prop !== null
|
|
? JSON.stringify(prop).slice(0, 80)
|
|
: String(prop)}
|
|
</span>
|
|
<button
|
|
className="text-xs"
|
|
style={{ color: "#ef4444" }}
|
|
>
|
|
Remove
|
|
</button>
|
|
</div>
|
|
))}
|
|
{list.length === 0 && (
|
|
<p className="py-2 text-xs" style={{ color: "var(--forge-text-secondary)" }}>
|
|
No item properties
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function ItemEditor({ repo, filePath, content, onSave, onSwitchToRaw }: ItemEditorProps) {
|
|
const fieldOverrides = useMemo(() => {
|
|
const overrides = new Map<string, (props: FieldOverrideProps) => React.ReactNode>();
|
|
|
|
overrides.set("BaseItem", (props) => <BaseItemOverride {...props} />);
|
|
overrides.set("StackSize", (props) => <CompactNumbersOverride {...props} />);
|
|
overrides.set("Cost", () => null);
|
|
overrides.set("Charges", () => null);
|
|
overrides.set("Identified", (props) => <BooleanFlagsOverride {...props} />);
|
|
overrides.set("Plot", () => null);
|
|
overrides.set("Stolen", () => null);
|
|
overrides.set("Cursed", () => null);
|
|
overrides.set("PropertiesList", (props) => <PropertiesListOverride {...props} />);
|
|
|
|
return overrides;
|
|
}, []);
|
|
|
|
const itemName = useMemo(() => {
|
|
try {
|
|
const data = JSON.parse(content);
|
|
const nameField = data.LocalizedName;
|
|
return getLocStringText(nameField) || "(unnamed item)";
|
|
} catch {
|
|
return "(unnamed item)";
|
|
}
|
|
}, [content]);
|
|
|
|
const headerSlot = (
|
|
<div
|
|
className="border-b px-4 py-3"
|
|
style={{ borderColor: "var(--forge-border)", backgroundColor: "var(--forge-surface)" }}
|
|
>
|
|
<h2 className="text-lg font-semibold" style={{ color: "var(--forge-text)" }}>
|
|
{itemName}
|
|
</h2>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<GffEditor
|
|
repo={repo}
|
|
filePath={filePath}
|
|
content={content}
|
|
onSave={onSave}
|
|
onSwitchToRaw={onSwitchToRaw}
|
|
fieldOverrides={fieldOverrides}
|
|
headerSlot={headerSlot}
|
|
/>
|
|
);
|
|
}
|