Skip to content

Commit 50ced0e

Browse files
leaanthonyclaude
andcommitted
feat(setup): add TypeScript binding style selection (interfaces vs classes)
- Add new wizard step after TypeScript selection for binding style choice - Add UseInterfaces field to GlobalDefaults and pass through to templates - Update Taskfile template to conditionally add -i flag for interfaces - Improve button positioning to match SplashPage layout - Remove "This sets the default template" text from template page - Fix overflow issues when resizing window 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 4c91130 commit 50ced0e

File tree

12 files changed

+243
-106
lines changed

12 files changed

+243
-106
lines changed

v3/internal/commands/build_assets/Taskfile.tmpl.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ tasks:
9494
generates:
9595
- frontend/bindings/**/*
9696
cmds:
97-
- wails3 generate bindings -f {{ "'{{.BUILD_FLAGS}}'" }} -clean=true {{- if .Typescript}} -ts{{end}}
97+
- wails3 generate bindings -f {{ "'{{.BUILD_FLAGS}}'" }} -clean=true {{- if .Typescript}} -ts{{end}}{{- if .UseInterfaces}} -i{{end}}
9898

9999
generate:icons:
100100
summary: Generates Windows `.ico` and Mac `.icns` files from an image

v3/internal/commands/init.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ func applyGlobalDefaults(options *flags.Init, globalDefaults defaults.GlobalDefa
130130
if options.ProductVersion == "0.1.0" && globalDefaults.Project.DefaultVersion != "" {
131131
options.ProductVersion = globalDefaults.GetDefaultVersion()
132132
}
133+
134+
// Apply UseInterfaces from global defaults (for TypeScript binding generation)
135+
options.UseInterfaces = globalDefaults.Project.UseInterfaces
133136
}
134137

135138
func Init(options *flags.Init) error {

v3/internal/defaults/defaults.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ type ProjectDefaults struct {
4242

4343
// Default product version for new projects
4444
DefaultVersion string `json:"defaultVersion" yaml:"defaultVersion"`
45+
46+
// UseInterfaces generates TypeScript interfaces instead of classes for bindings
47+
// Interfaces are lighter-weight and don't require runtime imports
48+
UseInterfaces bool `json:"useInterfaces" yaml:"useInterfaces"`
4549
}
4650

4751
// Default returns sensible defaults for first-time users
@@ -57,6 +61,7 @@ func Default() GlobalDefaults {
5761
CopyrightTemplate: "© {year}, {company}",
5862
DescriptionTemplate: "A {name} application",
5963
DefaultVersion: "0.1.0",
64+
UseInterfaces: true,
6065
},
6166
}
6267
}

v3/internal/flags/init.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ type Init struct {
2020
ProductComments string `description:"Comments to add to the generated files" default:"This is a comment"`
2121
ProductIdentifier string `description:"The product identifier, e.g com.mycompany.myproduct"`
2222
SkipWarning bool `name:"s" description:"Skips the warning message when using remote templates"`
23+
UseInterfaces bool `description:"Generate TypeScript interfaces instead of classes for bindings" default:"true"`
2324
}

v3/internal/setupwizard/frontend/dist/assets/index-BEsMGVNY.js

Lines changed: 0 additions & 48 deletions
This file was deleted.

v3/internal/setupwizard/frontend/dist/assets/index-BHYGl-ql.js

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/internal/setupwizard/frontend/dist/assets/index-BrSDszdC.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/internal/setupwizard/frontend/dist/assets/index-dcTD-mpY.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

v3/internal/setupwizard/frontend/dist/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<link rel="preconnect" href="https://fonts.googleapis.com">
88
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
99
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
10-
<script type="module" crossorigin src="/assets/index-BEsMGVNY.js"></script>
11-
<link rel="stylesheet" crossorigin href="/assets/index-dcTD-mpY.css">
10+
<script type="module" crossorigin src="/assets/index-BHYGl-ql.js"></script>
11+
<link rel="stylesheet" crossorigin href="/assets/index-BrSDszdC.css">
1212
</head>
1313
<body>
1414
<div id="root"></div>

v3/internal/setupwizard/frontend/src/App.tsx

Lines changed: 167 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type OOBEStep =
1515
| 'docker-setup'
1616
| 'projects'
1717
| 'language-select'
18+
| 'binding-style'
1819
| 'template-select'
1920
| 'complete';
2021

@@ -45,6 +46,7 @@ function getWizardStage(step: OOBEStep): WizardStage {
4546
case 'projects':
4647
return 'identity';
4748
case 'language-select':
49+
case 'binding-style':
4850
case 'template-select':
4951
return 'templates';
5052
case 'complete':
@@ -330,7 +332,7 @@ function CheckingPage() {
330332
animate="animate"
331333
exit="exit"
332334
transition={{ duration: 0.3 }}
333-
className="flex-1 flex flex-col items-center justify-start pt-[30%]"
335+
className="flex-1 flex flex-col items-center justify-start pt-[15%]"
334336
>
335337
<motion.div
336338
className="w-12 h-12 border-3 border-gray-300 dark:border-gray-600 border-t-red-500 rounded-full mb-6"
@@ -902,61 +904,145 @@ function LanguageSelectPage({
902904
animate="animate"
903905
exit="exit"
904906
transition={{ duration: 0.3 }}
905-
className="flex-1 flex flex-col"
907+
className="flex-1 flex flex-col items-center justify-center"
906908
>
907-
{/* Main content - centered */}
908-
<div className="flex-1 flex flex-col items-center justify-center">
909-
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white mb-2 text-center">
910-
Language Preference
911-
</h2>
912-
<p className="text-gray-500 dark:text-gray-400 mb-8 text-center max-w-md">
913-
Choose your preferred language for new projects
914-
</p>
909+
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white mb-2 text-center">
910+
Language Preference
911+
</h2>
912+
<p className="text-gray-500 dark:text-gray-400 mb-8 text-center max-w-md">
913+
Choose your preferred language for new projects
914+
</p>
915915

916-
<div className="flex gap-4">
917-
{/* JavaScript card */}
918-
<button
919-
onClick={() => onSelect(false)}
920-
className={`w-40 h-48 rounded-xl p-5 flex flex-col items-center justify-center gap-3 transition-all border-2 ${
921-
!preferTypeScript
922-
? 'border-yellow-400 bg-yellow-400/10 shadow-lg shadow-yellow-400/20'
923-
: 'border-white/10 bg-white/5 hover:bg-white/10'
924-
}`}
925-
>
926-
<div className="w-16 h-16 flex items-center justify-center">
927-
<img src="/logos/javascript.svg" alt="JavaScript" className="w-14 h-14" />
928-
</div>
929-
<span className="text-lg font-semibold text-white">JavaScript</span>
930-
<span className="text-xs text-white/50">Dynamic typing</span>
931-
</button>
916+
<div className="flex gap-4 mb-8">
917+
{/* JavaScript card */}
918+
<button
919+
onClick={() => onSelect(false)}
920+
className={`w-40 h-48 rounded-xl p-5 flex flex-col items-center justify-center gap-3 transition-all border-2 ${
921+
!preferTypeScript
922+
? 'border-yellow-400 bg-yellow-400/10 shadow-lg shadow-yellow-400/20'
923+
: 'border-white/10 bg-white/5 hover:bg-white/10'
924+
}`}
925+
>
926+
<div className="w-16 h-16 flex items-center justify-center">
927+
<img src="/logos/javascript.svg" alt="JavaScript" className="w-14 h-14" />
928+
</div>
929+
<span className="text-lg font-semibold text-white">JavaScript</span>
930+
<span className="text-xs text-white/50">Dynamic typing</span>
931+
</button>
932932

933-
{/* TypeScript card */}
934-
<button
935-
onClick={() => onSelect(true)}
936-
className={`w-40 h-48 rounded-xl p-5 flex flex-col items-center justify-center gap-3 transition-all border-2 ${
937-
preferTypeScript
938-
? 'border-blue-400 bg-blue-400/10 shadow-lg shadow-blue-400/20'
939-
: 'border-white/10 bg-white/5 hover:bg-white/10'
940-
}`}
941-
>
942-
<div className="w-16 h-16 flex items-center justify-center">
943-
<img src="/logos/typescript.svg" alt="TypeScript" className="w-14 h-14" />
944-
</div>
945-
<span className="text-lg font-semibold text-white">TypeScript</span>
946-
<span className="text-xs text-white/50">Type safety</span>
947-
</button>
948-
</div>
933+
{/* TypeScript card */}
934+
<button
935+
onClick={() => onSelect(true)}
936+
className={`w-40 h-48 rounded-xl p-5 flex flex-col items-center justify-center gap-3 transition-all border-2 ${
937+
preferTypeScript
938+
? 'border-blue-400 bg-blue-400/10 shadow-lg shadow-blue-400/20'
939+
: 'border-white/10 bg-white/5 hover:bg-white/10'
940+
}`}
941+
>
942+
<div className="w-16 h-16 flex items-center justify-center">
943+
<img src="/logos/typescript.svg" alt="TypeScript" className="w-14 h-14" />
944+
</div>
945+
<span className="text-lg font-semibold text-white">TypeScript</span>
946+
<span className="text-xs text-white/50">Type safety</span>
947+
</button>
949948
</div>
950949

951-
{/* Button area - sibling below main content */}
952-
<div className="flex justify-center pb-6">
950+
<button
951+
onClick={onNext}
952+
className="px-6 py-2.5 rounded-lg border border-red-500 text-red-600 dark:text-red-400 text-sm font-medium hover:bg-red-500/10 transition-colors"
953+
>
954+
Continue
955+
</button>
956+
</motion.div>
957+
);
958+
}
959+
960+
// Binding Style Select Page - Classes vs Interfaces (TypeScript only)
961+
function BindingStylePage({
962+
useInterfaces,
963+
onSelect,
964+
onNext,
965+
}: {
966+
useInterfaces: boolean;
967+
onSelect: (useInterfaces: boolean) => void;
968+
onNext: () => void;
969+
}) {
970+
return (
971+
<motion.div
972+
variants={pageVariants}
973+
initial="initial"
974+
animate="animate"
975+
exit="exit"
976+
transition={{ duration: 0.3 }}
977+
className="flex-1 flex flex-col items-center justify-center px-4 overflow-hidden"
978+
>
979+
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white mb-2 text-center">
980+
TypeScript Binding Style
981+
</h2>
982+
<p className="text-gray-500 dark:text-gray-400 mb-6 text-center max-w-lg">
983+
Choose how Go structs are represented in TypeScript
984+
</p>
985+
986+
<div className="flex gap-4 mb-8 max-w-full overflow-x-auto">
987+
{/* Interfaces option */}
953988
<button
954-
onClick={onNext}
955-
className="px-5 py-2 rounded-lg border border-red-500 text-red-600 dark:text-red-400 text-sm font-medium hover:bg-red-500/10 transition-colors"
989+
onClick={() => onSelect(true)}
990+
className={`w-56 shrink-0 rounded-xl p-4 flex flex-col items-start gap-2 transition-all border-2 text-left ${
991+
useInterfaces
992+
? 'border-blue-400 bg-blue-400/10 shadow-lg shadow-blue-400/20'
993+
: 'border-white/10 bg-white/5 hover:bg-white/10'
994+
}`}
956995
>
957-
Continue
996+
<span className="text-base font-semibold text-gray-900 dark:text-white">Interfaces</span>
997+
<pre className="text-[10px] leading-tight text-gray-700 dark:text-white/70 font-mono bg-gray-100 dark:bg-black/30 p-2 rounded-lg w-full overflow-x-auto">
998+
{`interface Person {
999+
name: string;
1000+
age: number;
1001+
}`}
1002+
</pre>
1003+
<ul className="text-[10px] text-gray-500 dark:text-white/50 space-y-0.5">
1004+
<li>• Lightweight types</li>
1005+
<li>• No runtime code</li>
1006+
<li>• Simpler output</li>
1007+
</ul>
1008+
</button>
1009+
1010+
{/* Classes option */}
1011+
<button
1012+
onClick={() => onSelect(false)}
1013+
className={`w-56 shrink-0 rounded-xl p-4 flex flex-col items-start gap-2 transition-all border-2 text-left ${
1014+
!useInterfaces
1015+
? 'border-purple-400 bg-purple-400/10 shadow-lg shadow-purple-400/20'
1016+
: 'border-white/10 bg-white/5 hover:bg-white/10'
1017+
}`}
1018+
>
1019+
<span className="text-base font-semibold text-gray-900 dark:text-white">Classes</span>
1020+
<pre className="text-[10px] leading-tight text-gray-700 dark:text-white/70 font-mono bg-gray-100 dark:bg-black/30 p-2 rounded-lg w-full overflow-x-auto">
1021+
{`class Person {
1022+
name: string;
1023+
age: number;
1024+
constructor(src) {
1025+
Object.assign(this, src);
1026+
}
1027+
static createFrom(src) {
1028+
return new Person(src);
1029+
}
1030+
}`}
1031+
</pre>
1032+
<ul className="text-[10px] text-gray-500 dark:text-white/50 space-y-0.5">
1033+
<li>• Factory methods</li>
1034+
<li>• Default initialization</li>
1035+
<li>• More verbose</li>
1036+
</ul>
9581037
</button>
9591038
</div>
1039+
1040+
<button
1041+
onClick={onNext}
1042+
className="px-6 py-2.5 rounded-lg border border-red-500 text-red-600 dark:text-red-400 text-sm font-medium hover:bg-red-500/10 transition-colors"
1043+
>
1044+
Continue
1045+
</button>
9601046
</motion.div>
9611047
);
9621048
}
@@ -1004,9 +1090,6 @@ function TemplateSelectPage({
10041090
</button>
10051091
))}
10061092
</div>
1007-
<p className="text-xs text-white/40 mt-4 text-center">
1008-
This sets the default template for new projects
1009-
</p>
10101093
</PageTemplate>
10111094
);
10121095
}
@@ -1182,13 +1265,15 @@ export default function App() {
11821265
defaultTemplate: 'vanilla',
11831266
copyrightTemplate: '(c) {year}, {company}',
11841267
descriptionTemplate: 'A {name} application',
1185-
defaultVersion: '0.1.0'
1268+
defaultVersion: '0.1.0',
1269+
useInterfaces: true
11861270
}
11871271
});
11881272
const [savingDefaults, setSavingDefaults] = useState(false);
11891273
const [backgroundDockerStarted, setBackgroundDockerStarted] = useState(false);
11901274
const [preferTypeScript, setPreferTypeScript] = useState(true);
11911275
const [selectedFramework, setSelectedFramework] = useState('vanilla');
1276+
const [useInterfaces, setUseInterfaces] = useState(true);
11921277
const [theme, setTheme] = useState<Theme>(() => {
11931278
if (typeof window !== 'undefined') {
11941279
const saved = localStorage.getItem('wails-setup-theme');
@@ -1275,6 +1360,7 @@ export default function App() {
12751360
// Load defaults and go to projects
12761361
const loadedDefaults = await getDefaults();
12771362
setDefaults(loadedDefaults);
1363+
setUseInterfaces(loadedDefaults.project?.useInterfaces ?? true);
12781364
setStep('projects');
12791365
};
12801366

@@ -1307,12 +1393,14 @@ export default function App() {
13071393
// Load defaults and go to projects
13081394
const loadedDefaults = await getDefaults();
13091395
setDefaults(loadedDefaults);
1396+
setUseInterfaces(loadedDefaults.project?.useInterfaces ?? true);
13101397
setStep('projects');
13111398
};
13121399

13131400
const handleDockerSkip = async () => {
13141401
const loadedDefaults = await getDefaults();
13151402
setDefaults(loadedDefaults);
1403+
setUseInterfaces(loadedDefaults.project?.useInterfaces ?? true);
13161404
setStep('projects');
13171405
};
13181406

@@ -1325,6 +1413,14 @@ export default function App() {
13251413
};
13261414

13271415
const handleLanguageSelectNext = () => {
1416+
if (preferTypeScript) {
1417+
setStep('binding-style');
1418+
} else {
1419+
setStep('template-select');
1420+
}
1421+
};
1422+
1423+
const handleBindingStyleNext = () => {
13281424
setStep('template-select');
13291425
};
13301426

@@ -1336,12 +1432,13 @@ export default function App() {
13361432
? 'vanilla-ts'
13371433
: selectedFramework;
13381434

1339-
// Update defaults with selected template
1435+
// Update defaults with selected template and binding style
13401436
const updatedDefaults = {
13411437
...defaults,
13421438
project: {
13431439
...defaults.project,
1344-
defaultTemplate: templateName
1440+
defaultTemplate: templateName,
1441+
useInterfaces: preferTypeScript ? useInterfaces : true, // Only relevant for TypeScript
13451442
}
13461443
};
13471444

@@ -1352,8 +1449,16 @@ export default function App() {
13521449
};
13531450

13541451
const handleTemplateSelectSkip = async () => {
1452+
// Save defaults with binding style preference
1453+
const updatedDefaults = {
1454+
...defaults,
1455+
project: {
1456+
...defaults.project,
1457+
useInterfaces: preferTypeScript ? useInterfaces : true,
1458+
}
1459+
};
13551460
setSavingDefaults(true);
1356-
await saveDefaults(defaults);
1461+
await saveDefaults(updatedDefaults);
13571462
setSavingDefaults(false);
13581463
setStep('complete');
13591464
};
@@ -1449,6 +1554,14 @@ export default function App() {
14491554
onNext={handleLanguageSelectNext}
14501555
/>
14511556
)}
1557+
{step === 'binding-style' && (
1558+
<BindingStylePage
1559+
key="binding-style"
1560+
useInterfaces={useInterfaces}
1561+
onSelect={setUseInterfaces}
1562+
onNext={handleBindingStyleNext}
1563+
/>
1564+
)}
14521565
{step === 'template-select' && (
14531566
<TemplateSelectPage
14541567
key="template-select"

0 commit comments

Comments
 (0)