diff --git a/v2/cmd/wails/generate.go b/v2/cmd/wails/generate.go index 159df90a1b5..05290051987 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 d079790aa7f..be031819c65 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 51e3e63ba1c..58cd94ef0e0 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 96ed84e30c0..4c217b17c4a 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 d911e12a3d7..568e11b0318 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 2309d6daf32..b37334ec325 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 00000000000..5b5b4419edb --- /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 837d5fad338..94941d0a390 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 c2e35191517..6d643b92d5a 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 6b99d43bed3..e7080c6940e 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 d2c5349c58a..b627772fe18 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 8e7c7ca6de9..498c5976ceb 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/db.go b/v2/internal/binding/db.go index 1fc7e8c662e..f7b793839d8 100644 --- a/v2/internal/binding/db.go +++ b/v2/internal/binding/db.go @@ -2,7 +2,6 @@ package binding import ( "encoding/json" - "sort" "sync" "unsafe" ) @@ -17,17 +16,22 @@ type DB struct { methodMap map[string]*BoundMethod // This uses ids to reference bound methods at runtime - obfuscatedMethodMap map[int]*BoundMethod + obfuscatedMethodArray []*ObfuscatedMethod // Lock to ensure sync access to the data lock sync.RWMutex } +type ObfuscatedMethod struct { + method *BoundMethod + methodName string +} + func newDB() *DB { return &DB{ - store: make(map[string]map[string]map[string]*BoundMethod), - methodMap: make(map[string]*BoundMethod), - obfuscatedMethodMap: make(map[int]*BoundMethod), + store: make(map[string]map[string]map[string]*BoundMethod), + methodMap: make(map[string]*BoundMethod), + obfuscatedMethodArray: []*ObfuscatedMethod{}, } } @@ -65,7 +69,11 @@ func (d *DB) GetObfuscatedMethod(id int) *BoundMethod { d.lock.RLock() defer d.lock.RUnlock() - return d.obfuscatedMethodMap[id] + if len(d.obfuscatedMethodArray) <= id { + return nil + } + + return d.obfuscatedMethodArray[id].method } // AddMethod adds the given method definition to the db using the given qualified path: packageName.structName.methodName @@ -96,6 +104,7 @@ func (d *DB) AddMethod(packageName string, structName string, methodName string, // Store in the methodMap key := packageName + "." + structName + "." + methodName d.methodMap[key] = methodDefinition + d.obfuscatedMethodArray = append(d.obfuscatedMethodArray, &ObfuscatedMethod{method: methodDefinition, methodName: key}) } // ToJSON converts the method map to JSON @@ -117,17 +126,9 @@ func (d *DB) ToJSON() (string, error) { func (d *DB) UpdateObfuscatedCallMap() map[string]int { mappings := make(map[string]int) - // Iterate map keys and sort them - keys := make([]string, 0, len(d.methodMap)) - for k := range d.methodMap { - keys = append(keys, k) + for id, k := range d.obfuscatedMethodArray { + mappings[k.methodName] = id } - sort.Strings(keys) - // Iterate sorted keys and add to obfuscated method map - for id, k := range keys { - mappings[k] = id - d.obfuscatedMethodMap[id] = d.methodMap[k] - } return mappings } diff --git a/v2/internal/binding/generate_test.go b/v2/internal/binding/generate_test.go index 565fba31caa..8d6a833b817 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 34cbe88da59..0d84f18552f 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 bb72e6fb8b5..c06a8b2ec79 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 1432acee11c..310b1e9afc0 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 2223bf575f0..62c08e910d1 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 088e7c46ab8..66d56ceaa5e 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/community/showcase/snippetexpander.mdx b/website/docs/community/showcase/snippetexpander.mdx new file mode 100644 index 00000000000..1f9fb615796 --- /dev/null +++ b/website/docs/community/showcase/snippetexpander.mdx @@ -0,0 +1,27 @@ +# Snippet Expander + +```mdx-code-block +
+
+
+ Screenshot of Snippet Expander's Select Snippet window
+
+
+
+ Screenshot of Snippet Expander's Add Snippet screen
+
+
+
+ Screenshot of Snippet Expander's Search & Paste window
+