diff --git a/v2/cmd/wails/generate.go b/v2/cmd/wails/generate.go
index 159df90a1..052900519 100644
--- a/v2/cmd/wails/generate.go
+++ b/v2/cmd/wails/generate.go
@@ -43,10 +43,15 @@ func generateModule(f *flags.GenerateModule) error {
return err
}
+ if projectConfig.Bindings.TsGeneration.OutputType == "" {
+ projectConfig.Bindings.TsGeneration.OutputType = "classes"
+ }
+
_, err = bindings.GenerateBindings(bindings.Options{
- Tags: buildTags,
- TsPrefix: projectConfig.Bindings.TsGeneration.Prefix,
- TsSuffix: projectConfig.Bindings.TsGeneration.Suffix,
+ Tags: buildTags,
+ TsPrefix: projectConfig.Bindings.TsGeneration.Prefix,
+ TsSuffix: projectConfig.Bindings.TsGeneration.Suffix,
+ TsOutputType: projectConfig.Bindings.TsGeneration.OutputType,
})
if err != nil {
return err
diff --git a/v2/internal/app/app_bindings.go b/v2/internal/app/app_bindings.go
index d079790aa..be031819c 100644
--- a/v2/internal/app/app_bindings.go
+++ b/v2/internal/app/app_bindings.go
@@ -31,6 +31,7 @@ func (a *App) Run() error {
var tsPrefixFlag *string
var tsPostfixFlag *string
+ var tsOutputTypeFlag *string
tsPrefix := os.Getenv("tsprefix")
if tsPrefix == "" {
@@ -42,6 +43,11 @@ func (a *App) Run() error {
tsPostfixFlag = bindingFlags.String("tssuffix", "", "Suffix for generated typescript entities")
}
+ tsOutputType := os.Getenv("tsoutputtype")
+ if tsOutputType == "" {
+ tsOutputTypeFlag = bindingFlags.String("tsoutputtype", "", "Output type for generated typescript entities (classes|interfaces)")
+ }
+
_ = bindingFlags.Parse(os.Args[1:])
if tsPrefixFlag != nil {
tsPrefix = *tsPrefixFlag
@@ -49,11 +55,15 @@ func (a *App) Run() error {
if tsPostfixFlag != nil {
tsSuffix = *tsPostfixFlag
}
+ if tsOutputTypeFlag != nil {
+ tsOutputType = *tsOutputTypeFlag
+ }
- appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated())
+ appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated(), a.options.EnumBind)
appBindings.SetTsPrefix(tsPrefix)
appBindings.SetTsSuffix(tsSuffix)
+ appBindings.SetOutputType(tsOutputType)
err := generateBindings(appBindings)
if err != nil {
diff --git a/v2/internal/app/app_dev.go b/v2/internal/app/app_dev.go
index 51e3e63ba..58cd94ef0 100644
--- a/v2/internal/app/app_dev.go
+++ b/v2/internal/app/app_dev.go
@@ -209,7 +209,7 @@ func CreateApp(appoptions *options.App) (*App, error) {
appoptions.OnDomReady,
appoptions.OnBeforeClose,
}
- appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, false)
+ appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, false, appoptions.EnumBind)
eventHandler := runtime.NewEvents(myLogger)
ctx = context.WithValue(ctx, "events", eventHandler)
diff --git a/v2/internal/app/app_production.go b/v2/internal/app/app_production.go
index 96ed84e30..4c217b17c 100644
--- a/v2/internal/app/app_production.go
+++ b/v2/internal/app/app_production.go
@@ -72,7 +72,7 @@ func CreateApp(appoptions *options.App) (*App, error) {
appoptions.OnDomReady,
appoptions.OnBeforeClose,
}
- appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, IsObfuscated())
+ appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, IsObfuscated(), appoptions.EnumBind)
eventHandler := runtime.NewEvents(myLogger)
ctx = context.WithValue(ctx, "events", eventHandler)
// Attach logger to context
diff --git a/v2/internal/binding/binding.go b/v2/internal/binding/binding.go
index d911e12a3..568e11b03 100644
--- a/v2/internal/binding/binding.go
+++ b/v2/internal/binding/binding.go
@@ -23,17 +23,20 @@ type Bindings struct {
exemptions slicer.StringSlicer
structsToGenerateTS map[string]map[string]interface{}
+ enumsToGenerateTS map[string]map[string]interface{}
tsPrefix string
tsSuffix string
+ tsInterface bool
obfuscate bool
}
// NewBindings returns a new Bindings object
-func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}, obfuscate bool) *Bindings {
+func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}, obfuscate bool, enumsToBind []interface{}) *Bindings {
result := &Bindings{
db: newDB(),
logger: logger.CustomLogger("Bindings"),
structsToGenerateTS: make(map[string]map[string]interface{}),
+ enumsToGenerateTS: make(map[string]map[string]interface{}),
obfuscate: obfuscate,
}
@@ -47,6 +50,10 @@ func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exem
result.exemptions.Add(name)
}
+ for _, enum := range enumsToBind {
+ result.AddEnumToGenerateTS(enum)
+ }
+
// Add the structs to bind
for _, ptr := range structPointersToBind {
err := result.Add(ptr)
@@ -88,16 +95,21 @@ func (b *Bindings) ToJSON() (string, error) {
func (b *Bindings) GenerateModels() ([]byte, error) {
models := map[string]string{}
var seen slicer.StringSlicer
+ var seenEnumsPackages slicer.StringSlicer
allStructNames := b.getAllStructNames()
allStructNames.Sort()
+ allEnumNames := b.getAllEnumNames()
+ allEnumNames.Sort()
for packageName, structsToGenerate := range b.structsToGenerateTS {
thisPackageCode := ""
w := typescriptify.New()
w.WithPrefix(b.tsPrefix)
w.WithSuffix(b.tsSuffix)
+ w.WithInterface(b.tsInterface)
w.Namespace = packageName
w.WithBackupDir("")
w.KnownStructs = allStructNames
+ w.KnownEnums = allEnumNames
// sort the structs
var structNames []string
for structName := range structsToGenerate {
@@ -112,6 +124,20 @@ func (b *Bindings) GenerateModels() ([]byte, error) {
structInterface := structsToGenerate[structName]
w.Add(structInterface)
}
+
+ // if we have enums for this package, add them as well
+ var enums, enumsExist = b.enumsToGenerateTS[packageName]
+ if enumsExist {
+ for enumName, enum := range enums {
+ fqemumname := packageName + "." + enumName
+ if seen.Contains(fqemumname) {
+ continue
+ }
+ w.AddEnum(enum)
+ }
+ seenEnumsPackages.Add(packageName)
+ }
+
str, err := w.Convert(nil)
if err != nil {
return nil, err
@@ -121,6 +147,35 @@ func (b *Bindings) GenerateModels() ([]byte, error) {
models[packageName] = thisPackageCode
}
+ // Add outstanding enums to the models that were not in packages with structs
+ for packageName, enumsToGenerate := range b.enumsToGenerateTS {
+ if seenEnumsPackages.Contains(packageName) {
+ continue
+ }
+
+ thisPackageCode := ""
+ w := typescriptify.New()
+ w.WithPrefix(b.tsPrefix)
+ w.WithSuffix(b.tsSuffix)
+ w.WithInterface(b.tsInterface)
+ w.Namespace = packageName
+ w.WithBackupDir("")
+
+ for enumName, enum := range enumsToGenerate {
+ fqemumname := packageName + "." + enumName
+ if seen.Contains(fqemumname) {
+ continue
+ }
+ w.AddEnum(enum)
+ }
+ str, err := w.Convert(nil)
+ if err != nil {
+ return nil, err
+ }
+ thisPackageCode += str
+ models[packageName] = thisPackageCode
+ }
+
// Sort the package names first to make the output deterministic
sortedPackageNames := make([]string, 0)
for packageName := range models {
@@ -163,6 +218,39 @@ func (b *Bindings) WriteModels(modelsDir string) error {
return nil
}
+func (b *Bindings) AddEnumToGenerateTS(e interface{}) {
+ enumType := reflect.TypeOf(e)
+
+ var packageName string
+ var enumName string
+ // enums should be represented as array of all possible values
+ if hasElements(enumType) {
+ enum := enumType.Elem()
+ // simple enum represented by struct with Value/TSName fields
+ if enum.Kind() == reflect.Struct {
+ _, tsNamePresented := enum.FieldByName("TSName")
+ enumT, valuePresented := enum.FieldByName("Value")
+ if tsNamePresented && valuePresented {
+ packageName = getPackageName(enumT.Type.String())
+ enumName = enumT.Type.Name()
+ } else {
+ return
+ }
+ // otherwise expecting implementation with TSName() https://github.com/tkrajina/typescriptify-golang-structs#enums-with-tsname
+ } else {
+ packageName = getPackageName(enumType.Elem().String())
+ enumName = enumType.Elem().Name()
+ }
+ if b.enumsToGenerateTS[packageName] == nil {
+ b.enumsToGenerateTS[packageName] = make(map[string]interface{})
+ }
+ if b.enumsToGenerateTS[packageName][enumName] != nil {
+ return
+ }
+ b.enumsToGenerateTS[packageName][enumName] = e
+ }
+}
+
func (b *Bindings) AddStructToGenerateTS(packageName string, structName string, s interface{}) {
if b.structsToGenerateTS[packageName] == nil {
b.structsToGenerateTS[packageName] = make(map[string]interface{})
@@ -231,6 +319,13 @@ func (b *Bindings) SetTsSuffix(postfix string) *Bindings {
return b
}
+func (b *Bindings) SetOutputType(outputType string) *Bindings {
+ if outputType == "interfaces" {
+ b.tsInterface = true
+ }
+ return b
+}
+
func (b *Bindings) getAllStructNames() *slicer.StringSlicer {
var result slicer.StringSlicer
for packageName, structsToGenerate := range b.structsToGenerateTS {
@@ -241,6 +336,16 @@ func (b *Bindings) getAllStructNames() *slicer.StringSlicer {
return &result
}
+func (b *Bindings) getAllEnumNames() *slicer.StringSlicer {
+ var result slicer.StringSlicer
+ for packageName, enumsToGenerate := range b.enumsToGenerateTS {
+ for enumName := range enumsToGenerate {
+ result.Add(packageName + "." + enumName)
+ }
+ }
+ return &result
+}
+
func (b *Bindings) hasExportedJSONFields(typeOf reflect.Type) bool {
for i := 0; i < typeOf.NumField(); i++ {
jsonFieldName := ""
diff --git a/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go b/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go
index 2309d6daf..b37334ec3 100644
--- a/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go
+++ b/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go
@@ -42,7 +42,7 @@ func TestConflictingPackageName(t *testing.T) {
// setup
testLogger := &logger.Logger{}
- b := binding.NewBindings(testLogger, []interface{}{&HandlerTest{}}, []interface{}{}, false)
+ b := binding.NewBindings(testLogger, []interface{}{&HandlerTest{}}, []interface{}{}, false, []interface{}{})
// then
err := b.GenerateGoBindings(generationDir)
diff --git a/v2/internal/binding/binding_test/binding_importedenum_test.go b/v2/internal/binding/binding_test/binding_importedenum_test.go
new file mode 100644
index 000000000..5b5b4419e
--- /dev/null
+++ b/v2/internal/binding/binding_test/binding_importedenum_test.go
@@ -0,0 +1,50 @@
+package binding_test
+
+import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import"
+
+type ImportedEnumStruct struct {
+ EnumValue binding_test_import.ImportedEnum `json:"EnumValue"`
+}
+
+func (s ImportedEnumStruct) Get() ImportedEnumStruct {
+ return s
+}
+
+var ImportedEnumTest = BindingTest{
+ name: "ImportedEnum",
+ structs: []interface{}{
+ &ImportedEnumStruct{},
+ },
+ enums: []interface{}{
+ binding_test_import.AllImportedEnumValues,
+ },
+ exemptions: nil,
+ shouldError: false,
+ want: `export namespace binding_test {
+
+ export class ImportedEnumStruct {
+ EnumValue: binding_test_import.ImportedEnum;
+
+ static createFrom(source: any = {}) {
+ return new ImportedEnumStruct(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.EnumValue = source["EnumValue"];
+ }
+ }
+
+ }
+
+ export namespace binding_test_import {
+
+ export enum ImportedEnum {
+ Value1 = "value1",
+ Value2 = "value2",
+ Value3 = "value3",
+ }
+
+ }
+`,
+}
diff --git a/v2/internal/binding/binding_test/binding_returned_promises_test.go b/v2/internal/binding/binding_test/binding_returned_promises_test.go
index 837d5fad3..94941d0a3 100644
--- a/v2/internal/binding/binding_test/binding_returned_promises_test.go
+++ b/v2/internal/binding/binding_test/binding_returned_promises_test.go
@@ -59,7 +59,7 @@ func TestPromises(t *testing.T) {
// setup
testLogger := &logger.Logger{}
- b := binding.NewBindings(testLogger, []interface{}{&PromisesTest{}}, []interface{}{}, false)
+ b := binding.NewBindings(testLogger, []interface{}{&PromisesTest{}}, []interface{}{}, false, []interface{}{})
// then
err := b.GenerateGoBindings(generationDir)
diff --git a/v2/internal/binding/binding_test/binding_test.go b/v2/internal/binding/binding_test/binding_test.go
index c2e351915..6d643b92d 100644
--- a/v2/internal/binding/binding_test/binding_test.go
+++ b/v2/internal/binding/binding_test/binding_test.go
@@ -13,6 +13,7 @@ import (
type BindingTest struct {
name string
structs []interface{}
+ enums []interface{}
exemptions []interface{}
want string
shouldError bool
@@ -20,8 +21,9 @@ type BindingTest struct {
}
type TsGenerationOptionsTest struct {
- TsPrefix string
- TsSuffix string
+ TsPrefix string
+ TsSuffix string
+ TsOutputType string
}
func TestBindings_GenerateModels(t *testing.T) {
@@ -31,12 +33,17 @@ func TestBindings_GenerateModels(t *testing.T) {
ImportedStructTest,
ImportedSliceTest,
ImportedMapTest,
+ ImportedEnumTest,
NestedFieldTest,
NonStringMapKeyTest,
SingleFieldTest,
MultistructTest,
EmptyStructTest,
GeneratedJsEntityTest,
+ GeneratedJsEntityWithIntEnumTest,
+ GeneratedJsEntityWithStringEnumTest,
+ GeneratedJsEntityWithEnumTsName,
+ GeneratedJsEntityWithNestedStructInterfacesTest,
AnonymousSubStructTest,
AnonymousSubStructMultiLevelTest,
GeneratedJsEntityWithNestedStructTest,
@@ -46,13 +53,14 @@ func TestBindings_GenerateModels(t *testing.T) {
testLogger := &logger.Logger{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- b := binding.NewBindings(testLogger, tt.structs, tt.exemptions, false)
+ b := binding.NewBindings(testLogger, tt.structs, tt.exemptions, false, tt.enums)
for _, s := range tt.structs {
err := b.Add(s)
require.NoError(t, err)
}
b.SetTsPrefix(tt.TsPrefix)
b.SetTsSuffix(tt.TsSuffix)
+ b.SetOutputType(tt.TsOutputType)
got, err := b.GenerateModels()
if (err != nil) != tt.shouldError {
t.Errorf("GenerateModels() error = %v, shouldError %v", err, tt.shouldError)
diff --git a/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go b/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go
index 6b99d43be..e7080c694 100644
--- a/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go
+++ b/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go
@@ -13,3 +13,20 @@ type ASliceWrapper struct {
type AMapWrapper struct {
AMap map[string]binding_test_nestedimport.A `json:"AMap"`
}
+
+type ImportedEnum string
+
+const (
+ ImportedEnumValue1 ImportedEnum = "value1"
+ ImportedEnumValue2 ImportedEnum = "value2"
+ ImportedEnumValue3 ImportedEnum = "value3"
+)
+
+var AllImportedEnumValues = []struct {
+ Value ImportedEnum
+ TSName string
+}{
+ {ImportedEnumValue1, "Value1"},
+ {ImportedEnumValue2, "Value2"},
+ {ImportedEnumValue3, "Value3"},
+}
diff --git a/v2/internal/binding/binding_test/binding_tsgeneration_test.go b/v2/internal/binding/binding_test/binding_tsgeneration_test.go
index d2c5349c5..b627772fe 100644
--- a/v2/internal/binding/binding_test/binding_tsgeneration_test.go
+++ b/v2/internal/binding/binding_test/binding_tsgeneration_test.go
@@ -275,3 +275,235 @@ export namespace binding_test {
`,
}
+
+type IntEnum int
+
+const (
+ IntEnumValue1 IntEnum = iota
+ IntEnumValue2
+ IntEnumValue3
+)
+
+var AllIntEnumValues = []struct {
+ Value IntEnum
+ TSName string
+}{
+ {IntEnumValue1, "Value1"},
+ {IntEnumValue2, "Value2"},
+ {IntEnumValue3, "Value3"},
+}
+
+type EntityWithIntEnum struct {
+ Name string `json:"name"`
+ Enum IntEnum `json:"enum"`
+}
+
+func (e EntityWithIntEnum) Get() EntityWithIntEnum {
+ return e
+}
+
+var GeneratedJsEntityWithIntEnumTest = BindingTest{
+ name: "GeneratedJsEntityWithIntEnumTest",
+ structs: []interface{}{
+ &EntityWithIntEnum{},
+ },
+ enums: []interface{}{
+ AllIntEnumValues,
+ },
+ exemptions: nil,
+ shouldError: false,
+ TsGenerationOptionsTest: TsGenerationOptionsTest{
+ TsPrefix: "MY_PREFIX_",
+ TsSuffix: "_MY_SUFFIX",
+ },
+ want: `export namespace binding_test {
+
+ export enum MY_PREFIX_IntEnum_MY_SUFFIX {
+ Value1 = 0,
+ Value2 = 1,
+ Value3 = 2,
+ }
+ export class MY_PREFIX_EntityWithIntEnum_MY_SUFFIX {
+ name: string;
+ enum: MY_PREFIX_IntEnum_MY_SUFFIX;
+
+ static createFrom(source: any = {}) {
+ return new MY_PREFIX_EntityWithIntEnum_MY_SUFFIX(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.name = source["name"];
+ this.enum = source["enum"];
+ }
+ }
+
+ }
+`,
+}
+
+type StringEnum string
+
+const (
+ StringEnumValue1 StringEnum = "value1"
+ StringEnumValue2 StringEnum = "value2"
+ StringEnumValue3 StringEnum = "value3"
+)
+
+var AllStringEnumValues = []struct {
+ Value StringEnum
+ TSName string
+}{
+ {StringEnumValue1, "Value1"},
+ {StringEnumValue2, "Value2"},
+ {StringEnumValue3, "Value3"},
+}
+
+type EntityWithStringEnum struct {
+ Name string `json:"name"`
+ Enum StringEnum `json:"enum"`
+}
+
+func (e EntityWithStringEnum) Get() EntityWithStringEnum {
+ return e
+}
+
+var GeneratedJsEntityWithStringEnumTest = BindingTest{
+ name: "GeneratedJsEntityWithStringEnumTest",
+ structs: []interface{}{
+ &EntityWithStringEnum{},
+ },
+ enums: []interface{}{
+ AllStringEnumValues,
+ },
+ exemptions: nil,
+ shouldError: false,
+ TsGenerationOptionsTest: TsGenerationOptionsTest{
+ TsPrefix: "MY_PREFIX_",
+ TsSuffix: "_MY_SUFFIX",
+ },
+ want: `export namespace binding_test {
+
+ export enum MY_PREFIX_StringEnum_MY_SUFFIX {
+ Value1 = "value1",
+ Value2 = "value2",
+ Value3 = "value3",
+ }
+ export class MY_PREFIX_EntityWithStringEnum_MY_SUFFIX {
+ name: string;
+ enum: MY_PREFIX_StringEnum_MY_SUFFIX;
+
+ static createFrom(source: any = {}) {
+ return new MY_PREFIX_EntityWithStringEnum_MY_SUFFIX(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.name = source["name"];
+ this.enum = source["enum"];
+ }
+ }
+
+ }
+`,
+}
+
+type EnumWithTsName string
+
+const (
+ EnumWithTsName1 EnumWithTsName = "value1"
+ EnumWithTsName2 EnumWithTsName = "value2"
+ EnumWithTsName3 EnumWithTsName = "value3"
+)
+
+var AllEnumWithTsNameValues = []EnumWithTsName{EnumWithTsName1, EnumWithTsName2, EnumWithTsName3}
+
+func (v EnumWithTsName) TSName() string {
+ switch v {
+ case EnumWithTsName1:
+ return "TsName1"
+ case EnumWithTsName2:
+ return "TsName2"
+ case EnumWithTsName3:
+ return "TsName3"
+ default:
+ return "???"
+ }
+}
+
+type EntityWithEnumTsName struct {
+ Name string `json:"name"`
+ Enum EnumWithTsName `json:"enum"`
+}
+
+func (e EntityWithEnumTsName) Get() EntityWithEnumTsName {
+ return e
+}
+
+var GeneratedJsEntityWithEnumTsName = BindingTest{
+ name: "GeneratedJsEntityWithEnumTsName",
+ structs: []interface{}{
+ &EntityWithEnumTsName{},
+ },
+ enums: []interface{}{
+ AllEnumWithTsNameValues,
+ },
+ exemptions: nil,
+ shouldError: false,
+ TsGenerationOptionsTest: TsGenerationOptionsTest{
+ TsPrefix: "MY_PREFIX_",
+ TsSuffix: "_MY_SUFFIX",
+ },
+ want: `export namespace binding_test {
+
+ export enum MY_PREFIX_EnumWithTsName_MY_SUFFIX {
+ TsName1 = "value1",
+ TsName2 = "value2",
+ TsName3 = "value3",
+ }
+ export class MY_PREFIX_EntityWithEnumTsName_MY_SUFFIX {
+ name: string;
+ enum: MY_PREFIX_EnumWithTsName_MY_SUFFIX;
+
+ static createFrom(source: any = {}) {
+ return new MY_PREFIX_EntityWithEnumTsName_MY_SUFFIX(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.name = source["name"];
+ this.enum = source["enum"];
+ }
+ }
+
+ }
+`,
+}
+
+var GeneratedJsEntityWithNestedStructInterfacesTest = BindingTest{
+ name: "GeneratedJsEntityWithNestedStructInterfacesTest",
+ structs: []interface{}{
+ &ParentEntity{},
+ },
+ exemptions: nil,
+ shouldError: false,
+ TsGenerationOptionsTest: TsGenerationOptionsTest{
+ TsPrefix: "MY_PREFIX_",
+ TsSuffix: "_MY_SUFFIX",
+ TsOutputType: "interfaces",
+ },
+ want: `export namespace binding_test {
+
+ export interface MY_PREFIX_ChildEntity_MY_SUFFIX {
+ name: string;
+ childProp: number;
+ }
+ export interface MY_PREFIX_ParentEntity_MY_SUFFIX {
+ name: string;
+ ref: MY_PREFIX_ChildEntity_MY_SUFFIX;
+ parentProp: string;
+ }
+
+ }
+`,
+}
diff --git a/v2/internal/binding/binding_test/binding_type_alias_test.go b/v2/internal/binding/binding_test/binding_type_alias_test.go
index 8e7c7ca6d..498c5976c 100644
--- a/v2/internal/binding/binding_test/binding_type_alias_test.go
+++ b/v2/internal/binding/binding_test/binding_type_alias_test.go
@@ -41,7 +41,7 @@ func TestAliases(t *testing.T) {
// setup
testLogger := &logger.Logger{}
- b := binding.NewBindings(testLogger, []interface{}{&AliasTest{}}, []interface{}{}, false)
+ b := binding.NewBindings(testLogger, []interface{}{&AliasTest{}}, []interface{}{}, false, []interface{}{})
// then
err := b.GenerateGoBindings(generationDir)
diff --git a/v2/internal/binding/generate_test.go b/v2/internal/binding/generate_test.go
index 565fba31c..8d6a833b8 100644
--- a/v2/internal/binding/generate_test.go
+++ b/v2/internal/binding/generate_test.go
@@ -25,7 +25,7 @@ type B struct {
func TestNestedStruct(t *testing.T) {
bind := &BindForTest{}
- testBindings := NewBindings(logger.New(nil), []interface{}{bind}, []interface{}{}, false)
+ testBindings := NewBindings(logger.New(nil), []interface{}{bind}, []interface{}{}, false, []interface{}{})
namesStrSlicer := testBindings.getAllStructNames()
names := []string{}
diff --git a/v2/internal/project/project.go b/v2/internal/project/project.go
index 34cbe88da..0d84f1855 100644
--- a/v2/internal/project/project.go
+++ b/v2/internal/project/project.go
@@ -242,8 +242,9 @@ type Bindings struct {
}
type TsGeneration struct {
- Prefix string `json:"prefix"`
- Suffix string `json:"suffix"`
+ Prefix string `json:"prefix"`
+ Suffix string `json:"suffix"`
+ OutputType string `json:"outputType"`
}
// Parse the given JSON data into a Project struct
diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go
index bb72e6fb8..c06a8b2ec 100644
--- a/v2/internal/typescriptify/typescriptify.go
+++ b/v2/internal/typescriptify/typescriptify.go
@@ -104,6 +104,7 @@ type TypeScriptify struct {
Namespace string
KnownStructs *slicer.StringSlicer
+ KnownEnums *slicer.StringSlicer
}
func New() *TypeScriptify {
@@ -723,7 +724,16 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m
}
} else { // Simple field:
t.logf(depth, "- simple field %s.%s", typeOf.Name(), field.Name)
- err = builder.AddSimpleField(jsonFieldName, field, fldOpts)
+ // check if type is in known enum. If so, then replace TStype with enum name to avoid missing types
+ isKnownEnum := t.KnownEnums.Contains(getStructFQN(field.Type.String()))
+ if isKnownEnum {
+ err = builder.AddSimpleField(jsonFieldName, field, TypeOptions{
+ TSType: getStructFQN(field.Type.String()),
+ TSTransform: fldOpts.TSTransform,
+ })
+ } else {
+ err = builder.AddSimpleField(jsonFieldName, field, fldOpts)
+ }
}
if err != nil {
return "", err
diff --git a/v2/pkg/commands/bindings/bindings.go b/v2/pkg/commands/bindings/bindings.go
index 1432acee1..310b1e9af 100644
--- a/v2/pkg/commands/bindings/bindings.go
+++ b/v2/pkg/commands/bindings/bindings.go
@@ -21,6 +21,7 @@ type Options struct {
GoModTidy bool
TsPrefix string
TsSuffix string
+ TsOutputType string
}
// GenerateBindings generates bindings for the Wails project in the given ProjectDirectory.
@@ -65,6 +66,7 @@ func GenerateBindings(options Options) (string, error) {
env := os.Environ()
env = shell.SetEnv(env, "tsprefix", options.TsPrefix)
env = shell.SetEnv(env, "tssuffix", options.TsSuffix)
+ env = shell.SetEnv(env, "tsoutputtype", options.TsOutputType)
stdout, stderr, err = shell.RunCommandWithEnv(env, workingDirectory, filename)
if err != nil {
diff --git a/v2/pkg/commands/build/build.go b/v2/pkg/commands/build/build.go
index 2223bf575..62c08e910 100644
--- a/v2/pkg/commands/build/build.go
+++ b/v2/pkg/commands/build/build.go
@@ -219,12 +219,17 @@ func GenerateBindings(buildOptions *Options) error {
printBulletPoint("Generating bindings: ")
}
+ if buildOptions.ProjectData.Bindings.TsGeneration.OutputType == "" {
+ buildOptions.ProjectData.Bindings.TsGeneration.OutputType = "classes"
+ }
+
// Generate Bindings
output, err := bindings.GenerateBindings(bindings.Options{
- Tags: buildOptions.UserTags,
- GoModTidy: !buildOptions.SkipModTidy,
- TsPrefix: buildOptions.ProjectData.Bindings.TsGeneration.Prefix,
- TsSuffix: buildOptions.ProjectData.Bindings.TsGeneration.Suffix,
+ Tags: buildOptions.UserTags,
+ GoModTidy: !buildOptions.SkipModTidy,
+ TsPrefix: buildOptions.ProjectData.Bindings.TsGeneration.Prefix,
+ TsSuffix: buildOptions.ProjectData.Bindings.TsGeneration.Suffix,
+ TsOutputType: buildOptions.ProjectData.Bindings.TsGeneration.OutputType,
})
if err != nil {
return err
diff --git a/v2/pkg/options/options.go b/v2/pkg/options/options.go
index 088e7c46a..66d56ceaa 100644
--- a/v2/pkg/options/options.go
+++ b/v2/pkg/options/options.go
@@ -63,6 +63,7 @@ type App struct {
OnShutdown func(ctx context.Context) `json:"-"`
OnBeforeClose func(ctx context.Context) (prevent bool) `json:"-"`
Bind []interface{}
+ EnumBind []interface{}
WindowStartState WindowStartState
// ErrorFormatter overrides the formatting of errors returned by backend methods
diff --git a/website/docs/guides/application-development.mdx b/website/docs/guides/application-development.mdx
index 9d04fe917..78a6df3bc 100644
--- a/website/docs/guides/application-development.mdx
+++ b/website/docs/guides/application-development.mdx
@@ -144,6 +144,65 @@ func main() {
}
```
+Also you might want to use Enums in your structs and have models for them on frontend.
+In that case you should create array that will contain all possible enum values, instrument enum type and bind it to the app:
+
+```go {16-18} title="app.go"
+type Weekday string
+
+const (
+ Sunday Weekday = "Sunday"
+ Monday Weekday = "Monday"
+ Tuesday Weekday = "Tuesday"
+ Wednesday Weekday = "Wednesday"
+ Thursday Weekday = "Thursday"
+ Friday Weekday = "Friday"
+ Saturday Weekday = "Saturday"
+)
+
+var AllWeekdays = []struct {
+ Value Weekday
+ TSName string
+}{
+ {Sunday, "SUNDAY"},
+ {Monday, "MONDAY"},
+ {Tuesday, "TUESDAY"},
+ {Wednesday, "WEDNESDAY"},
+ {Thursday, "THURSDAY"},
+ {Friday, "FRIDAY"},
+ {Saturday, "SATURDAY"},
+}
+```
+
+In the main application configuration, the `EnumBind` key is where we can tell Wails what we want to bind enums as well:
+
+```go {11-13} title="main.go"
+func main() {
+
+ app := NewApp()
+
+ err := wails.Run(&options.App{
+ Title: "My App",
+ Width: 800,
+ Height: 600,
+ OnStartup: app.startup,
+ OnShutdown: app.shutdown,
+ Bind: []interface{}{
+ app,
+ },
+ EnumBind: []interface{}{
+ AllWeekdays,
+ },
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+```
+
+This will add missing enums to your `model.ts` file.
+
More information on Binding can be found [here](../howdoesitwork.mdx#method-binding).
## Application Menu
diff --git a/website/docs/howdoesitwork.mdx b/website/docs/howdoesitwork.mdx
index e9f2c6e3d..6e23d5eb9 100644
--- a/website/docs/howdoesitwork.mdx
+++ b/website/docs/howdoesitwork.mdx
@@ -206,6 +206,57 @@ You may bind as many structs as you like. Just make sure you create an instance
```
+You may bind enums types as well.
+In that case you should create array that will contain all possible enum values, instrument enum type and bind it to the app via `EnumBind`:
+
+```go {16-18} title="app.go"
+type Weekday string
+
+const (
+ Sunday Weekday = "Sunday"
+ Monday Weekday = "Monday"
+ Tuesday Weekday = "Tuesday"
+ Wednesday Weekday = "Wednesday"
+ Thursday Weekday = "Thursday"
+ Friday Weekday = "Friday"
+ Saturday Weekday = "Saturday"
+)
+
+var AllWeekdays = []struct {
+ Value Weekday
+ TSName string
+}{
+ {Sunday, "SUNDAY"},
+ {Monday, "MONDAY"},
+ {Tuesday, "TUESDAY"},
+ {Wednesday, "WEDNESDAY"},
+ {Thursday, "THURSDAY"},
+ {Friday, "FRIDAY"},
+ {Saturday, "SATURDAY"},
+}
+```
+
+```go {10-12}
+ //...
+ err := wails.Run(&options.App{
+ Title: "Basic Demo",
+ Width: 1024,
+ Height: 768,
+ AssetServer: &assetserver.Options{
+ Assets: assets,
+ },
+ Bind: []interface{}{
+ app,
+ &mystruct1{},
+ &mystruct2{},
+ },
+ EnumBind: []interface{}{
+ AllWeekdays,
+ },
+ })
+
+```
+
When you run `wails dev` (or `wails generate module`), a frontend module will be generated containing the following:
- JavaScript bindings for all bound methods
diff --git a/website/docs/reference/options.mdx b/website/docs/reference/options.mdx
index a2a193277..caa2d0f80 100644
--- a/website/docs/reference/options.mdx
+++ b/website/docs/reference/options.mdx
@@ -58,7 +58,14 @@ func main() {
Bind: []interface{}{
app,
},
+ EnumBind: []interface{}{
+ AllWeekdays,
+ },
ErrorFormatter: func(err error) any { return err.Error() },
+ SingleInstanceLock: &options.SingleInstanceLock{
+ UniqueId: "c9c8fd93-6758-4144-87d1-34bdb0a8bd60",
+ OnSecondInstanceLaunch: app.onSecondInstanceLaunch,
+ },
Windows: &windows.Options{
WebviewIsTransparent: false,
WindowIsTranslucent: false,
@@ -92,6 +99,8 @@ func main() {
FullSizeContent: false,
UseToolbar: false,
HideToolbarSeparator: true,
+ OnFileOpen: app.onFileOpen,
+ OnUrlOpen: app.onUrlOpen,
},
Appearance: mac.NSAppearanceNameDarkAqua,
WebviewIsTransparent: true,
@@ -479,6 +488,13 @@ A slice of struct instances defining methods that need to be bound to the fronte
Name: Bind
Type: `[]interface{}`
+### EnumBind
+
+A slice of Enum arrays that need to be bound to the frontend.
+
+Name: EnumBind
+Type: `[]interface{}`
+
### ErrorFormatter
A function that determines how errors are formatted when returned by a JS-to-Go
@@ -487,6 +503,28 @@ method call. The returned value will be marshalled as JSON.
Name: ErrorFormatter
Type: `func (error) any`
+### SingleInstanceLock
+
+Enables single instance locking. This means that only one instance of your application can be running at a time.
+
+Name: SingleInstanceLock
+Type: `*options.SingleInstanceLock`
+
+#### UniqueId
+
+This id is used to generate the mutex name on Windows and macOS and the dbus name on Linux. Use a UUID to ensure that the id is unique.
+
+Name: UniqueId
+Type: `string`
+
+#### OnSecondInstanceLaunch
+
+Callback that is called when a second instance of your app is launched.
+
+Name: OnSecondInstanceLaunch
+Type: `func(secondInstanceData SecondInstanceData)`
+
+
### Windows
This defines [Windows specific options](#windows).
@@ -797,6 +835,20 @@ with [WebviewIsTransparent](#WebviewIsTransparent) to make frosty-looking applic
Name: WindowIsTranslucent
Type: `bool`
+#### OnFileOpen
+
+Callback that is called when a file is opened with the application.
+
+Name: OnFileOpen
+Type: `func(filePath string)`
+
+#### OnUrlOpen
+
+Callback that is called when a URL is opened with the application.
+
+Name: OnUrlOpen
+Type: `func(filePath string)`
+
#### Preferences
The Preferences struct provides the ability to configure the Webview preferences.
@@ -804,7 +856,7 @@ The Preferences struct provides the ability to configure the Webview preferences
Name: Preferences
Type: [`*mac.Preferences`](#preferences-struct)
-##### Preferences struct
+##### Preferences struct
You can specify the webview preferences.
@@ -812,7 +864,7 @@ You can specify the webview preferences.
type Preferences struct {
TabFocusesLinks u.Bool
TextInteractionEnabled u.Bool
- FullscreenEnabled u.Bool
+ FullscreenEnabled u.Bool
}
```
diff --git a/website/docs/reference/project-config.mdx b/website/docs/reference/project-config.mdx
index 8e763502b..3a6f09495 100644
--- a/website/docs/reference/project-config.mdx
+++ b/website/docs/reference/project-config.mdx
@@ -73,14 +73,52 @@ The project config resides in the `wails.json` file in the project directory. Th
// The copyright of the product. Default: 'Copyright.........'
"copyright": "",
// A short comment of the app. Default: 'Built using Wails (https://wails.app)'
- "comments": ""
+ "comments": "",
+ // File associations for the app
+ "fileAssociations": [
+ {
+ // The extension (minus the leading period). e.g. png
+ "ext": "wails",
+ // The name. e.g. PNG File
+ "name": "Wails",
+ // Windows-only. The description. It is displayed on the `Type` column on Windows Explorer.
+ "description": "Wails file",
+ // The icon name without extension. Icons should be located in build folder. Proper icons will be generated from .png file for both macOS and Windows)
+ "iconName": "fileIcon",
+ // macOS-only. The app’s role with respect to the type. Corresponds to CFBundleTypeRole.
+ "role": "Editor"
+ },
+ ],
+ // Custom URI protocols that should be opened by the application
+ "protocols": [
+ {
+ // protocol scheme. e.g. myapp
+ "scheme": "myapp",
+ // Windows-only. The description. It is displayed on the `Type` column on Windows Explorer.
+ "description": "Myapp protocol",
+ // macOS-only. The app’s role with respect to the type. Corresponds to CFBundleTypeRole.
+ "role": "Editor"
+ }
+ ]
},
// 'multiple': One installer per architecture. 'single': Single universal installer for all architectures being built. Default: 'multiple'
"nsisType": "",
// Whether the app should be obfuscated. Default: false
"obfuscated": "",
// The arguments to pass to the garble command when using the obfuscated flag
- "garbleargs": ""
+ "garbleargs": "",
+ // Bindings configurations
+ "bindings": {
+ // model.ts file generation config
+ "ts_generation": {
+ // All generated JavaScript entities will be prefixed with this value
+ "prefix": "",
+ // All generated JavaScript entities will be suffixed with this value
+ "suffix": "",
+ // Type of output to generate (classes|interfaces)
+ "outputType": "classes",
+ }
+ }
}
```
diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx
index 8b65b5490..c6d637506 100644
--- a/website/src/pages/changelog.mdx
+++ b/website/src/pages/changelog.mdx
@@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added CPU/GPU/Memory detection for `wails doctor`. Added by @leaanthony in #d51268b8d0680430f3a614775b13e6cd2b906d1c
- The [AssetServer](/docs/reference/options#assetserver) now injects the runtime/IPC into all index html files and into all html files returned when requesting a folder path. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2203)
- Added Custom Protocol Schemes associations support for macOS and Windows. Added by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/3000)
+– Added support for TS interfaces generation as an option. Add support for Enums in TS types. Added by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/3047)
### Changed
diff --git a/website/static/schemas/config.v2.json b/website/static/schemas/config.v2.json
index f215415e6..7a6d26f15 100644
--- a/website/static/schemas/config.v2.json
+++ b/website/static/schemas/config.v2.json
@@ -251,6 +251,33 @@
"garbleargs": {
"type": "string",
"description": "The arguments to pass to the garble command when using the obfuscated flag"
+ },
+ "bindings": {
+ "type": "object",
+ "description": "Bindings configurations",
+ "properties": {
+ "ts_generation": {
+ "type": "object",
+ "description": "model.ts file generation config",
+ "properties": {
+ "prefix": {
+ "type": "string",
+ "description": "All generated JavaScript entities will be prefixed with this value"
+ },
+ "suffix": {
+ "type": "string",
+ "description": "All generated JavaScript entities will be suffixed with this value"
+ },
+ "outputType": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/BindingsOutputTypes"
+ }
+ ]
+ }
+ }
+ }
+ }
}
},
"dependencies": {
@@ -346,26 +373,25 @@
]
}
]
- }
- },
- "bindings": {
- "type": "object",
- "description": "Bindings configurations",
- "properties": {
- "ts_generation": {
- "type": "object",
- "description": "model.ts file generation config",
- "properties": {
- "prefix": {
- "type": "string",
- "description": "All generated JavaScript entities will be prefixed with this value"
- },
- "suffix": {
- "type": "string",
- "description": "All generated JavaScript entities will be suffixed with this value"
- }
+ },
+ "BindingsOutputTypes": {
+ "description": "Type of output to generate",
+ "oneOf": [
+ {
+ "description": "Classes",
+ "type": "string",
+ "enum": [
+ "classes"
+ ]
+ },
+ {
+ "description": "Interfaces",
+ "type": "string",
+ "enum": [
+ "interfaces"
+ ]
}
- }
+ ]
}
}
}