Merge branch 'chore/move_assetserver_to_pkg' into future/exp

This commit is contained in:
Lea Anthony 2022-12-31 13:08:34 +11:00
commit 71fb7fadff
No known key found for this signature in database
GPG key ID: 33DAF7BB90A58405
973 changed files with 69218 additions and 825 deletions

View file

@ -107,9 +107,11 @@ func buildApplication(f *flags.Build) error {
return err
}
err = gomod.SyncGoMod(logger, f.UpdateWailsVersionGoMod)
if err != nil {
return err
if !f.NoSyncGoMod {
err = gomod.SyncGoMod(logger, f.UpdateWailsVersionGoMod)
if err != nil {
return err
}
}
// Check platform

View file

@ -8,6 +8,7 @@ type BuildCommon struct {
SkipFrontend bool `name:"s" description:"Skips building the frontend"`
Verbosity int `name:"v" description:"Verbosity level (0 = quiet, 1 = normal, 2 = verbose)"`
Tags string `description:"Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated"`
NoSyncGoMod bool `description:"Don't sync go.mod"`
}
func (c BuildCommon) Default() BuildCommon {

View file

@ -4,10 +4,6 @@ import (
"context"
"errors"
"fmt"
"github.com/samber/lo"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
"io"
"net/http"
"net/url"
@ -22,7 +18,11 @@ import (
"syscall"
"time"
"github.com/wailsapp/wails/v2/pkg/commands/bindings"
"github.com/samber/lo"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"github.com/google/shlex"
@ -75,7 +75,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
cwd := lo.Must(os.Getwd())
// Update go.mod to use current wails version
err := gomod.SyncGoMod(logger, true)
err := gomod.SyncGoMod(logger, !f.NoSyncGoMod)
if err != nil {
return err
}
@ -96,30 +96,8 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
buildOptions.UserTags = userTags
err = build.CreateEmbedDirectories(cwd, buildOptions)
if err != nil {
return err
}
projectConfig := f.ProjectConfig()
if !buildOptions.SkipBindings {
if f.Verbosity == build.VERBOSE {
logutils.LogGreen("Generating Bindings...")
}
stdout, err := bindings.GenerateBindings(bindings.Options{
Tags: buildOptions.UserTags,
TsPrefix: projectConfig.Bindings.TsGeneration.Prefix,
TsSuffix: projectConfig.Bindings.TsGeneration.Suffix,
})
if err != nil {
return err
}
if f.Verbosity == build.VERBOSE {
logutils.LogGreen(stdout)
}
}
// Setup signal handler
quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)

View file

@ -2,13 +2,15 @@ package gomod
import (
"fmt"
"os"
"strings"
"github.com/wailsapp/wails/v2/cmd/wails/internal"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/gomod"
"github.com/wailsapp/wails/v2/internal/goversion"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"os"
)
func SyncGoMod(logger *clilogger.CLILogger, updateWailsVersion bool) error {
@ -32,12 +34,13 @@ func SyncGoMod(logger *clilogger.CLILogger, updateWailsVersion bool) error {
LogGreen("Updated go.mod to use Go '%s'", goversion.MinRequirement)
}
if outOfSync, err := gomod.GoModOutOfSync(gomodData, internal.Version); err != nil {
internalVersion := strings.TrimSpace(internal.Version)
if outOfSync, err := gomod.GoModOutOfSync(gomodData, internalVersion); err != nil {
return err
} else if outOfSync {
if updateWailsVersion {
LogGreen("Updating go.mod to use Wails '%s'", internal.Version)
gomodData, err = gomod.UpdateGoModVersion(gomodData, internal.Version)
LogGreen("Updating go.mod to use Wails '%s'", internalVersion)
gomodData, err = gomod.UpdateGoModVersion(gomodData, internalVersion)
if err != nil {
return err
}

View file

@ -1 +1 @@
v2.2.0
v2.3.1

View file

@ -12,8 +12,9 @@ import (
"os"
"path/filepath"
"github.com/wailsapp/wails/v2/pkg/assetserver"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
"github.com/wailsapp/wails/v2/internal/frontend/devserver"
"github.com/wailsapp/wails/v2/internal/frontend/dispatcher"

View file

@ -0,0 +1,65 @@
package binding_test
type StructWithAnonymousSubMultiLevelStruct struct {
Name string `json:"name"`
Meta struct {
Age int `json:"age"`
More struct {
Info string `json:"info"`
MoreInMore struct {
Demo string `json:"demo"`
} `json:"more_in_more"`
} `json:"more"`
} `json:"meta"`
}
func (s StructWithAnonymousSubMultiLevelStruct) Get() StructWithAnonymousSubMultiLevelStruct {
return s
}
var AnonymousSubStructMultiLevelTest = BindingTest{
name: "StructWithAnonymousSubMultiLevelStruct",
structs: []interface{}{
&StructWithAnonymousSubMultiLevelStruct{},
},
exemptions: nil,
shouldError: false,
want: `
export namespace binding_test {
export class StructWithAnonymousSubMultiLevelStruct {
name: string;
// Go type: struct { Age int "json:\"age\""; More struct { Info string "json:\"info\""; MoreInMore struct { Demo string "json:\"demo\"" } "json:\"more_in_more\"" } "json:\"more\"" }
meta: any;
static createFrom(source: any = {}) {
return new StructWithAnonymousSubMultiLevelStruct(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.meta = this.convertValues(source["meta"], Object);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
`,
}

View file

@ -0,0 +1,59 @@
package binding_test
type StructWithAnonymousSubStruct struct {
Name string `json:"name"`
Meta struct {
Age int `json:"age"`
} `json:"meta"`
}
func (s StructWithAnonymousSubStruct) Get() StructWithAnonymousSubStruct {
return s
}
var AnonymousSubStructTest = BindingTest{
name: "StructWithAnonymousSubStruct",
structs: []interface{}{
&StructWithAnonymousSubStruct{},
},
exemptions: nil,
shouldError: false,
want: `
export namespace binding_test {
export class StructWithAnonymousSubStruct {
name: string;
// Go type: struct { Age int "json:\"age\"" }
meta: any;
static createFrom(source: any = {}) {
return new StructWithAnonymousSubStruct(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.meta = this.convertValues(source["meta"], Object);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
`,
}

View file

@ -27,7 +27,7 @@ export namespace binding_test {
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.empty = this.convertValues(source["empty"], null);
this.empty = this.convertValues(source["empty"], Object);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {

View file

@ -0,0 +1,59 @@
package binding_test
import (
"io/fs"
"os"
"testing"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger"
)
const expectedPromiseBindings = `// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function ErrorReturn(arg1:number):Promise<void>;
export function NoReturn(arg1:string):Promise<void>;
export function SingleReturn(arg1:any):Promise<number>;
export function SingleReturnWithError(arg1:number):Promise<string>;
export function TwoReturn(arg1:any):Promise<string|number>;
`
type PromisesTest struct{}
func (h *PromisesTest) NoReturn(_ string) {}
func (h *PromisesTest) ErrorReturn(_ int) error { return nil }
func (h *PromisesTest) SingleReturn(_ interface{}) int { return 0 }
func (h *PromisesTest) SingleReturnWithError(_ int) (string, error) { return "", nil }
func (h *PromisesTest) TwoReturn(_ interface{}) (string, int) { return "", 0 }
func TestPromises(t *testing.T) {
// given
generationDir := t.TempDir()
// setup
testLogger := &logger.Logger{}
b := binding.NewBindings(testLogger, []interface{}{&PromisesTest{}}, []interface{}{}, false)
// then
err := b.GenerateGoBindings(generationDir)
if err != nil {
t.Fatalf("could not generate the Go bindings: %v", err)
}
// then
rawGeneratedBindings, err := fs.ReadFile(os.DirFS(generationDir), "binding_test/PromisesTest.d.ts")
if err != nil {
t.Fatalf("could not read the generated bindings: %v", err)
}
// then
generatedBindings := string(rawGeneratedBindings)
if generatedBindings != expectedPromiseBindings {
t.Fatalf("the generated bindings does not match the expected ones.\nWanted:\n%s\n\nGot:\n%s", expectedPromiseBindings, generatedBindings)
}
}

View file

@ -37,6 +37,8 @@ func TestBindings_GenerateModels(t *testing.T) {
MultistructTest,
EmptyStructTest,
GeneratedJsEntityTest,
AnonymousSubStructTest,
AnonymousSubStructMultiLevelTest,
}
testLogger := &logger.Logger{}

View file

@ -77,21 +77,27 @@ func (b *Bindings) GenerateGoBindings(baseDir string) error {
args.Add(arg + ":" + goTypeToTypescriptType(input.TypeName, &importNamespaces))
}
tsBody.WriteString(args.Join(",") + "):")
returnType := "Promise"
if methodDetails.OutputCount() > 0 {
// now build Typescript return types
// If there is no return value or only returning error, TS returns Promise<void>
// If returning single value, TS returns Promise<type>
// If returning single value or error, TS returns Promise<type>
// If returning two values, TS returns Promise<type1|type2>
// Otherwise, TS returns Promise<type1> (instead of throwing Go error?)
var returnType string
if methodDetails.OutputCount() == 0 {
returnType = "Promise<void>"
} else if methodDetails.OutputCount() == 1 && methodDetails.Outputs[0].TypeName == "error" {
returnType = "Promise<void>"
} else {
outputTypeName := entityFullReturnType(methodDetails.Outputs[0].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
firstType := goTypeToTypescriptType(outputTypeName, &importNamespaces)
returnType += "<" + firstType
if methodDetails.OutputCount() == 2 {
if methodDetails.Outputs[1].TypeName != "error" {
outputTypeName = entityFullReturnType(methodDetails.Outputs[1].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
secondType := goTypeToTypescriptType(outputTypeName, &importNamespaces)
returnType += "|" + secondType
}
returnType = "Promise<" + firstType
if methodDetails.OutputCount() == 2 && methodDetails.Outputs[1].TypeName != "error" {
outputTypeName = entityFullReturnType(methodDetails.Outputs[1].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
secondType := goTypeToTypescriptType(outputTypeName, &importNamespaces)
returnType += "|" + secondType
}
returnType += ">"
} else {
returnType = "Promise<void>"
}
tsBody.WriteString(returnType + ";\n")
}

View file

@ -48,7 +48,10 @@ const bool IsFullScreen(void *ctx);
const bool IsMinimised(void *ctx);
const bool IsMaximised(void *ctx);
void ProcessURLResponse(void *inctx, unsigned long long requestId, int statusCode, void *headersString, int headersStringLength, void* data, int datalength);
void ProcessURLDidReceiveResponse(void *inctx, unsigned long long requestId, int statusCode, void *headersString, int headersStringLength);
bool ProcessURLDidReceiveData(void *inctx, unsigned long long requestId, void* data, int datalength);
void ProcessURLDidFinish(void *inctx, unsigned long long requestId);
int ProcessURLRequestReadBodyStream(void *inctx, unsigned long long requestId, void *buf, int bufLen);
/* Dialogs */

View file

@ -52,12 +52,33 @@ WailsContext* Create(const char* title, int width, int height, int frameless, in
return result;
}
void ProcessURLResponse(void *inctx, unsigned long long requestId, int statusCode, void *headersString, int headersStringLength, void* data, int datalength) {
void ProcessURLDidReceiveResponse(void *inctx, unsigned long long requestId, int statusCode, void *headersString, int headersStringLength) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
@autoreleasepool {
NSData *nsHeadersJSON = [NSData dataWithBytes:headersString length:headersStringLength];
[ctx processURLDidReceiveResponse:requestId :statusCode :nsHeadersJSON];
}
}
bool ProcessURLDidReceiveData(void *inctx, unsigned long long requestId, void* data, int datalength) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
@autoreleasepool {
NSData *nsdata = [NSData dataWithBytes:data length:datalength];
[ctx processURLResponse:requestId :statusCode :nsHeadersJSON :nsdata];
return [ctx processURLDidReceiveData:requestId :nsdata];
}
}
void ProcessURLDidFinish(void *inctx, unsigned long long requestId) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
@autoreleasepool {
[ctx processURLDidFinish:requestId];
}
}
int ProcessURLRequestReadBodyStream(void *inctx, unsigned long long requestId, void *buf, int bufLen) {
WailsContext *ctx = (__bridge WailsContext*) inctx;
@autoreleasepool {
return [ctx processURLRequestReadBodyStream:requestId :buf :bufLen];
}
}

View file

@ -29,7 +29,7 @@
- (void) disableWindowConstraints;
@end
@interface WailsContext : NSObject <WKURLSchemeHandler,WKScriptMessageHandler,WKNavigationDelegate>
@interface WailsContext : NSObject <WKURLSchemeHandler,WKScriptMessageHandler,WKNavigationDelegate,WKUIDelegate>
@property (retain) WailsWindow* mainWindow;
@property (retain) WKWebView* webview;
@ -89,7 +89,10 @@
- (void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters;
- (void) loadRequest:(NSString*)url;
- (void) processURLResponse:(unsigned long long)requestId :(int)statusCode :(NSData *)headersString :(NSData*)data;
- (void) processURLDidReceiveResponse:(unsigned long long)requestId :(int)statusCode :(NSData *)headersJSON;
- (bool) processURLDidReceiveData:(unsigned long long)requestId :(NSData *)data;
- (void) processURLDidFinish:(unsigned long long)requestId;
- (int) processURLRequestReadBodyStream:(unsigned long long)requestId :(void *)buf :(int)bufLen;
- (void) ExecJS:(NSString*)script;
- (NSScreen*) getCurrentScreen;

View file

@ -15,6 +15,8 @@
#import "message.h"
#import "Role.h"
typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
@implementation WailsWindow
- (BOOL)canBecomeKeyWindow
@ -107,7 +109,6 @@
[self.mouseEvent release];
[self.userContentController release];
[self.urlRequests release];
[self.urlRequestsLock release];
[self.applicationMenu release];
[super dealloc];
}
@ -138,7 +139,6 @@
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString*)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight {
self.urlRequestsId = 0;
self.urlRequestsLock = [NSLock new];
self.urlRequests = [NSMutableDictionary new];
NSWindowStyleMask styleMask = 0;
@ -250,7 +250,8 @@
}
[self.webview setNavigationDelegate:self];
self.webview.UIDelegate = self;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:FALSE forKey:@"NSAutomaticQuoteSubstitutionEnabled"];
@ -407,37 +408,76 @@
[self.webview evaluateJavaScript:script completionHandler:nil];
}
- (void) processURLResponse:(unsigned long long)requestId :(int)statusCode :(NSData *)headersJSON :(NSData *)data {
NSNumber *key = [NSNumber numberWithUnsignedLongLong:requestId];
[self.urlRequestsLock lock];
id<WKURLSchemeTask> urlSchemeTask = self.urlRequests[key];
[self.urlRequestsLock unlock];
- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters
initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * URLs))completionHandler {
@try {
if (urlSchemeTask == nil) {
return;
}
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection;
if (@available(macOS 10.14, *)) {
openPanel.canChooseDirectories = parameters.allowsDirectories;
}
[openPanel
beginSheetModalForWindow:webView.window
completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK)
completionHandler(openPanel.URLs);
else
completionHandler(nil);
}];
}
- (void) processURLDidReceiveResponse:(unsigned long long)requestId :(int)statusCode :(NSData *)headersJSON {
[self processURLSchemeTaskCall:requestId :^(id<WKURLSchemeTask> urlSchemeTask) {
NSDictionary *headerFields = [NSJSONSerialization JSONObjectWithData: headersJSON options: NSJSONReadingMutableContainers error: nil];
NSHTTPURLResponse *response = [[[NSHTTPURLResponse alloc] initWithURL:urlSchemeTask.request.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:headerFields] autorelease];
@try {
[urlSchemeTask didReceiveResponse:response];
[urlSchemeTask didReceiveData:data];
[urlSchemeTask didFinish];
} @catch (NSException *exception) {
// This is very bad to detect a stopped schemeTask this should be implemented in a better way
// See todo in stopURLSchemeTask...
if (![exception.reason isEqualToString: @"This task has already been stopped"]) {
@throw exception;
[urlSchemeTask didReceiveResponse:response];
}];
}
- (bool) processURLDidReceiveData:(unsigned long long)requestId :(NSData *)data {
return [self processURLSchemeTaskCall:requestId :^(id<WKURLSchemeTask> urlSchemeTask) {
[urlSchemeTask didReceiveData:data];
}];
}
- (void) processURLDidFinish:(unsigned long long)requestId {
[self processURLSchemeTaskCall:requestId :^(id<WKURLSchemeTask> urlSchemeTask) {
[urlSchemeTask didFinish];
}];
NSNumber *key = [NSNumber numberWithUnsignedLongLong:requestId];
[self removeURLSchemeTask:key];
}
- (int) processURLRequestReadBodyStream:(unsigned long long)requestId :(void *)buf :(int)bufLen {
int res = 0;
int *pRes = &res;
bool hasRequest = [self processURLSchemeTaskCall:requestId :^(id<WKURLSchemeTask> urlSchemeTask) {
NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream;
if (!stream) {
*pRes = -3;
} else {
NSStreamStatus status = stream.streamStatus;
if (status == NSStreamStatusAtEnd) {
*pRes = 0;
} else if (status != NSStreamStatusOpen) {
*pRes = -4;
} else if (!stream.hasBytesAvailable) {
*pRes = 0;
} else {
*pRes = [stream read:buf maxLength:bufLen];
}
}
} @finally {
[self.urlRequestsLock lock];
[self.urlRequests removeObjectForKey:key]; // This will release the urlSchemeTask which was retained from the dictionary
[self.urlRequestsLock unlock];
}];
if (!hasRequest) {
res = -2;
}
return res;
}
- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
@ -447,6 +487,7 @@
const char *headerJSON = "";
const void *body = nil;
int bodyLen = 0;
int hasBodyStream = 0;
NSData *headers = [NSJSONSerialization dataWithJSONObject: urlSchemeTask.request.allHTTPHeaderFields options:0 error: nil];
if (headers) {
@ -457,23 +498,85 @@
if (urlSchemeTask.request.HTTPBody) {
body = urlSchemeTask.request.HTTPBody.bytes;
bodyLen = urlSchemeTask.request.HTTPBody.length;
} else {
// TODO handle HTTPBodyStream
} else if (urlSchemeTask.request.HTTPBodyStream) {
hasBodyStream = 1;
[urlSchemeTask.request.HTTPBodyStream open];
}
[self.urlRequestsLock lock];
self.urlRequestsId++;
unsigned long long requestId = self.urlRequestsId;
NSNumber *key = [NSNumber numberWithUnsignedLongLong:requestId];
self.urlRequests[key] = urlSchemeTask;
[self.urlRequestsLock unlock];
unsigned long long requestId;
@synchronized(self.urlRequests) {
self.urlRequestsId++;
requestId = self.urlRequestsId;
NSNumber *key = [NSNumber numberWithUnsignedLongLong:requestId];
self.urlRequests[key] = urlSchemeTask;
}
processURLRequest(self, requestId, url, method, headerJSON, body, bodyLen);
processURLRequest(self, requestId, url, method, headerJSON, body, bodyLen, hasBodyStream);
}
- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
// TODO implement the stopping process here in a better way...
// As soon as we introduce response body streaming we need to rewrite this nevertheless.
NSArray<NSNumber*> *keys;
@synchronized(self.urlRequests) {
keys = [self.urlRequests allKeys];
}
for (NSNumber *key in keys) {
if (self.urlRequests[key] == urlSchemeTask) {
[self removeURLSchemeTask:key];
}
}
}
- (void) removeURLSchemeTask:(NSNumber *)urlSchemeTaskKey {
id<WKURLSchemeTask> urlSchemeTask = nil;
@synchronized(self.urlRequests) {
urlSchemeTask = self.urlRequests[urlSchemeTaskKey];
}
if (!urlSchemeTask) {
return;
}
NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream;
if (stream) {
[stream close];
}
@synchronized(self.urlRequests) {
[self.urlRequests removeObjectForKey:urlSchemeTaskKey];
}
}
- (bool)processURLSchemeTaskCall:(unsigned long long)requestId :(schemeTaskCaller)fn {
NSNumber *key = [NSNumber numberWithUnsignedLongLong:requestId];
id<WKURLSchemeTask> urlSchemeTask;
@synchronized(self.urlRequests) {
urlSchemeTask = self.urlRequests[key];
}
if (urlSchemeTask == nil) {
// Stopped task, drop content...
return false;
}
@try {
fn(urlSchemeTask);
} @catch (NSException *exception) {
[self removeURLSchemeTask:key];
// This is very bad to detect a stopped schemeTask this should be implemented in a better way
// But it seems to be very tricky to not deadlock when keeping a lock curing executing fn()
// It seems like those call switch the thread back to the main thread and then deadlocks when they reentrant want
// to get the lock again to start another request or stop it.
if ([exception.reason isEqualToString: @"This task has already been stopped"]) {
return false;
}
@throw exception;
}
return true;
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {

View file

@ -14,22 +14,20 @@ package darwin
*/
import "C"
import (
"bytes"
"context"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"unsafe"
"github.com/wailsapp/wails/v2/pkg/assetserver"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
@ -37,7 +35,7 @@ import (
const startURL = "wails://wails/"
var messageBuffer = make(chan string, 100)
var requestBuffer = make(chan *request, 100)
var requestBuffer = make(chan *wkWebViewRequest, 100)
var callbackBuffer = make(chan uint, 10)
type Frontend struct {
@ -90,13 +88,17 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
} else {
appBindings.DB().UpdateObfuscatedCallMap()
}
assets, err := assetserver.NewAssetServerMainPage(ctx, bindings, appoptions)
assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, runtime.RuntimeAssetsBundle)
if err != nil {
log.Fatal(err)
}
result.assets = assets
go result.startRequestProcessor()
// Start 10 processors to handle requests in parallel
for i := 0; i < 10; i++ {
go result.startRequestProcessor()
}
}
go result.startMessageProcessor()
@ -342,8 +344,10 @@ func (f *Frontend) ExecJS(js string) {
f.mainWindow.ExecJS(js)
}
func (f *Frontend) processRequest(r *request) {
rw := httptest.NewRecorder()
func (f *Frontend) processRequest(r *wkWebViewRequest) {
rw := &wkWebViewResponseWriter{r: r}
defer rw.Close()
f.assets.ProcessHTTPRequest(
r.url,
rw,
@ -363,28 +367,6 @@ func (f *Frontend) processRequest(r *request) {
return req, nil
},
)
header := map[string]string{}
for k := range rw.Header() {
header[k] = rw.Header().Get(k)
}
headerData, _ := json.Marshal(header)
var content unsafe.Pointer
var contentLen int
if _contents := rw.Body.Bytes(); _contents != nil {
content = unsafe.Pointer(&_contents[0])
contentLen = len(_contents)
}
var headers unsafe.Pointer
var headersLen int
if len(headerData) != 0 {
headers = unsafe.Pointer(&headerData[0])
headersLen = len(headerData)
}
C.ProcessURLResponse(r.ctx, r.id, C.int(rw.Code), headers, C.int(headersLen), content, C.int(contentLen))
}
//func (f *Frontend) processSystemEvent(message string) {
@ -403,64 +385,12 @@ func (f *Frontend) processRequest(r *request) {
// }
//}
type request struct {
id C.ulonglong
url string
method string
headers string
body []byte
ctx unsafe.Pointer
}
func (r *request) GetHttpRequest() (*http.Request, error) {
var body io.Reader
if len(r.body) != 0 {
body = bytes.NewReader(r.body)
}
req, err := http.NewRequest(r.method, r.url, body)
if err != nil {
return nil, err
}
if r.headers != "" {
var h map[string]string
if err := json.Unmarshal([]byte(r.headers), &h); err != nil {
return nil, fmt.Errorf("Unable to unmarshal request headers: %s", err)
}
for k, v := range h {
req.Header.Add(k, v)
}
}
return req, nil
}
//export processMessage
func processMessage(message *C.char) {
goMessage := C.GoString(message)
messageBuffer <- goMessage
}
//export processURLRequest
func processURLRequest(ctx unsafe.Pointer, requestId C.ulonglong, url *C.char, method *C.char, headers *C.char, body unsafe.Pointer, bodyLen C.int) {
var goBody []byte
if body != nil && bodyLen != 0 {
goBody = C.GoBytes(body, bodyLen)
}
requestBuffer <- &request{
id: requestId,
url: C.GoString(url),
method: C.GoString(method),
headers: C.GoString(headers),
body: goBody,
ctx: ctx,
}
}
//export processCallback
func processCallback(callbackID uint) {
callbackBuffer <- callbackID

View file

@ -15,7 +15,7 @@ extern "C"
#endif
void processMessage(const char *);
void processURLRequest(void*, unsigned long long, const char *, const char *, const char *, const void *, int);
void processURLRequest(void*, unsigned long long, const char *, const char *, const char *, const void *, int, int);
void processMessageDialogResponse(int);
void processOpenFileDialogResponse(const char*);
void processSaveFileDialogResponse(const char*);

View file

@ -0,0 +1,106 @@
//go:build darwin
package darwin
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
#import "Application.h"
#include <stdlib.h>
*/
import "C"
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"unsafe"
)
//export processURLRequest
func processURLRequest(ctx unsafe.Pointer, requestId C.ulonglong, url *C.char, method *C.char, headers *C.char, body unsafe.Pointer, bodyLen C.int, hasBodyStream C.int) {
var bodyReader io.Reader
if body != nil && bodyLen != 0 {
bodyReader = bytes.NewReader(C.GoBytes(body, bodyLen))
} else if hasBodyStream != 0 {
bodyReader = &bodyStreamReader{id: requestId, ctx: ctx}
}
requestBuffer <- &wkWebViewRequest{
id: requestId,
url: C.GoString(url),
method: C.GoString(method),
headers: C.GoString(headers),
body: bodyReader,
ctx: ctx,
}
}
type wkWebViewRequest struct {
id C.ulonglong
url string
method string
headers string
body io.Reader
ctx unsafe.Pointer
}
func (r *wkWebViewRequest) GetHttpRequest() (*http.Request, error) {
req, err := http.NewRequest(r.method, r.url, r.body)
if err != nil {
return nil, err
}
if r.headers != "" {
var h map[string]string
if err := json.Unmarshal([]byte(r.headers), &h); err != nil {
return nil, fmt.Errorf("unable to unmarshal request headers: %s", err)
}
for k, v := range h {
req.Header.Add(k, v)
}
}
return req, nil
}
var _ io.Reader = &bodyStreamReader{}
type bodyStreamReader struct {
id C.ulonglong
ctx unsafe.Pointer
}
// Read implements io.Reader
func (r *bodyStreamReader) Read(p []byte) (n int, err error) {
var content unsafe.Pointer
var contentLen int
if p != nil {
content = unsafe.Pointer(&p[0])
contentLen = len(p)
}
res := C.ProcessURLRequestReadBodyStream(r.ctx, r.id, content, C.int(contentLen))
if res > 0 {
return int(res), nil
}
switch res {
case 0:
return 0, io.EOF
case -1:
return 0, fmt.Errorf("body: stream error")
case -2:
return 0, errRequestStopped
case -3:
return 0, fmt.Errorf("body: no stream defined")
case -4:
return 0, io.ErrClosedPipe
default:
return 0, fmt.Errorf("body: unknown error %d", res)
}
}

View file

@ -0,0 +1,81 @@
//go:build darwin
package darwin
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
#import "Application.h"
#include <stdlib.h>
*/
import "C"
import (
"encoding/json"
"errors"
"net/http"
"unsafe"
)
var (
errRequestStopped = errors.New("request has been stopped")
)
type wkWebViewResponseWriter struct {
r *wkWebViewRequest
header http.Header
wroteHeader bool
}
func (rw *wkWebViewResponseWriter) Header() http.Header {
if rw.header == nil {
rw.header = http.Header{}
}
return rw.header
}
func (rw *wkWebViewResponseWriter) Write(buf []byte) (int, error) {
rw.WriteHeader(http.StatusOK)
var content unsafe.Pointer
var contentLen int
if buf != nil {
content = unsafe.Pointer(&buf[0])
contentLen = len(buf)
}
if !C.ProcessURLDidReceiveData(rw.r.ctx, rw.r.id, content, C.int(contentLen)) {
return 0, errRequestStopped
}
return contentLen, nil
}
func (rw *wkWebViewResponseWriter) WriteHeader(code int) {
if rw.wroteHeader {
return
}
rw.wroteHeader = true
header := map[string]string{}
for k := range rw.Header() {
header[k] = rw.Header().Get(k)
}
headerData, _ := json.Marshal(header)
var headers unsafe.Pointer
var headersLen int
if len(headerData) != 0 {
headers = unsafe.Pointer(&headerData[0])
headersLen = len(headerData)
}
C.ProcessURLDidReceiveResponse(rw.r.ctx, rw.r.id, C.int(code), headers, C.int(headersLen))
}
func (rw *wkWebViewResponseWriter) Close() {
if !rw.wroteHeader {
rw.WriteHeader(http.StatusNotImplemented)
}
C.ProcessURLDidFinish(rw.r.ctx, rw.r.id)
}

View file

@ -88,9 +88,11 @@ import (
"text/template"
"unsafe"
"github.com/wailsapp/wails/v2/pkg/assetserver"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
wailsruntime "github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
@ -159,7 +161,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
} else {
appBindings.DB().UpdateObfuscatedCallMap()
}
assets, err := assetserver.NewAssetServerMainPage(ctx, bindings, appoptions)
assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle)
if err != nil {
log.Fatal(err)
}

View file

@ -16,7 +16,7 @@ import (
"strings"
"unsafe"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/pkg/assetserver"
)
const webkit2MinMinorVersion = 36

View file

@ -15,7 +15,7 @@ import (
"net/http"
"unsafe"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/pkg/assetserver"
)
const webkit2MinMinorVersion = 0

View file

@ -21,7 +21,7 @@ import (
"syscall"
"unsafe"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/pkg/assetserver"
)
type webKitResponseWriter struct {

View file

@ -165,7 +165,6 @@ float xroot = 0.0f;
float yroot = 0.0f;
int dragTime = -1;
uint mouseButton = 0;
bool contextMenuDisabled = false;
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, void* dummy)
{
@ -175,8 +174,8 @@ gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, void* dummy)
return FALSE;
}
mouseButton = event->button;
if( event->button == 3 && contextMenuDisabled ) {
return TRUE;
if( event->button == 3 ) {
return FALSE;
}
if (event->type == GDK_BUTTON_PRESS && event->button == 1)
@ -605,12 +604,15 @@ gboolean UnFullscreen(gpointer data) {
return G_SOURCE_REMOVE;
}
bool disableContextMenu(GtkWindow* window) {
// function to disable the context menu but propogate the event
gboolean disableContextMenu(GtkWidget *widget, WebKitContextMenu *context_menu, GdkEvent *event, WebKitHitTestResult *hit_test_result, gpointer data) {
// return true to disable the context menu
return TRUE;
}
void DisableContextMenu(void* webview) {
contextMenuDisabled = TRUE;
// Disable the context menu but propogate the event
g_signal_connect(WEBKIT_WEB_VIEW(webview), "context-menu", G_CALLBACK(disableContextMenu), NULL);
}

View file

@ -22,13 +22,14 @@ import (
"github.com/bep/debounce"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge"
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
wailsruntime "github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
"github.com/wailsapp/wails/v2/pkg/assetserver"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/windows"
)
@ -102,7 +103,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
appBindings.DB().UpdateObfuscatedCallMap()
}
assets, err := assetserver.NewAssetServerMainPage(ctx, bindings, appoptions)
assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle)
if err != nil {
log.Fatal(err)
}

View file

@ -1,4 +1,4 @@
//go:build windows && exp_gowebview2loader
//go:build windows && !native_webview2loader
package edge

View file

@ -1,4 +1,4 @@
//go:build windows && !exp_gowebview2loader
//go:build windows && native_webview2loader
package edge

View file

@ -1,4 +1,4 @@
//go:build windows && exp_gowebview2loader
//go:build windows && !native_webview2loader
package webviewloader
@ -13,7 +13,7 @@ import (
)
func init() {
fmt.Println("DEB | Using experimental go webview2loader")
fmt.Println("DEB | Using go webview2loader")
}
type webView2RunTimeType int32

View file

@ -1,4 +1,4 @@
//go:build exp_gowebview2loader
//go:build windows && !native_webview2loader
package webviewloader

View file

@ -1,4 +1,4 @@
//go:build windows && exp_gowebview2loader
//go:build windows && !native_webview2loader
package webviewloader

View file

@ -1,4 +1,4 @@
//go:build windows && exp_gowebview2loader
//go:build windows && !native_webview2loader
package webviewloader

View file

@ -1,4 +1,4 @@
//go:build windows && !exp_gowebview2loader
//go:build windows && native_webview2loader
package webviewloader

View file

@ -1,4 +1,4 @@
//go:build !exp_gowebview2loader
//go:build windows && native_webview2loader
package webviewloader

View file

@ -1,4 +1,4 @@
//go:build !exp_gowebview2loader
//go:build windows && native_webview2loader
package webviewloader

View file

@ -1,4 +1,4 @@
//go:build !exp_gowebview2loader
//go:build windows && native_webview2loader
package webviewloader

View file

@ -1,4 +1,4 @@
//go:build exp_gowebview2loader
//go:build windows && !native_webview2loader
package webviewloader

View file

@ -18,10 +18,13 @@ import (
"sync"
"time"
"github.com/wailsapp/wails/v2/pkg/assetserver"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/labstack/echo/v4"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/pkg/options"
@ -61,6 +64,8 @@ func (d *DevWebServer) Run(ctx context.Context) error {
var assetHandler http.Handler
var wsHandler http.Handler
var myLogger *logger.Logger
_fronendDevServerURL, _ := ctx.Value("frontenddevserverurl").(string)
if _fronendDevServerURL == "" {
assetdir, _ := ctx.Value("assetdir").(string)
@ -68,8 +73,11 @@ func (d *DevWebServer) Run(ctx context.Context) error {
return c.String(http.StatusOK, assetdir)
})
if _logger := ctx.Value("logger"); _logger != nil {
myLogger = _logger.(*logger.Logger)
}
var err error
assetHandler, err = assetserver.NewAssetHandler(ctx, assetServerConfig)
assetHandler, err = assetserver.NewAssetHandler(assetServerConfig, myLogger)
if err != nil {
log.Fatal(err)
}
@ -101,7 +109,7 @@ func (d *DevWebServer) Run(ctx context.Context) error {
log.Fatal(err)
}
assetServer, err := assetserver.NewDevAssetServer(ctx, assetHandler, wsHandler, bindingsJSON)
assetServer, err := assetserver.NewDevAssetServer(assetHandler, wsHandler, bindingsJSON, ctx.Value("assetdir") != nil, myLogger, runtime.RuntimeAssetsBundle)
if err != nil {
log.Fatal(err)
}

View file

@ -0,0 +1,26 @@
//go:build !dev
package runtime
var RuntimeAssetsBundle = &RuntimeAssets{
desktopIPC: DesktopIPC,
runtimeDesktopJS: RuntimeDesktopJS,
}
type RuntimeAssets struct {
desktopIPC []byte
websocketIPC []byte
runtimeDesktopJS []byte
}
func (r *RuntimeAssets) DesktopIPC() []byte {
return r.desktopIPC
}
func (r *RuntimeAssets) WebsocketIPC() []byte {
return r.websocketIPC
}
func (r *RuntimeAssets) RuntimeDesktopJS() []byte {
return r.runtimeDesktopJS
}

View file

@ -0,0 +1,27 @@
//go:build dev
package runtime
var RuntimeAssetsBundle = &RuntimeAssets{
desktopIPC: DesktopIPC,
websocketIPC: WebsocketIPC,
runtimeDesktopJS: RuntimeDesktopJS,
}
type RuntimeAssets struct {
desktopIPC []byte
websocketIPC []byte
runtimeDesktopJS []byte
}
func (r *RuntimeAssets) DesktopIPC() []byte {
return r.desktopIPC
}
func (r *RuntimeAssets) WebsocketIPC() []byte {
return r.websocketIPC
}
func (r *RuntimeAssets) RuntimeDesktopJS() []byte {
return r.runtimeDesktopJS
}

View file

@ -2,7 +2,6 @@ package fs
import (
"crypto/md5"
"embed"
"fmt"
"io"
"io/fs"
@ -403,32 +402,3 @@ func FindFileInParents(path string, filename string) string {
}
return pathToFile
}
// FindEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files.
func FindEmbedRootPath(fsys embed.FS) (string, error) {
stopErr := fmt.Errorf("files or multiple dirs found")
fPath := ""
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
fPath = path
if entries, dErr := fs.ReadDir(fsys, path); dErr != nil {
return dErr
} else if len(entries) <= 1 {
return nil
}
}
return stopErr
})
if err != nil && err != stopErr {
return "", err
}
return fPath, nil
}

View file

@ -2,7 +2,6 @@ package gomod
import (
"fmt"
"github.com/Masterminds/semver"
"golang.org/x/mod/modfile"
)

View file

@ -0,0 +1,135 @@
//go:build darwin || linux
package gomod
const basic string = `module changeme
go 1.17
require github.com/wailsapp/wails/v2 v2.0.0-beta.7
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => /home/lea/wails/v2
`
const basicUpdated string = `module changeme
go 1.17
require github.com/wailsapp/wails/v2 v2.0.0-beta.20
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => /home/lea/wails/v2
`
const multilineRequire = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => /home/lea/wails/v2
`
const multilineReplace = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => /home/lea/wails/v2
`
const multilineReplaceNoVersion = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace github.com/wailsapp/wails/v2 => /home/lea/wails/v2
`
const multilineReplaceNoVersionBlock = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace (
github.com/wailsapp/wails/v2 => /home/lea/wails/v2
)
`
const multilineReplaceBlock = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace (
github.com/wailsapp/wails/v2 v2.0.0-beta.7 => /home/lea/wails/v2
)
`
const multilineRequireUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => /home/lea/wails/v2
`
const multilineReplaceUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace github.com/wailsapp/wails/v2 v2.0.0-beta.20 => /home/lea/wails/v2
`
const multilineReplaceNoVersionUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace github.com/wailsapp/wails/v2 => /home/lea/wails/v2
`
const multilineReplaceNoVersionBlockUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace (
github.com/wailsapp/wails/v2 => /home/lea/wails/v2
)
`
const multilineReplaceBlockUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace (
github.com/wailsapp/wails/v2 v2.0.0-beta.20 => /home/lea/wails/v2
)
`

View file

@ -0,0 +1,135 @@
//go:build windows
package gomod
const basic string = `module changeme
go 1.17
require github.com/wailsapp/wails/v2 v2.0.0-beta.7
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const basicUpdated string = `module changeme
go 1.17
require github.com/wailsapp/wails/v2 v2.0.0-beta.20
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineRequire = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplace = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplaceNoVersion = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace github.com/wailsapp/wails/v2 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplaceNoVersionBlock = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace (
github.com/wailsapp/wails/v2 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
)
`
const multilineReplaceBlock = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace (
github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
)
`
const multilineRequireUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplaceUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace github.com/wailsapp/wails/v2 v2.0.0-beta.20 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplaceNoVersionUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace github.com/wailsapp/wails/v2 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplaceNoVersionBlockUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace (
github.com/wailsapp/wails/v2 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
)
`
const multilineReplaceBlockUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace (
github.com/wailsapp/wails/v2 v2.0.0-beta.20 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
)
`

View file

@ -1,5 +1,3 @@
//go:build windows
package gomod
import (
@ -10,15 +8,6 @@ import (
"github.com/matryer/is"
)
const basic string = `module changeme
go 1.17
require github.com/wailsapp/wails/v2 v2.0.0-beta.7
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
func TestGetWailsVersion(t *testing.T) {
tests := []struct {
name string
@ -42,84 +31,6 @@ func TestGetWailsVersion(t *testing.T) {
}
}
const basicUpdated string = `module changeme
go 1.17
require github.com/wailsapp/wails/v2 v2.0.0-beta.20
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineRequire = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplace = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplaceNoVersion = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace github.com/wailsapp/wails/v2 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplaceNoVersionBlock = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace (
github.com/wailsapp/wails/v2 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
)
`
const multilineReplaceBlock = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.7
)
replace (
github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
)
`
const multilineRequireUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
//replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
func TestUpdateGoModVersion(t *testing.T) {
is2 := is.New(t)
@ -180,52 +91,6 @@ func TestGoModOutOfSync(t *testing.T) {
}
}
const multilineReplaceUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace github.com/wailsapp/wails/v2 v2.0.0-beta.20 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplaceNoVersionUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace github.com/wailsapp/wails/v2 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
`
const multilineReplaceNoVersionBlockUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace (
github.com/wailsapp/wails/v2 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
)
`
const multilineReplaceBlockUpdated = `module changeme
go 1.17
require (
github.com/wailsapp/wails/v2 v2.0.0-beta.20
)
replace (
github.com/wailsapp/wails/v2 v2.0.0-beta.20 => C:\Users\leaan\Documents\wails-v2-beta\wails\v2
)
`
const basicGo118 string = `module changeme
go 1.18

View file

@ -1,5 +1,3 @@
//go:build windows
package platform
import (

View file

@ -0,0 +1,83 @@
//go:build linux
/*
* Based on code originally from https://github.com/tadvi/systray. Copyright (C) 2019 The Systray Authors. All Rights Reserved.
*/
package systray
import (
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
)
type Systray struct {
}
func (p *Systray) Close() {
err := p.Stop()
if err != nil {
println(err.Error())
}
}
func (p *Systray) Update() error {
return nil
}
// SetTitle is unused on Windows
func (p *Systray) SetTitle(_ string) {}
func New() (*Systray, error) {
return nil, nil
}
func (p *Systray) SetMenu(popupMenu *menu.Menu) (err error) {
return
}
func (p *Systray) Stop() error {
return nil
}
func (p *Systray) OnLeftClick(fn func()) {
}
func (p *Systray) OnRightClick(fn func()) {
}
func (p *Systray) OnLeftDoubleClick(fn func()) {
}
func (p *Systray) OnRightDoubleClick(fn func()) {
}
func (p *Systray) OnMenuClose(fn func()) {
}
func (p *Systray) OnMenuOpen(fn func()) {
}
func (p *Systray) SetTooltip(tooltip string) error {
return nil
}
func (p *Systray) Show() error {
return p.setVisible(true)
}
func (p *Systray) Hide() error {
return p.setVisible(false)
}
func (p *Systray) setVisible(visible bool) error {
return nil
}
func (p *Systray) SetIcons(lightModeIcon, darkModeIcon *options.SystemTrayIcon) error {
return nil
}
func (p *Systray) Run() error {
return nil
}

View file

@ -0,0 +1,91 @@
//go:build darwin
package systray
import (
"errors"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
)
var NotImplementedSysTray = errors.New("not implemented")
type Systray struct {
}
func (p *Systray) Close() {
err := p.Stop()
if err != nil {
println(err.Error())
}
}
func (p *Systray) Update() error {
return NotImplementedSysTray
}
func (p *Systray) SetTitle(_ string) {}
func New() (*Systray, error) {
return nil, NotImplementedSysTray
}
func (p *Systray) SetMenu(popupMenu *menu.Menu) (err error) {
return NotImplementedSysTray
}
func (p *Systray) Stop() error {
return NotImplementedSysTray
}
func (p *Systray) OnLeftClick(fn func()) {
}
func (p *Systray) OnRightClick(fn func()) {
}
func (p *Systray) OnLeftDoubleClick(fn func()) {
}
func (p *Systray) OnRightDoubleClick(fn func()) {
}
func (p *Systray) OnMenuClose(fn func()) {
}
func (p *Systray) OnMenuOpen(fn func()) {
}
func (p *Systray) SetTooltip(tooltip string) error {
return NotImplementedSysTray
}
func (p *Systray) ShowMessage(title, msg string, bigIcon bool) error {
return NotImplementedSysTray
}
func (p *Systray) Show() error {
return p.setVisible(true)
}
func (p *Systray) Hide() error {
return p.setVisible(false)
}
func (p *Systray) setVisible(visible bool) error {
return NotImplementedSysTray
}
func (p *Systray) SetIcons(lightModeIcon, darkModeIcon *options.SystemTrayIcon) error {
return NotImplementedSysTray
}
func (p *Systray) Run() error {
return NotImplementedSysTray
}

View file

@ -4,10 +4,11 @@ package win32
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
"golang.org/x/sys/windows"
"syscall"
"unsafe"
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
"golang.org/x/sys/windows"
)
var (
@ -330,7 +331,7 @@ const (
WS_EX_APPWINDOW = 0x00040000
WS_OVERLAPPEDWINDOW = 0x00000000 | 0x00C00000 | 0x00080000 | 0x00040000 | 0x00020000 | 0x00010000
WS_EX_NOREDIRECTIONBITMAP = 0x00200000
CW_USEDEFAULT = 0x80000000
CW_USEDEFAULT = ^0x7fffffff
NIM_ADD = 0x00000000
NIM_MODIFY = 0x00000001

40
v2/internal/shell/env.go Normal file
View file

@ -0,0 +1,40 @@
package shell
import (
"fmt"
"strings"
)
func UpsertEnv(env []string, key string, update func(v string) string) []string {
newEnv := make([]string, len(env), len(env)+1)
found := false
for i := range env {
if strings.HasPrefix(env[i], key+"=") {
eqIndex := strings.Index(env[i], "=")
val := env[i][eqIndex+1:]
newEnv[i] = fmt.Sprintf("%s=%v", key, update(val))
found = true
continue
}
newEnv[i] = env[i]
}
if !found {
newEnv = append(newEnv, fmt.Sprintf("%s=%v", key, update("")))
}
return newEnv
}
func RemoveEnv(env []string, key string) []string {
newEnv := make([]string, 0, len(env))
for _, e := range env {
if strings.HasPrefix(e, key+"=") {
continue
}
newEnv = append(newEnv, e)
}
return newEnv
}
func SetEnv(env []string, key string, value string) []string {
return UpsertEnv(env, key, func(_ string) string { return value })
}

View file

@ -0,0 +1,67 @@
package shell
import "testing"
func TestUpdateEnv(t *testing.T) {
env := []string{"one=1", "two=a=b", "three="}
newEnv := UpsertEnv(env, "two", func(v string) string {
return v + "+added"
})
newEnv = UpsertEnv(newEnv, "newVar", func(v string) string {
return "added"
})
newEnv = UpsertEnv(newEnv, "three", func(v string) string {
return "3"
})
newEnv = UpsertEnv(newEnv, "GOARCH", func(v string) string {
return "amd64"
})
if len(newEnv) != 5 {
t.Errorf("expected: 5, got: %d", len(newEnv))
}
if newEnv[1] != "two=a=b+added" {
t.Errorf("expected: \"two=a=b+added\", got: %q", newEnv[1])
}
if newEnv[2] != "three=3" {
t.Errorf("expected: \"three=3\", got: %q", newEnv[2])
}
if newEnv[3] != "newVar=added" {
t.Errorf("expected: \"newVar=added\", got: %q", newEnv[3])
}
if newEnv[4] != "GOARCH=amd64" {
t.Errorf("expected: \"newVar=added\", got: %q", newEnv[4])
}
}
func TestSetEnv(t *testing.T) {
env := []string{"one=1", "two=a=b", "three="}
newEnv := SetEnv(env, "two", "set")
newEnv = SetEnv(newEnv, "newVar", "added")
if len(newEnv) != 4 {
t.Errorf("expected: 4, got: %d", len(newEnv))
}
if newEnv[1] != "two=set" {
t.Errorf("expected: \"two=set\", got: %q", newEnv[1])
}
if newEnv[3] != "newVar=added" {
t.Errorf("expected: \"newVar=added\", got: %q", newEnv[3])
}
}
func TestRemoveEnv(t *testing.T) {
env := []string{"one=1", "two=a=b", "three=3"}
newEnv := RemoveEnv(env, "two")
if len(newEnv) != 2 {
t.Errorf("expected: 2, got: %d", len(newEnv))
}
if newEnv[0] != "one=1" {
t.Errorf("expected: \"one=1\", got: %q", newEnv[1])
}
if newEnv[1] != "three=3" {
t.Errorf("expected: \"three=3\", got: %q", newEnv[3])
}
}

View file

@ -62,7 +62,19 @@ func CreateCommand(directory string, command string, args ...string) *exec.Cmd {
// RunCommand will run the given command + args in the given directory
// Will return stdout, stderr and error
func RunCommand(directory string, command string, args ...string) (string, string, error) {
return RunCommandWithEnv(nil, directory, command, args...)
}
// RunCommandWithEnv will run the given command + args in the given directory and using the specified env.
//
// Env specifies the environment of the process. Each entry is of the form "key=value".
// If Env is nil, the new process uses the current process's environment.
//
// Will return stdout, stderr and error
func RunCommandWithEnv(env []string, directory string, command string, args ...string) (string, string, error) {
cmd := CreateCommand(directory, command, args...)
cmd.Env = env
var stdo, stde bytes.Buffer
cmd.Stdout = &stdo
cmd.Stderr = &stde

View file

@ -32,6 +32,7 @@ func (y *Dnf) Packages() packagemap {
{Name: "gtk3-devel", SystemPackage: true, Library: true},
},
"libwebkit": []*Package{
{Name: "webkit2gtk4.0-devel", SystemPackage: true, Library: true},
{Name: "webkit2gtk3-devel", SystemPackage: true, Library: true},
// {Name: "webkitgtk3-devel", SystemPackage: true, Library: true},
},

View file

@ -641,13 +641,19 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m
err = builder.AddSimpleField(jsonFieldName, field, fldOpts)
} else if field.Type.Kind() == reflect.Struct { // Struct:
t.logf(depth, "- struct %s.%s (%s)", typeOf.Name(), field.Name, field.Type.String())
typeScriptChunk, err := t.convertType(depth+1, field.Type, customCode)
if err != nil {
return "", err
}
if typeScriptChunk != "" {
result = typeScriptChunk + "\n" + result
// Anonymous structures is ignored
// It is possible to generate them but hard to generate correct name
if field.Type.Name() != "" {
typeScriptChunk, err := t.convertType(depth+1, field.Type, customCode)
if err != nil {
return "", err
}
if typeScriptChunk != "" {
result = typeScriptChunk + "\n" + result
}
}
isKnownType := t.KnownStructs.Contains(getStructFQN(field.Type.String()))
println("KnownStructs:", t.KnownStructs.Join("\t"))
println(getStructFQN(field.Type.String()))
@ -833,16 +839,24 @@ func (t *typeScriptClassBuilder) AddEnumField(fieldName string, field reflect.St
func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect.StructField, isAnyType bool) {
strippedFieldName := strings.ReplaceAll(fieldName, "?", "")
namespace := strings.Split(field.Type.String(), ".")[0]
fqname := "any"
fqname := field.Type.Name()
classname := "null"
fqname = field.Type.Name()
namespace := strings.Split(field.Type.String(), ".")[0]
if namespace != t.namespace {
fqname = field.Type.String()
}
if !isAnyType {
classname = fqname
}
// Anonymous struct
if field.Type.Name() == "" {
classname = "Object"
}
t.addField(fieldName, fqname, isAnyType)
t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("this.convertValues(source[\"%s\"], %s)", strippedFieldName, classname))
}

View file

@ -2,7 +2,6 @@ package assetserver
import (
"bytes"
"context"
"embed"
"errors"
"fmt"
@ -13,11 +12,14 @@ import (
"path"
"strings"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
type Logger interface {
Debug(message string, args ...interface{})
Error(message string, args ...interface{})
}
//go:embed defaultindex.html
var defaultHTML []byte
@ -29,16 +31,12 @@ type assetHandler struct {
fs iofs.FS
handler http.Handler
logger *logger.Logger
logger Logger
retryMissingFiles bool
}
func NewAssetHandler(ctx context.Context, options assetserver.Options) (http.Handler, error) {
var log *logger.Logger
if _logger := ctx.Value("logger"); _logger != nil {
log = _logger.(*logger.Logger)
}
func NewAssetHandler(options assetserver.Options, log Logger) (http.Handler, error) {
vfs := options.Assets
if vfs != nil {
@ -46,12 +44,12 @@ func NewAssetHandler(ctx context.Context, options assetserver.Options) (http.Han
return nil, err
}
subDir, err := fs.FindPathToFile(vfs, indexHTML)
subDir, err := FindPathToFile(vfs, indexHTML)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
msg := "no `index.html` could be found in your Assets fs.FS"
if embedFs, isEmbedFs := vfs.(embed.FS); isEmbedFs {
rootFolder, _ := fs.FindEmbedRootPath(embedFs)
rootFolder, _ := FindEmbedRootPath(embedFs)
msg += fmt.Sprintf(", please make sure the embedded directory '%s' is correct and contains your assets", rootFolder)
}
@ -100,7 +98,7 @@ func (d *assetHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}
if err != nil {
d.logError("Unable to load file '%s': %s", filename, err)
d.logError("Unable to handle request '%s': %s", url, err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
@ -129,13 +127,14 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi
return err
}
url := req.URL.Path
isDirectoryPath := url == "" || url[len(url)-1] == '/'
if statInfo.IsDir() {
url := req.URL.Path
if url != "" && url[len(url)-1] != '/' {
if !isDirectoryPath {
// If the URL doesn't end in a slash normally a http.redirect should be done, but that currently doesn't work on
// WebKit WebVies (macOS/Linux).
// So we handle this as a file that could not be found.
return os.ErrNotExist
// WebKit WebViews (macOS/Linux).
// So we handle this as a specific error
return fmt.Errorf("a directory has been requested without a trailing slash, please add a trailing slash to your request")
}
filename = path.Join(filename, indexHTML)
@ -150,6 +149,8 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi
if err != nil {
return err
}
} else if isDirectoryPath {
return fmt.Errorf("a file has been requested with a trailing slash, please remove the trailing slash from your request")
}
var buf [512]byte

View file

@ -2,7 +2,6 @@ package assetserver
import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httptest"
@ -10,8 +9,6 @@ import (
"golang.org/x/net/html"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
@ -21,41 +18,48 @@ const (
ipcJSPath = "/wails/ipc.js"
)
type RuntimeAssets interface {
DesktopIPC() []byte
WebsocketIPC() []byte
RuntimeDesktopJS() []byte
}
type AssetServer struct {
handler http.Handler
wsHandler http.Handler
runtimeJS []byte
ipcJS func(*http.Request) []byte
logger *logger.Logger
logger Logger
runtime RuntimeAssets
servingFromDisk bool
appendSpinnerToBody bool
}
func NewAssetServerMainPage(ctx context.Context, bindingsJSON string, options *options.App) (*AssetServer, error) {
func NewAssetServerMainPage(bindingsJSON string, options *options.App, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
assetOptions, err := BuildAssetServerConfig(options)
if err != nil {
return nil, err
}
return NewAssetServer(ctx, bindingsJSON, assetOptions)
return NewAssetServer(bindingsJSON, assetOptions, servingFromDisk, logger, runtime)
}
func NewAssetServer(ctx context.Context, bindingsJSON string, options assetserver.Options) (*AssetServer, error) {
handler, err := NewAssetHandler(ctx, options)
func NewAssetServer(bindingsJSON string, options assetserver.Options, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
handler, err := NewAssetHandler(options, logger)
if err != nil {
return nil, err
}
return NewAssetServerWithHandler(ctx, handler, bindingsJSON)
return NewAssetServerWithHandler(handler, bindingsJSON, servingFromDisk, logger, runtime)
}
func NewAssetServerWithHandler(ctx context.Context, handler http.Handler, bindingsJSON string) (*AssetServer, error) {
func NewAssetServerWithHandler(handler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
var buffer bytes.Buffer
if bindingsJSON != "" {
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
}
buffer.Write(runtime.RuntimeDesktopJS)
buffer.Write(runtime.RuntimeDesktopJS())
result := &AssetServer{
handler: handler,
@ -65,11 +69,9 @@ func NewAssetServerWithHandler(ctx context.Context, handler http.Handler, bindin
// If so, this means we are in dev mode and are serving assets off disk.
// We indicate this through the `servingFromDisk` flag to ensure requests
// aren't cached in dev mode.
servingFromDisk: ctx.Value("assetdir") != nil,
}
if _logger := ctx.Value("logger"); _logger != nil {
result.logger = _logger.(*logger.Logger)
servingFromDisk: servingFromDisk,
logger: logger,
runtime: runtime,
}
return result, nil
@ -121,7 +123,7 @@ func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
d.writeBlob(rw, path, d.runtimeJS)
case ipcJSPath:
content := runtime.DesktopIPC
content := d.runtime.DesktopIPC()
if d.ipcJS != nil {
content = d.ipcJS(req)
}

View file

@ -4,11 +4,8 @@
package assetserver
import (
"context"
"net/http"
"strings"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
)
/*
@ -16,8 +13,8 @@ The assetserver for the dev mode.
Depending on the UserAgent it injects a websocket based IPC script into `index.html` or the default desktop IPC. The
default desktop IPC is injected when the webview accesses the devserver.
*/
func NewDevAssetServer(ctx context.Context, handler http.Handler, wsHandler http.Handler, bindingsJSON string) (*AssetServer, error) {
result, err := NewAssetServerWithHandler(ctx, handler, bindingsJSON)
func NewDevAssetServer(handler http.Handler, wsHandler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
result, err := NewAssetServerWithHandler(handler, bindingsJSON, servingFromDisk, logger, runtime)
if err != nil {
return nil, err
}
@ -26,9 +23,9 @@ func NewDevAssetServer(ctx context.Context, handler http.Handler, wsHandler http
result.appendSpinnerToBody = true
result.ipcJS = func(req *http.Request) []byte {
if strings.Contains(req.UserAgent(), WailsUserAgentValue) {
return runtime.DesktopIPC
return runtime.DesktopIPC()
}
return runtime.WebsocketIPC
return runtime.WebsocketIPC()
}
return result, nil

75
v2/pkg/assetserver/fs.go Normal file
View file

@ -0,0 +1,75 @@
package assetserver
import (
"embed"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
)
// FindEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files.
func FindEmbedRootPath(fsys embed.FS) (string, error) {
stopErr := fmt.Errorf("files or multiple dirs found")
fPath := ""
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
fPath = path
if entries, dErr := fs.ReadDir(fsys, path); dErr != nil {
return dErr
} else if len(entries) <= 1 {
return nil
}
}
return stopErr
})
if err != nil && err != stopErr {
return "", err
}
return fPath, nil
}
func FindPathToFile(fsys fs.FS, file string) (string, error) {
stat, _ := fs.Stat(fsys, file)
if stat != nil {
return ".", nil
}
var indexFiles []string
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, file) {
indexFiles = append(indexFiles, path)
}
return nil
})
if err != nil {
return "", err
}
if len(indexFiles) > 1 {
selected := indexFiles[0]
for _, f := range indexFiles {
if len(f) < len(selected) {
selected = f
}
}
path, _ := filepath.Split(selected)
return path, nil
}
if len(indexFiles) > 0 {
path, _ := filepath.Split(indexFiles[0])
return path, nil
}
return "", fmt.Errorf("%s: %w", file, os.ErrNotExist)
}

View file

@ -62,7 +62,12 @@ func GenerateBindings(options Options) (string, error) {
_ = os.Remove(filename)
}()
stdout, stderr, err = shell.RunCommand(workingDirectory, filename, "-tsprefix", options.TsPrefix, "-tssuffix", options.TsSuffix)
// Set environment variables accordingly
env := os.Environ()
env = shell.SetEnv(env, "tsprefix", options.TsPrefix)
env = shell.SetEnv(env, "tssuffix", options.TsSuffix)
stdout, stderr, err = shell.RunCommandWithEnv(env, workingDirectory, filename)
if err != nil {
return stdout, fmt.Errorf("%s\n%s\n%s", stdout, stderr, err)
}

View file

@ -302,8 +302,8 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
cmd.Env = os.Environ() // inherit env
if options.Platform != "windows" {
// Use upsertEnv so we don't overwrite user's CGO_CFLAGS
cmd.Env = upsertEnv(cmd.Env, "CGO_CFLAGS", func(v string) string {
// Use shell.UpsertEnv so we don't overwrite user's CGO_CFLAGS
cmd.Env = shell.UpsertEnv(cmd.Env, "CGO_CFLAGS", func(v string) string {
if options.Platform == "darwin" {
if v != "" {
v += " "
@ -312,8 +312,8 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
}
return v
})
// Use upsertEnv so we don't overwrite user's CGO_CXXFLAGS
cmd.Env = upsertEnv(cmd.Env, "CGO_CXXFLAGS", func(v string) string {
// Use shell.UpsertEnv so we don't overwrite user's CGO_CXXFLAGS
cmd.Env = shell.UpsertEnv(cmd.Env, "CGO_CXXFLAGS", func(v string) string {
if v != "" {
v += " "
}
@ -321,7 +321,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
return v
})
cmd.Env = upsertEnv(cmd.Env, "CGO_ENABLED", func(v string) string {
cmd.Env = shell.UpsertEnv(cmd.Env, "CGO_ENABLED", func(v string) string {
return "1"
})
if options.Platform == "darwin" {
@ -338,7 +338,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
}
addUTIFramework := majorVersion >= 11
// Set the minimum Mac SDK to 10.13
cmd.Env = upsertEnv(cmd.Env, "CGO_LDFLAGS", func(v string) string {
cmd.Env = shell.UpsertEnv(cmd.Env, "CGO_LDFLAGS", func(v string) string {
if v != "" {
v += " "
}
@ -352,11 +352,11 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
}
}
cmd.Env = upsertEnv(cmd.Env, "GOOS", func(v string) string {
cmd.Env = shell.UpsertEnv(cmd.Env, "GOOS", func(v string) string {
return options.Platform
})
cmd.Env = upsertEnv(cmd.Env, "GOARCH", func(v string) string {
cmd.Env = shell.UpsertEnv(cmd.Env, "GOARCH", func(v string) string {
return options.Arch
})
@ -608,22 +608,3 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
pterm.Println("Done.")
return nil
}
func upsertEnv(env []string, key string, update func(v string) string) []string {
newEnv := make([]string, len(env), len(env)+1)
found := false
for i := range env {
if strings.HasPrefix(env[i], key+"=") {
eqIndex := strings.Index(env[i], "=")
val := env[i][eqIndex+1:]
newEnv[i] = fmt.Sprintf("%s=%v", key, update(val))
found = true
continue
}
newEnv[i] = env[i]
}
if !found {
newEnv = append(newEnv, fmt.Sprintf("%s=%v", key, update("")))
}
return newEnv
}

View file

@ -2,40 +2,6 @@ package build
import "testing"
func TestUpdateEnv(t *testing.T) {
env := []string{"one=1", "two=a=b", "three="}
newEnv := upsertEnv(env, "two", func(v string) string {
return v + "+added"
})
newEnv = upsertEnv(newEnv, "newVar", func(v string) string {
return "added"
})
newEnv = upsertEnv(newEnv, "three", func(v string) string {
return "3"
})
newEnv = upsertEnv(newEnv, "GOARCH", func(v string) string {
return "amd64"
})
if len(newEnv) != 5 {
t.Errorf("expected: 5, got: %d", len(newEnv))
}
if newEnv[1] != "two=a=b+added" {
t.Errorf("expected: \"two=a=b+added\", got: %q", newEnv[1])
}
if newEnv[2] != "three=3" {
t.Errorf("expected: \"three=3\", got: %q", newEnv[2])
}
if newEnv[3] != "newVar=added" {
t.Errorf("expected: \"newVar=added\", got: %q", newEnv[3])
}
if newEnv[4] != "GOARCH=amd64" {
t.Errorf("expected: \"newVar=added\", got: %q", newEnv[4])
}
}
func Test_commandPrettifier(t *testing.T) {
tests := []struct {
name string

View file

@ -8,8 +8,8 @@ import (
"strings"
"github.com/pterm/pterm"
"github.com/samber/lo"
"github.com/wailsapp/wails/v2/internal/staticanalysis"
"github.com/wailsapp/wails/v2/pkg/commands/bindings"
@ -333,17 +333,17 @@ func execBuildApplication(builder Builder, options *Options) (string, error) {
}
if options.Platform == "windows" {
const expWebView2Loader = "exp_gowebview2loader"
const nativeWebView2Loader = "native_webview2loader"
message := ""
tags := options.UserTags
if lo.Contains(tags, expWebView2Loader) {
message = "Thanks for testing the new experimental Go native WebView2Loader. Please report your feedback and any bugs you think might be related to using the new loader: https://github.com/wailsapp/wails/issues/2004"
if lo.Contains(tags, nativeWebView2Loader) {
message := "You are using the legacy native WebView2Loader. This loader will be deprecated in the near future. Please report any bugs related to the new loader: https://github.com/wailsapp/wails/issues/2004"
pterm.Warning.Println(message)
} else {
tags = append(tags, expWebView2Loader)
message = fmt.Sprintf("An experimental Go native WebView2Loader is available. We would love to hear your feedback about it and invite you to test it by building with `-tags %s`", strings.Join(tags, ","))
tags = append(tags, nativeWebView2Loader)
message := fmt.Sprintf("Wails is now using the new Go WebView2Loader. If you encounter any issues with it, please report them to https://github.com/wailsapp/wails/issues/2004. You could also use the old legacy loader with `-tags %s`, but keep in mind this will be deprecated in the near future.", strings.Join(tags, ","))
pterm.Info.Println(message)
}
pterm.Info.Println(message)
}
if options.Platform == "darwin" && options.Mode == Debug {

View file

@ -28,6 +28,7 @@ If you are unsure about a template, inspect `package.json` and `wails.json` for
- [wails-vite-vue-the-works](https://github.com/codydbentley/wails-vite-vue-the-works) - Vue 3 TypeScript with Vite, Vuex, Vue Router, Sass, and ESLint + Prettier
- [wails-template-quasar-js](https://github.com/sgosiaco/wails-template-quasar-js) - A template using JavaScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint, Prettier)
- [wails-template-quasar-ts](https://github.com/sgosiaco/wails-template-quasar-ts) - A template using TypeScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint, Prettier, Composition API with &lt;script setup&gt;)
- [wails-template-naive](https://github.com/tk103331/wails-template-naive) - Wails template based on Naive UI (A Vue 3 Component Library)
## Angular

View file

@ -74,6 +74,7 @@ If you are unsure about a template, inspect `package.json` and `wails.json` for
| -windowsconsole | Keep the console window for Windows builds | |
| -obfuscate | Obfuscate the application using [garble](https://github.com/burrowers/garble) | false |
| -garbleargs | Arguments to pass to garble | `-literals -tiny -seed=random` |
| -nosyncgomod | Do not sync go.mod with the Wails version | false |
For a detailed description of the `webview2` flag, please refer to the [Windows](../guides/windows.mdx) Guide.
@ -188,6 +189,9 @@ Your system is ready for Wails development!
| -save | Saves the given `assetdir`, `reloaddirs`, `wailsjsdir`, `debounce`, `devserver` and `frontenddevserverurl` flags in `wails.json` to become the defaults for subsequent invocations. | |
| -race | Build with Go's race detector | false |
| -s | Skip building the frontend | false |
| -nosyncgomod | Do not sync go.mod with the Wails version | false |
Example:

View file

@ -258,11 +258,11 @@ Not all features of an `http.Request` are currently supported, please see the fo
| DELETE | ✅ | ✅ | ✅ [^1] |
| Request Headers | ✅ | ✅ | ✅ [^1] |
| Request Body | ✅ | ✅ | ❌ |
| Request Body Streaming | ❌ | ❌ | ❌ |
| Request Body Streaming | ✅ | ✅ | ❌ |
| Response StatusCodes | ✅ | ✅ | ✅ [^1] |
| Response Headers | ✅ | ✅ | ✅ [^1] |
| Response Body | ✅ | ✅ | ✅ |
| Response Body Streaming | ❌ | | ✅ |
| Response Body Streaming | ❌ | | ✅ |
| WebSockets | ❌ | ❌ | ❌ |
| HTTP Redirects 30x | ✅ | ❌ | ❌ |

View file

@ -225,12 +225,8 @@ module.exports = async function configCreatorAsync() {
href: "https://twitter.com/wailsapp",
},
{
label: "Slack",
href: "https://gophers.slack.com/messages/CJ4P9F7MZ/",
},
{
label: "Slack invite",
href: "https://invite.slack.golangbridge.org/",
label: "Discord",
href: "https://discord.gg/JDdSxwjhGf",
},
],
},

View file

@ -411,5 +411,13 @@
"theme.SearchModal.placeholder": {
"message": "Search docs",
"description": "The placeholder of the input of the DocSearch pop-up modal"
},
"theme.docs.sidebar.closeSidebarButtonAriaLabel": {
"message": "Close navigation bar",
"description": "The ARIA label for close button of mobile sidebar"
},
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": {
"message": "Toggle navigation bar",
"description": "The ARIA label for hamburger menu button of mobile navigation"
}
}

View file

@ -0,0 +1,38 @@
{
"version.label": {
"message": "v2.3.0",
"description": "The label for version v2.3.0"
},
"sidebar.docs.category.Getting Started": {
"message": "Getting Started",
"description": "The label for category Getting Started in sidebar docs"
},
"sidebar.docs.category.Reference": {
"message": "Reference",
"description": "The label for category Reference in sidebar docs"
},
"sidebar.docs.category.Runtime": {
"message": "Runtime",
"description": "The label for category Runtime in sidebar docs"
},
"sidebar.docs.category.Community": {
"message": "Community",
"description": "The label for category Community in sidebar docs"
},
"sidebar.docs.category.Showcase": {
"message": "Showcase",
"description": "The label for category Showcase in sidebar docs"
},
"sidebar.docs.category.Guides": {
"message": "Guides",
"description": "The label for category Guides in sidebar docs"
},
"sidebar.docs.category.Tutorials": {
"message": "Tutorials",
"description": "The label for category Tutorials in sidebar docs"
},
"sidebar.docs.link.Contributing": {
"message": "Contributing",
"description": "The label for link Contributing in sidebar docs, linking to /community-guide#ways-of-contributing"
}
}

View file

@ -0,0 +1,38 @@
{
"version.label": {
"message": "v2.3.1",
"description": "The label for version v2.3.1"
},
"sidebar.docs.category.Getting Started": {
"message": "Getting Started",
"description": "The label for category Getting Started in sidebar docs"
},
"sidebar.docs.category.Reference": {
"message": "Reference",
"description": "The label for category Reference in sidebar docs"
},
"sidebar.docs.category.Runtime": {
"message": "Runtime",
"description": "The label for category Runtime in sidebar docs"
},
"sidebar.docs.category.Community": {
"message": "Community",
"description": "The label for category Community in sidebar docs"
},
"sidebar.docs.category.Showcase": {
"message": "Showcase",
"description": "The label for category Showcase in sidebar docs"
},
"sidebar.docs.category.Guides": {
"message": "Guides",
"description": "The label for category Guides in sidebar docs"
},
"sidebar.docs.category.Tutorials": {
"message": "Tutorials",
"description": "The label for category Tutorials in sidebar docs"
},
"sidebar.docs.link.Contributing": {
"message": "Contributing",
"description": "The label for link Contributing in sidebar docs, linking to /community-guide#ways-of-contributing"
}
}

View file

@ -50,5 +50,9 @@
"link.item.label.Awesome": {
"message": "Awesome",
"description": "The label of footer link with label=Awesome linking to https://github.com/wailsapp/awesome-wails"
},
"link.item.label.Discord": {
"message": "Discord",
"description": "The label of footer link with label=Discord linking to https://discord.gg/JDdSxwjhGf"
}
}

View file

@ -0,0 +1,26 @@
---
sidebar_position: 2
---
# Liens
Cette page sert de liste pour les liens liés à la communauté. Veuillez soumettre une PR (cliquez sur `Modifier cette page` en bas) pour soumettre des liens.
## Awesome Wails
La [liste définitive](https://github.com/wailsapp/awesome-wails) de liens relatifs à Wails.
## Canaux de support
- [Gophers Slack Channel](https://gophers.slack.com/messages/CJ4P9F7MZ/)
- [Gophers Slack Channel Invite](https://invite.slack.golangbridge.org/)
- [Github Issues](https://github.com/wailsapp/wails/issues)
- [canal de discussion sur la bêta v2](https://github.com/wailsapp/wails/discussions/828)
## Réseaux sociaux
- [Twitter](https://twitter.com/wailsapp)
- [Groupe QQ pour la communauté chinoise de Wails](https://qm.qq.com/cgi-bin/qm/qr?k=PmIURne5hFGNd7QWzW5qd6FV-INEjNJv&jump_from=webapi) - Numéro de groupe : 1067173054
## Autres tutoriels et articles
- [Construction d'un Panneau d'Affichage](https://blog.customct.com/building-bulletin-board)

View file

@ -0,0 +1,12 @@
# EncryptEasy
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/encrypteasy.webp").default} />
<br />
</p>
```
**[EncryptEasy](https://www.encrypteasy.app) est un outil de chiffrement PGP simple et facile à utiliser, qui gère toutes vos clés et celles de vos contacts. Le chiffrement devrait être simple. Développé avec Wails.**
Chiffrer les messages à l'aide de PGP est la norme de l'industrie. Tout le monde a une clé privée et publique. Votre clé privée, eh bien, doit être privée afin que vous seul puissiez lire les messages. Votre clé publique est distribuée à toute personne qui veut vous envoyer des messages secrets, chiffrés. Gérer les clés, chiffrer les messages et déchiffrer les messages devrait être une expérience agréable. EncryptEasy a pour but de vous simplifier la tâche.

View file

@ -0,0 +1,16 @@
# Utilitaire d'exportation FileHound
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/filehound.webp").default} />
<br />
</p>
```
[L'utilitaire d'exportation FileHound](https://www.filehound.co.uk/) est une plate-forme de gestion de documents cloud conçue pour la conservation sécurisée de fichiers, l'automatisation des processus métier et les capacités de SmartCapture.
L'utilitaire d'exportation FileHound permet aux administrateurs FileHound d'exécuter des tâches sécurisées d'extraction de documents et de données à des fins alternatives de sauvegarde et de récupération. Cette application téléchargera tous les documents et/ou métadonnées enregistrés dans FileHound en fonction des filtres que vous avez choisis. Les métadonnées seront exportées dans les formats JSON et XML.
Backend construit avec: Go 1.15 Wails 1.11.0 go-sqlite3 1.14.6 go-linq 3.2
Frontend avec: Vue 2.6.11 Vuex 3.4.0 TypeScript Tailwind 1.9.6

View file

@ -0,0 +1,14 @@
# Minecraft Updater
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img
src={
require("@site/static/img/showcase/minecraft-mod-updater.webp").default
}
/>
<br />
</p>
```
[Minecraft Updater](https://github.com/Gurkengewuerz/MinecraftModUpdater) est un outil utilitaire pour mettre à jour et synchroniser les mods Minecraft pour votre base d'utilisateurs. Il a été conçu en utilisant Wails2 et React avec [antd](https://ant.design/) comme framework frontend.

View file

@ -0,0 +1,14 @@
# Modal File Manager
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img
src={require("@site/static/img/showcase/modalfilemanager.webp").default}
/>
<br />
</p>
```
[Modal File Manager](https://github.com/raguay/ModalFileManager) est un gestionnaire de fichiers à double volet utilisant des technologies web. Mon design original était basé sur NW.js et peut être trouvé [ici](https://github.com/raguay/ModalFileManager-NWjs). Cette version utilise le même code frontend basé sur Svelte (mais il a été grandement modifié depuis le départ de NW.js), mais le backend est une implémentation de [Wails 2](https://wails.io/). En utilisant cette implémentation, je n'utilise plus de commandes en ligne de commande `rm`, `cp`, etc. Il est entièrement codé en utilisant Go et fonctionne beaucoup plus rapidement que les versions précédentes.
Ce gestionnaire de fichiers est conçu autour du même principe que Vim: l'état est contrôlé par des actions via le clavier. Le nombre d'états n'est pas fixe, mais très programmable. Par conséquent, un nombre infini de configurations de clavier qui peuvent être créées et utilisées. C'est la principale différence par rapport aux autres gestionnaires de fichiers.

View file

@ -0,0 +1,10 @@
# Molley Wallet
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/mollywallet.webp").default} />
<br />
</p>
```
[Molly Wallet](https://github.com/grvlle/constellation_wallet/) le portefeuille officiel $DAG du Constellation Network. Cela permettra aux utilisateurs d'interagir avec le réseau Hypergraph de différentes manières, sans se limiter à la production de transactions en $DAG.

View file

@ -0,0 +1,14 @@
# October
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/october.webp").default} />
<br />
</p>
```
[Octobre](https://october.utf9k.net) est une petite application Wails qui rend vraiment facile d'extraire les surlignements de [Kobo eReaders](https://en.wikipedia.org/wiki/Kobo_eReader) puis de les transférer vers [Readwise](https://readwise.io).
Il a une taille relativement petite avec toutes les versions de la plate-forme pesant en moins de 10 Mo, et c'est sans activer la [compression UPX](https://upx.github.io/)!
En revanche, les précédentes tentatives de l'auteur avec Electron ont rapidement gonflé à plusieurs centaines de mégaoctets.

View file

@ -0,0 +1,10 @@
# Optimus
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/optimus.webp").default} />
<br />
</p>
```
[Optimus](https://github.com/splode/optimus) est une application d'optimisation d'image de bureau. Il supporte la conversion et la compression entre les formats dimages WebP, JPEG et PNG.

View file

@ -0,0 +1,10 @@
# Portfall
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/portfall.webp").default} />
<br />
</p>
```
[Portfall](https://github.com/rekon-oss/portfall) - Un portail de redirection de port k8 pour un accès facile à toutes les interfaces de votre instance

View file

@ -0,0 +1,12 @@
# Restic Browser
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img
src={require("@site/static/img/showcase/restic-browser-2.png").default}
/>
<br />
</p>
```
[Restic-Browser](https://github.com/emuell/restic-browser) - Une interface de sauvegarde simple et multiplateforme [restic](https://github.com/restic/restic) pour la navigation et la restauration de dépôts restic.

View file

@ -0,0 +1,21 @@
# RiftShare
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/riftshare-main.webp").default} />
<br />
</p>
```
Partage de fichiers facile, sécurisé et gratuit pour tout le monde. Apprenez-en plus sur [Riftshare.app](https://riftshare.app)
## Fonctionnalités
- Partage facile et sécurisé de fichiers entre ordinateurs à la fois sur le réseau local et via Internet
- Supporte l'envoi de fichiers ou de répertoires de manière sécurisée par le protocole [magic wormhole](https://magic-wormhole.readthedocs.io/en/latest/)
- Compatible avec toutes les autres applications utilisant magic wormhole (magic-wormhole or wormhole-william CLI, wormhole-gui, etc.)
- Compression automatique de plusieurs fichiers sélectionnés à envoyer en même temps
- Animations complètes, barre de progression et support d'annulation pour l'envoi et la réception
- Sélection de fichier natif au système d'exploitation
- Ouvrir les fichiers en un seul clic une fois reçus
- Mise à jour automatique - ne vous inquiétez pas d'avoir la dernière version!

View file

@ -0,0 +1,10 @@
# ScriptBar
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/scriptbar.webp").default} />
<br />
</p>
```
[Barre de scripts](https://GitHub.com/raguay/ScriptBarApp) est un programme pour afficher la sortie du serveur [Node-Red](https://nodered.org) intégré dans l'application [EmailIt](https://GitHub.com/raguay/EmailIt). Il affiche également la sortie des scripts sur votre système. ScriptBar ne les met pas dans la barre de menus, mais les a tous dans une fenêtre convenable pour une visualisation facile. Vous pouvez avoir plusieurs onglets pour voir plusieurs choses différentes. Vous pouvez également conserver les liens vers vos sites Web les plus visités.

View file

@ -0,0 +1,10 @@
# Surge
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/surge.png").default} />
<br />
</p>
```
[Surge](https://getsurge.io/) est une application de partage de fichiers p2p conçue pour utiliser les technologies blockchain afin d'activer les transferts de fichiers 100 % anonymes. Surge est chiffré de bout en bout, décentralisé et open source.

View file

@ -0,0 +1,10 @@
# Wally
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/wally.webp").default} />
<br />
</p>
```
[Wally](https://ergodox-ez.com/pages/wally) est le flasheur officiel du firmware pour les claviers [Ergodox](https://ergodox-ez.com/). C'est un excellent exemple de ce que vous pouvez réaliser avec Wails : la capacité de combiner la puissance de Go et les riches outils graphiques du monde du développement web.

View file

@ -0,0 +1,10 @@
# Wombat
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/wombat.webp").default} />
<br />
</p>
```
[Wombat](https://github.com/rogchap/wombat) est un client gRPC multi-plateforme.

View file

@ -0,0 +1,10 @@
# Ytd
```mdx-code-block
<p style={{ "text-align": "center" }}>
<img src={require("@site/static/img/showcase/ytd.webp").default} />
<br />
</p>
```
[Ytd](https://github.com/marcio199226/ytd/tree/v2-wails) est une application pour télécharger des pistes depuis youtube, créer des listes de lecture hors ligne et les partager avec vos amis, vos amis seront en mesure de lire vos playlists ou de les télécharger pour l'écoute hors ligne, a un lecteur intégré.

View file

@ -0,0 +1,56 @@
---
sidebar_position: 1
---
# Modèles
Cette page sert de liste pour les modèles supportés par la communauté. Veuillez soumettre une PR (cliquez sur `Modifier cette page` en bas) pour inclure vos modèles. Pour construire votre propre modèle, veuillez consulter le guide [Modèles](../guides/templates.mdx).
Pour utiliser ces modèles, exécutez `wails init -n "Votre nom de projet" -t [le lien ci-dessous[@version]]`
S'il n'y a pas de suffixe de version, la branche principale du modèle de code sera alors utilisé par défaut. S'il y a un suffixe de version, le modèle de code correspondant au tag de cette version sera utilisé.
Exemple : `wails init -n "Votre nom de projet" -t https://github.com/misitebao/wails-template-vue`
:::warning Attention
**Le projet Wails n'entretient pas, et n'est pas responsable des modèles de tierces parties!**
Si vous n'êtes pas sûr d'un modèle, inspectez `package.json` et `wails.json` pour savoir quels scripts sont exécutés et quels paquets sont installés.
:::
## Vue
- [wails-template-vue](https://github.com/misitebao/wails-template-vue) - Modèle de Wails basé sur Vue (TypeScript intégré, thème sombre, internationalisation, routage de page unique, TailwindCSS)
- [wails-vite-vue-ts](https://github.com/codydbentley/wails-vite-vue-ts) - Vue 3 TypeScript avec Vite (et instructions pour ajouter des fonctionnalités)
- [wails-vite-vue-the-works](https://github.com/codydbentley/wails-vite-vue-the-works) - Vue 3 TypeScript avec Vite, Vuex, Vue Router, Sass, et ESLint + Prettier
- [wails-template-quasar-js](https://github.com/sgosiaco/wails-template-quasar-js) - Un modèle utilisant JavaScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint, Prettier)
- [wails-template-quasar-ts](https://github.com/sgosiaco/wails-template-quasar-ts) - Un modèle utilisant TypeScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint, Prettier, Composition API avec &lt;script setup&gt;)
## Angular
- [wails-angular-template](https://github.com/TAINCER/wails-angular-template) - Angular avec TypeScript, Sass, rechargement à chaud, découpage dynamique de code et i18n
## React
- [wails-react-template](https://github.com/AlienRecall/wails-react-template) - Un modèle utilisant reactjs
- [wails-react-template](https://github.com/flin7/wails-react-template) - Un modèle minimal pour React qui supporte le développement en direct
- [wails-template-nextjs](https://github.com/LGiki/wails-template-nextjs) - Un modèle utilisant Next.js et TypeScript
- [wails-vite-react-ts-tailwind-template](https://github.com/hotafrika/wails-vite-react-ts-tailwind-template) - Un modèle pour React + TypeScript + Vite + TailwindCSS
## Svelte
- [wails-svelte-template](https://github.com/raitonoberu/wails-svelte-template) - Un modèle utilisant Svelte
- [wails-vite-svelte-template](https://github.com/BillBuilt/wails-vite-svelte-template) - Un modèle utilisant Svelte et Vite
- [wails-vite-svelte-tailwind-template](https://github.com/BillBuilt/wails-vite-svelte-tailwind-template) - Un modèle utilisant Svelte et Vite avec TailwindCSS v3
- [wails-sveltekit-template](https://github.com/h8gi/wails-sveltekit-template) - Un modèle utilisant SvelteKit
## Elm
- [wails-elm-template](https://github.com/benjamin-thomas/wails-elm-template) - Développez votre application GUI avec de la programmation fonctionnelle et une configuration de développement en direct :tada: :rocket:
- [wails-template-elm-tailwind](https://github.com/rnice01/wails-template-elm-tailwind) - Combine les puissances :muscle: d'Elm + Tailwind CSS + Wails ! Rechargement automatique pris en charge.
## Pure JavaScript (Vanilla)
- [wails-pure-js-template](https://github.com/KiddoV/wails-pure-js-template) - Un modèle avec rien que du JavaScript, du HTML et du CSS de base

Some files were not shown because too many files have changed in this diff Show more