mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 03:21:32 +08:00
Fix default index.html serving.
Support multi-language default. Remove default page from production builds. Improve defaultindex.html.
This commit is contained in:
parent
beacf06c7d
commit
a0b2ab7c0a
@ -21,9 +21,6 @@ type Logger interface {
|
||||
Error(message string, args ...interface{})
|
||||
}
|
||||
|
||||
//go:embed defaultindex.html
|
||||
var defaultHTML []byte
|
||||
|
||||
const (
|
||||
indexHTML = "index.html"
|
||||
)
|
||||
@ -120,7 +117,9 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
statInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
@ -143,7 +142,9 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
statInfo, err = file.Stat()
|
||||
if err != nil {
|
||||
|
0
v3/examples/dialogs-basic/.hidden_file
Normal file
0
v3/examples/dialogs-basic/.hidden_file
Normal file
36
v3/examples/dialogs-basic/README.md
Normal file
36
v3/examples/dialogs-basic/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Dialog Test Application
|
||||
|
||||
This application is designed to test macOS file dialog functionality across different versions of macOS. It provides a comprehensive suite of tests for various dialog features and configurations.
|
||||
|
||||
## Features Tested
|
||||
|
||||
1. Basic file open dialog
|
||||
2. Single extension filter
|
||||
3. Multiple extension filter
|
||||
4. Multiple file selection
|
||||
5. Directory selection
|
||||
6. Save dialog with extension
|
||||
7. Complex filters
|
||||
8. Hidden files
|
||||
9. Default directory
|
||||
10. Full featured dialog with all options
|
||||
|
||||
## Running the Tests
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
## Test Results
|
||||
|
||||
When running tests:
|
||||
- Each test will show the selected file(s) and their types
|
||||
- For multiple selections, all selected files will be listed
|
||||
- Errors will be displayed in an error dialog
|
||||
- The application logs debug information to help track issues
|
||||
|
||||
## Notes
|
||||
|
||||
- This test application is primarily for development and testing purposes
|
||||
- It can be used to verify dialog behavior across different macOS versions
|
||||
- The tests are designed to not interfere with CI pipelines
|
260
v3/examples/dialogs-basic/main.go
Normal file
260
v3/examples/dialogs-basic/main.go
Normal file
@ -0,0 +1,260 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Dialog Test",
|
||||
Description: "Test application for macOS dialogs",
|
||||
Logger: application.DefaultLogger(slog.LevelDebug),
|
||||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||
},
|
||||
})
|
||||
|
||||
// Create main window
|
||||
mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Dialog Tests",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
MinWidth: 800,
|
||||
MinHeight: 600,
|
||||
})
|
||||
mainWindow.SetAlwaysOnTop(true)
|
||||
|
||||
// Create main menu
|
||||
menu := app.NewMenu()
|
||||
app.SetMenu(menu)
|
||||
menu.AddRole(application.AppMenu)
|
||||
menu.AddRole(application.EditMenu)
|
||||
menu.AddRole(application.WindowMenu)
|
||||
|
||||
// Add test menu
|
||||
testMenu := menu.AddSubmenu("Tests")
|
||||
|
||||
// Test 1: Basic file open with no filters (no window)
|
||||
testMenu.Add("1. Basic Open (No Window)").OnClick(func(ctx *application.Context) {
|
||||
result, err := application.OpenFileDialog().
|
||||
CanChooseFiles(true).
|
||||
PromptForSingleSelection()
|
||||
showResult("Basic Open", result, err, nil)
|
||||
})
|
||||
|
||||
// Test 1b: Basic file open with window
|
||||
testMenu.Add("1b. Basic Open (With Window)").OnClick(func(ctx *application.Context) {
|
||||
result, err := application.OpenFileDialog().
|
||||
CanChooseFiles(true).
|
||||
AttachToWindow(mainWindow).
|
||||
PromptForSingleSelection()
|
||||
showResult("Basic Open", result, err, mainWindow)
|
||||
})
|
||||
|
||||
// Test 2: Open with single extension filter
|
||||
testMenu.Add("2. Single Filter").OnClick(func(ctx *application.Context) {
|
||||
result, err := application.OpenFileDialog().
|
||||
CanChooseFiles(true).
|
||||
AddFilter("Text Files", "*.txt").
|
||||
AttachToWindow(mainWindow).
|
||||
PromptForSingleSelection()
|
||||
showResult("Single Filter", result, err, mainWindow)
|
||||
})
|
||||
|
||||
// Test 3: Open with multiple extension filter
|
||||
testMenu.Add("3. Multiple Filter").OnClick(func(ctx *application.Context) {
|
||||
result, err := application.OpenFileDialog().
|
||||
CanChooseFiles(true).
|
||||
AddFilter("Documents", "*.txt;*.md;*.doc;*.docx").
|
||||
AttachToWindow(mainWindow).
|
||||
PromptForSingleSelection()
|
||||
showResult("Multiple Filter", result, err, mainWindow)
|
||||
})
|
||||
|
||||
// Test 4: Multiple file selection
|
||||
testMenu.Add("4. Multiple Selection").OnClick(func(ctx *application.Context) {
|
||||
results, err := application.OpenFileDialog().
|
||||
CanChooseFiles(true).
|
||||
AddFilter("Images", "*.png;*.jpg;*.jpeg").
|
||||
AttachToWindow(mainWindow).
|
||||
PromptForMultipleSelection()
|
||||
if err != nil {
|
||||
showError("Multiple Selection", err, mainWindow)
|
||||
return
|
||||
}
|
||||
showResults("Multiple Selection", results, mainWindow)
|
||||
})
|
||||
|
||||
// Test 5: Directory selection
|
||||
testMenu.Add("5. Directory Selection").OnClick(func(ctx *application.Context) {
|
||||
result, err := application.OpenFileDialog().
|
||||
CanChooseDirectories(true).
|
||||
CanChooseFiles(false).
|
||||
AttachToWindow(mainWindow).
|
||||
PromptForSingleSelection()
|
||||
showResult("Directory Selection", result, err, mainWindow)
|
||||
})
|
||||
|
||||
// Test 6: Save dialog with extension
|
||||
testMenu.Add("6. Save Dialog").OnClick(func(ctx *application.Context) {
|
||||
result, err := application.SaveFileDialog().
|
||||
SetFilename("test.txt").
|
||||
AddFilter("Text Files", "*.txt").
|
||||
AttachToWindow(mainWindow).
|
||||
PromptForSingleSelection()
|
||||
showResult("Save Dialog", result, err, mainWindow)
|
||||
})
|
||||
|
||||
// Test 7: Complex filters
|
||||
testMenu.Add("7. Complex Filters").OnClick(func(ctx *application.Context) {
|
||||
result, err := application.OpenFileDialog().
|
||||
CanChooseFiles(true).
|
||||
AddFilter("All Documents", "*.txt;*.md;*.doc;*.docx;*.pdf").
|
||||
AddFilter("Text Files", "*.txt").
|
||||
AddFilter("Markdown", "*.md").
|
||||
AddFilter("Word Documents", "*.doc;*.docx").
|
||||
AddFilter("PDF Files", "*.pdf").
|
||||
AttachToWindow(mainWindow).
|
||||
PromptForSingleSelection()
|
||||
showResult("Complex Filters", result, err, mainWindow)
|
||||
})
|
||||
|
||||
// Test 8: Hidden files
|
||||
testMenu.Add("8. Show Hidden").OnClick(func(ctx *application.Context) {
|
||||
result, err := application.OpenFileDialog().
|
||||
CanChooseFiles(true).
|
||||
ShowHiddenFiles(true).
|
||||
AttachToWindow(mainWindow).
|
||||
PromptForSingleSelection()
|
||||
showResult("Show Hidden", result, err, mainWindow)
|
||||
})
|
||||
|
||||
// Test 9: Default directory
|
||||
testMenu.Add("9. Default Directory").OnClick(func(ctx *application.Context) {
|
||||
home, _ := os.UserHomeDir()
|
||||
result, err := application.OpenFileDialog().
|
||||
CanChooseFiles(true).
|
||||
SetDirectory(home).
|
||||
AttachToWindow(mainWindow).
|
||||
PromptForSingleSelection()
|
||||
showResult("Default Directory", result, err, mainWindow)
|
||||
})
|
||||
|
||||
// Test 10: Full featured dialog
|
||||
testMenu.Add("10. Full Featured").OnClick(func(ctx *application.Context) {
|
||||
home, _ := os.UserHomeDir()
|
||||
dialog := application.OpenFileDialog().
|
||||
SetTitle("Full Featured Dialog").
|
||||
SetDirectory(home).
|
||||
CanChooseFiles(true).
|
||||
CanCreateDirectories(true).
|
||||
ShowHiddenFiles(true).
|
||||
ResolvesAliases(true).
|
||||
AllowsOtherFileTypes(true).
|
||||
AttachToWindow(mainWindow)
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
dialog.SetMessage("Please select files")
|
||||
}
|
||||
|
||||
dialog.AddFilter("All Supported", "*.txt;*.md;*.pdf;*.png;*.jpg")
|
||||
dialog.AddFilter("Documents", "*.txt;*.md;*.pdf")
|
||||
dialog.AddFilter("Images", "*.png;*.jpg;*.jpeg")
|
||||
|
||||
results, err := dialog.PromptForMultipleSelection()
|
||||
if err != nil {
|
||||
showError("Full Featured", err, mainWindow)
|
||||
return
|
||||
}
|
||||
showResults("Full Featured", results, mainWindow)
|
||||
})
|
||||
|
||||
// Show the window
|
||||
mainWindow.Show()
|
||||
|
||||
// Run the app
|
||||
if err := app.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func showResult(test string, result string, err error, window *application.WebviewWindow) {
|
||||
if err != nil {
|
||||
showError(test, err, window)
|
||||
return
|
||||
}
|
||||
if result == "" {
|
||||
dialog := application.InfoDialog().
|
||||
SetTitle(test).
|
||||
SetMessage("No file selected")
|
||||
if window != nil {
|
||||
dialog.AttachToWindow(window)
|
||||
}
|
||||
dialog.Show()
|
||||
return
|
||||
}
|
||||
dialog := application.InfoDialog().
|
||||
SetTitle(test).
|
||||
SetMessage(fmt.Sprintf("Selected: %s\nType: %s", result, getFileType(result)))
|
||||
if window != nil {
|
||||
dialog.AttachToWindow(window)
|
||||
}
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
func showResults(test string, results []string, window *application.WebviewWindow) {
|
||||
if len(results) == 0 {
|
||||
dialog := application.InfoDialog().
|
||||
SetTitle(test).
|
||||
SetMessage("No files selected")
|
||||
if window != nil {
|
||||
dialog.AttachToWindow(window)
|
||||
}
|
||||
dialog.Show()
|
||||
return
|
||||
}
|
||||
var message strings.Builder
|
||||
message.WriteString(fmt.Sprintf("Selected %d files:\n\n", len(results)))
|
||||
for _, result := range results {
|
||||
message.WriteString(fmt.Sprintf("%s (%s)\n", result, getFileType(result)))
|
||||
}
|
||||
dialog := application.InfoDialog().
|
||||
SetTitle(test).
|
||||
SetMessage(message.String())
|
||||
if window != nil {
|
||||
dialog.AttachToWindow(window)
|
||||
}
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
func showError(test string, err error, window *application.WebviewWindow) {
|
||||
dialog := application.ErrorDialog().
|
||||
SetTitle(test).
|
||||
SetMessage(fmt.Sprintf("Error: %v", err))
|
||||
if window != nil {
|
||||
dialog.AttachToWindow(window)
|
||||
}
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
func getFileType(path string) string {
|
||||
if path == "" {
|
||||
return "unknown"
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
if ext == "" {
|
||||
if fi, err := os.Stat(path); err == nil && fi.IsDir() {
|
||||
return "directory"
|
||||
}
|
||||
return "no extension"
|
||||
}
|
||||
return ext
|
||||
}
|
1
v3/examples/dialogs-basic/test.txt
Normal file
1
v3/examples/dialogs-basic/test.txt
Normal file
@ -0,0 +1 @@
|
||||
This is a sample text file to test filtering.
|
BIN
v3/examples/dialogs-basic/wails-logo-small.jpg
Normal file
BIN
v3/examples/dialogs-basic/wails-logo-small.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
v3/examples/dialogs-basic/wails-logo-small.png
Normal file
BIN
v3/examples/dialogs-basic/wails-logo-small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
@ -14,6 +14,7 @@ const (
|
||||
webViewRequestHeaderWindowId = "x-wails-window-id"
|
||||
webViewRequestHeaderWindowName = "x-wails-window-name"
|
||||
servicePrefix = "wails/services"
|
||||
HeaderAcceptLanguage = "accept-language"
|
||||
)
|
||||
|
||||
type RuntimeHandler interface {
|
||||
@ -100,7 +101,14 @@ func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userH
|
||||
a.writeBlob(rw, indexHTML, recorder.Body.Bytes())
|
||||
|
||||
case http.StatusNotFound:
|
||||
a.writeBlob(rw, indexHTML, defaultIndexHTML())
|
||||
// Read the accept-language header
|
||||
acceptLanguage := req.Header.Get(HeaderAcceptLanguage)
|
||||
if acceptLanguage == "" {
|
||||
acceptLanguage = "en"
|
||||
}
|
||||
// Set content type for default index.html
|
||||
header.Set(HeaderContentType, "text/html; charset=utf-8")
|
||||
a.writeBlob(rw, indexHTML, defaultIndexHTML(acceptLanguage))
|
||||
|
||||
default:
|
||||
rw.WriteHeader(recorder.Code)
|
||||
|
@ -2,6 +2,43 @@
|
||||
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"embed"
|
||||
_ "embed"
|
||||
"io"
|
||||
iofs "io/fs"
|
||||
)
|
||||
|
||||
//go:embed defaults
|
||||
var defaultHTML embed.FS
|
||||
|
||||
func defaultIndexHTML(language string) []byte {
|
||||
result := []byte("index.html not found")
|
||||
// Create an fs.Sub in the defaults directory
|
||||
defaults, err := iofs.Sub(defaultHTML, "defaults")
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
// Get the 2 character language code
|
||||
lang := "en"
|
||||
if len(language) >= 2 {
|
||||
lang = language[:2]
|
||||
}
|
||||
// Now we can read the index.html file in the format
|
||||
// index.<lang>.html.
|
||||
|
||||
indexFile, err := defaults.Open("index." + lang + ".html")
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
indexBytes, err := io.ReadAll(indexFile)
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
return indexBytes
|
||||
}
|
||||
|
||||
func (a *AssetServer) LogDetails() {
|
||||
var info = []any{
|
||||
"middleware", a.options.Middleware != nil,
|
||||
|
@ -2,4 +2,8 @@
|
||||
|
||||
package assetserver
|
||||
|
||||
func defaultIndexHTML(_ string) []byte {
|
||||
return []byte("index.html not found")
|
||||
}
|
||||
|
||||
func (a *AssetServer) LogDetails() {}
|
||||
|
@ -11,13 +11,6 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
//go:embed defaultindex.html
|
||||
var defaultHTML []byte
|
||||
|
||||
func defaultIndexHTML() []byte {
|
||||
return defaultHTML
|
||||
}
|
||||
|
||||
func NewAssetFileServer(vfs fs.FS) http.Handler {
|
||||
devServerURL := GetDevServerURL()
|
||||
if devServerURL == "" {
|
||||
|
File diff suppressed because one or more lines are too long
350
v3/internal/assetserver/defaults/index.en.html
Normal file
350
v3/internal/assetserver/defaults/index.en.html
Normal file
File diff suppressed because one or more lines are too long
302
v3/internal/assetserver/defaults/index.zh.html
Normal file
302
v3/internal/assetserver/defaults/index.zh.html
Normal file
File diff suppressed because one or more lines are too long
@ -3,10 +3,14 @@
|
||||
#ifndef _DIALOGS_DELEGATE_H_
|
||||
#define _DIALOGS_DELEGATE_H_
|
||||
|
||||
#import <UniformTypeIdentifiers/UTType.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
// create an NSOpenPanel delegate to handle the callback
|
||||
// Conditionally import UniformTypeIdentifiers based on OS version
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
|
||||
#import <UniformTypeIdentifiers/UTType.h>
|
||||
#endif
|
||||
|
||||
// OpenPanel delegate to handle file filtering
|
||||
@interface OpenPanelDelegate : NSObject <NSOpenSavePanelDelegate>
|
||||
@property (nonatomic, strong) NSArray *allowedExtensions;
|
||||
@end
|
||||
|
@ -8,29 +8,31 @@
|
||||
if (url == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
BOOL isDirectory = NO;
|
||||
if ([fileManager fileExistsAtPath:url.path isDirectory:&isDirectory] && isDirectory) {
|
||||
return YES;
|
||||
}
|
||||
if (self.allowedExtensions == nil) {
|
||||
|
||||
// If no extensions specified, allow all files
|
||||
if (self.allowedExtensions == nil || [self.allowedExtensions count] == 0) {
|
||||
return YES;
|
||||
}
|
||||
NSString *extension = url.pathExtension;
|
||||
if (extension == nil) {
|
||||
|
||||
NSString *extension = [url.pathExtension lowercaseString];
|
||||
if (extension == nil || [extension isEqualToString:@""]) {
|
||||
return NO;
|
||||
}
|
||||
if ([extension isEqualToString:@""]) {
|
||||
return NO;
|
||||
}
|
||||
if ([self.allowedExtensions containsObject:extension]) {
|
||||
return YES;
|
||||
|
||||
// Check if the extension is in our allowed list (case insensitive)
|
||||
for (NSString *allowedExt in self.allowedExtensions) {
|
||||
if ([[allowedExt lowercaseString] isEqualToString:extension]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user