mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-01 23:19:24 +08:00
Add support for interface generation and enums (#3047)
* Add support to output ts models as interfaces * Add support to generate enums from golang * cleanup logs * add missing documentation * fix package names for enum. Fix processing enums that are in separate packages * revert golang 1.21 * Fix spelling * Add support for simplified version of Enum for typescriptify * update docs * removed unused logs * Add tests. Fix imported enums types in models --------- Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
This commit is contained in:
parent
929dfb4123
commit
b9de31e38e
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 := ""
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
}
|
||||
|
||||
}
|
||||
`,
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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"},
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
`,
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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{}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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<br/>
|
||||
Type: `[]interface{}`
|
||||
|
||||
### EnumBind
|
||||
|
||||
A slice of Enum arrays that need to be bound to the frontend.
|
||||
|
||||
Name: EnumBind<br/>
|
||||
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<br/>
|
||||
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<br/>
|
||||
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<br/>
|
||||
Type: `string`
|
||||
|
||||
#### OnSecondInstanceLaunch
|
||||
|
||||
Callback that is called when a second instance of your app is launched.
|
||||
|
||||
Name: OnSecondInstanceLaunch<br/>
|
||||
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<br/>
|
||||
Type: `bool`
|
||||
|
||||
#### OnFileOpen
|
||||
|
||||
Callback that is called when a file is opened with the application.
|
||||
|
||||
Name: OnFileOpen<br/>
|
||||
Type: `func(filePath string)`
|
||||
|
||||
#### OnUrlOpen
|
||||
|
||||
Callback that is called when a URL is opened with the application.
|
||||
|
||||
Name: OnUrlOpen<br/>
|
||||
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<br/>
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user