Layonara Forge — NWN Development IDE
Electron desktop application for Neverwinter Nights module development. Clone, edit, build, and run a complete Layonara NWNX server with only Docker required. - React 19 + Vite frontend with Monaco editor and NWScript LSP - Node.js + Express backend managing Docker sibling containers - Electron shell with Docker availability check and auto-setup - Builder image auto-builds on first use from bundled Dockerfile - Cross-platform: Windows (.exe), macOS (.dmg), Linux (.AppImage) - Gitea Actions CI for automated release builds
This commit is contained in:
@@ -25,21 +25,28 @@ function AbilityScoresOverride({ data, onChange }: FieldOverrideProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm" style={{ color: "var(--forge-text-secondary)" }}>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
|
||||
<label style={{ fontSize: "var(--text-sm)", color: "var(--forge-text-secondary)" }}>
|
||||
Ability Scores
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: "0.75rem" }}>
|
||||
{abilities.flat().map((ab) => {
|
||||
const val = getFieldValue(data, ab);
|
||||
const num = typeof val === "number" ? val : 0;
|
||||
return (
|
||||
<div
|
||||
key={ab}
|
||||
className="flex flex-col items-center rounded border px-3 py-2"
|
||||
style={{ borderColor: "var(--forge-border)", backgroundColor: "var(--forge-bg)" }}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
borderRadius: "0.25rem",
|
||||
border: "1px solid var(--forge-border)",
|
||||
padding: "0.5rem 0.75rem",
|
||||
backgroundColor: "var(--forge-bg)",
|
||||
}}
|
||||
>
|
||||
<span className="text-xs font-bold" style={{ color: "var(--forge-text-secondary)" }}>
|
||||
<span style={{ fontSize: "var(--text-xs)", fontWeight: 700, color: "var(--forge-text-secondary)" }}>
|
||||
{displayNames[ab]}
|
||||
</span>
|
||||
<input
|
||||
@@ -48,14 +55,20 @@ function AbilityScoresOverride({ data, onChange }: FieldOverrideProps) {
|
||||
min={1}
|
||||
max={99}
|
||||
onChange={(e) => onChange(ab, parseInt(e.target.value, 10))}
|
||||
className="mt-1 w-16 rounded border px-1 py-1 text-center text-lg font-semibold"
|
||||
style={{
|
||||
marginTop: "0.25rem",
|
||||
width: "4rem",
|
||||
borderRadius: "0.25rem",
|
||||
border: "1px solid var(--forge-border)",
|
||||
padding: "0.25rem",
|
||||
textAlign: "center",
|
||||
fontSize: "var(--text-lg)",
|
||||
fontWeight: 600,
|
||||
backgroundColor: "var(--forge-surface)",
|
||||
borderColor: "var(--forge-border)",
|
||||
color: "var(--forge-text)",
|
||||
}}
|
||||
/>
|
||||
<span className="mt-0.5 text-xs" style={{ color: "var(--forge-text-secondary)" }}>
|
||||
<span style={{ marginTop: "0.125rem", fontSize: "var(--text-xs)", color: "var(--forge-text-secondary)" }}>
|
||||
mod {num >= 10 ? "+" : ""}{Math.floor((num - 10) / 2)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -73,34 +86,39 @@ function RaceGenderOverride({ data, onChange }: FieldOverrideProps) {
|
||||
const genderNum = typeof gender === "number" ? gender : 0;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm" style={{ color: "var(--forge-text-secondary)" }}>Race</label>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
|
||||
<label style={{ fontSize: "var(--text-sm)", color: "var(--forge-text-secondary)" }}>Race</label>
|
||||
<input
|
||||
type="number"
|
||||
value={raceNum}
|
||||
min={0}
|
||||
onChange={(e) => onChange("Race", parseInt(e.target.value, 10))}
|
||||
className="w-20 rounded border px-2 py-1.5 text-sm"
|
||||
style={{
|
||||
width: "5rem",
|
||||
borderRadius: "0.25rem",
|
||||
border: "1px solid var(--forge-border)",
|
||||
padding: "0.375rem 0.5rem",
|
||||
fontSize: "var(--text-sm)",
|
||||
backgroundColor: "var(--forge-bg)",
|
||||
borderColor: "var(--forge-border)",
|
||||
color: "var(--forge-text)",
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs" style={{ color: "var(--forge-text-secondary)" }}>
|
||||
<span style={{ fontSize: "var(--text-xs)", color: "var(--forge-text-secondary)" }}>
|
||||
(racialtypes.2da)
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm" style={{ color: "var(--forge-text-secondary)" }}>Gender</label>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
|
||||
<label style={{ fontSize: "var(--text-sm)", color: "var(--forge-text-secondary)" }}>Gender</label>
|
||||
<select
|
||||
value={genderNum}
|
||||
onChange={(e) => onChange("Gender", parseInt(e.target.value, 10))}
|
||||
className="rounded border px-2 py-1.5 text-sm"
|
||||
style={{
|
||||
borderRadius: "0.25rem",
|
||||
border: "1px solid var(--forge-border)",
|
||||
padding: "0.375rem 0.5rem",
|
||||
fontSize: "var(--text-sm)",
|
||||
backgroundColor: "var(--forge-bg)",
|
||||
borderColor: "var(--forge-border)",
|
||||
color: "var(--forge-text)",
|
||||
}}
|
||||
>
|
||||
@@ -121,13 +139,13 @@ function ScriptsOverride({ data, onChange }: FieldOverrideProps) {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
|
||||
{scripts.map((s) => {
|
||||
const val = getFieldValue(data, s.label);
|
||||
const str = typeof val === "string" ? val : "";
|
||||
return (
|
||||
<div key={s.label} className="flex items-center gap-3">
|
||||
<label className="w-28 shrink-0 text-sm" style={{ color: "var(--forge-text-secondary)" }}>
|
||||
<div key={s.label} style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
|
||||
<label style={{ width: "7rem", flexShrink: 0, fontSize: "var(--text-sm)", color: "var(--forge-text-secondary)" }}>
|
||||
{s.display}
|
||||
</label>
|
||||
<input
|
||||
@@ -135,10 +153,14 @@ function ScriptsOverride({ data, onChange }: FieldOverrideProps) {
|
||||
value={str}
|
||||
maxLength={16}
|
||||
onChange={(e) => onChange(s.label, e.target.value)}
|
||||
className="flex-1 rounded border px-2 py-1.5 font-mono text-sm"
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRadius: "0.25rem",
|
||||
border: "1px solid var(--forge-border)",
|
||||
padding: "0.375rem 0.5rem",
|
||||
fontFamily: "var(--font-mono)",
|
||||
fontSize: "var(--text-sm)",
|
||||
backgroundColor: "var(--forge-bg)",
|
||||
borderColor: "var(--forge-border)",
|
||||
color: "var(--forge-text)",
|
||||
}}
|
||||
placeholder="(none)"
|
||||
|
||||
Reference in New Issue
Block a user