Compare commits

...

15 Commits

Author SHA1 Message Date
Lea Anthony 2aae05f5bb Update plugins and docs 2024-04-14 21:35:47 +10:00
Lea Anthony a4971ab58e Update kvstore docs 2024-04-14 15:43:11 +10:00
Lea Anthony fe618b71b3 Update kvstore plugin + example 2024-04-14 15:39:29 +10:00
Lea Anthony c5c3f2ab5e refactor tabs in example 2024-04-13 21:01:40 +10:00
Lea Anthony fc2c5207fc Fix double click 2024-04-13 21:01:21 +10:00
Lea Anthony 2db9537c8c Add hashes example 2024-04-13 20:41:03 +10:00
Lea Anthony 38475a5da4 Fix drag/resize 2024-04-13 17:07:51 +10:00
Lea Anthony bf87f026f7 Better sqlite demo 2024-04-11 21:59:30 +10:00
Lea Anthony 724e0c7b86 Major plugin updates 2024-04-11 20:43:13 +10:00
Lea Anthony cf130a6e25 [windows] Fix directory select in Open Dialog 2024-04-06 14:38:43 +11:00
Lea Anthony e91c30fad0 Small doctor improvements 2024-04-03 20:48:55 +11:00
Lea Anthony 3e3f7b9273 Update discord link 2024-04-03 08:27:32 +11:00
Lea Anthony 88ff84f5a5 [windows] Improve Set window icon when in debug mode 2024-04-01 05:02:39 +11:00
Lea Anthony eee373f15a [windows] Set window icon when in debug mode 2024-04-01 04:55:34 +11:00
Lea Anthony c839c053cb [windows] Fix min/max buttons 2024-03-29 21:44:59 +11:00
43 changed files with 1042 additions and 398 deletions
@@ -5,7 +5,7 @@ posts before creating new ones. Here are the different ways to provide feedback:
=== "Bugs"
If you find a bug, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/3mgVyGua) channel on Discord.
If you find a bug, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/Vgff2p8gsy) channel on Discord.
- The post should clearly state what the bug is and have a simple reproducible example. If the docs are unclear what *should* happen, please include that in the post.
- The post should be given the `Bug` tag.
@@ -24,17 +24,17 @@ posts before creating new ones. Here are the different ways to provide feedback:
If you have a fix for a bug or an update for documentation, please do the following:
- Open a pull request on the [Wails repository](https://github.com/wailsapp/wails). The title of the PR should start with `[v3 alpha]`.
- Create a post in the [v3 Alpha Feedback](https://discord.gg/3mgVyGua) channel.
- Create a post in the [v3 Alpha Feedback](https://discord.gg/Vgff2p8gsy) channel.
- The post should be given the `PR` tag.
- Please include a link to the PR in your post.
=== "Suggestions"
If you have a suggestion, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/3mgVyGua) channel on Discord:
If you have a suggestion, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/Vgff2p8gsy) channel on Discord:
- The post should be given the `Suggestion` tag.
Please feel free to reach out to us on [Discord](https://discord.gg/3mgVyGua) if you have any questions.
Please feel free to reach out to us on [Discord](https://discord.gg/Vgff2p8gsy) if you have any questions.
=== "Upvoting"
+65
View File
@@ -0,0 +1,65 @@
# Wails v3 Plugin Guide
Wails v3 introduces the concept of plugins. A plugin is a self-contained module that can extend the functionality of your Wails application.
This guide will walk you through the structure and functionality of a Wails plugin.
## Plugin Structure
A Wails plugin is a standard Go module, typically consisting of the following files:
- `plugin.go`: This is the main Go file where the plugin's functionality is implemented.
- `plugin.yaml`: This is the plugin's metadata file. It contains information about the plugin such as its name, author, version, and more.
- `assets/`: This directory contains any static assets that the plugin might need.
- `README.md`: This file provides documentation for the plugin.
## Plugin Implementation
In `plugin.go`, a plugin is defined as a struct that implements the `application.Plugin` interface. This interface requires the following methods:
- `Init()`: This method is called when the plugin is initialized.
- `Shutdown()`: This method is called when the application is shutting down.
- `Name()`: This method returns the name of the plugin.
- `CallableByJS()`: This method returns a list of method names that can be called from the frontend.
In addition to these methods, you can define any number of additional methods that implement the plugin's functionality.
These methods can be called from the frontend using the `wails.Plugin()` function.
## Plugin Metadata
The `plugin.yaml` file contains metadata about the plugin. This includes the plugin's name, description, author, version, website, repository, and license.
## Plugin Assets
Any static assets that the plugin needs can be placed in the `assets/` directory.
These assets can be accessed by the frontend by requesting them at the plugin base path.
This path is `/wails/plugins/<plugin-name>/`.
### Example
If a plugin named `logopack` has an asset named `logo.png`, the frontend can access it at `/wails/plugins/logopack/logo.png`.
## Plugin Documentation
The `README.md` file provides documentation for the plugin. This should include instructions on how to install and use the plugin, as well as any other information that users of the plugin might find useful.
## Example
Here's an example of how to use a plugin in your Wails application:
```go
Plugins: map[string]application.Plugin{
"log": log.NewPlugin(),
},
```
And here's how you can call a plugin method from the frontend:
```js
wails.Plugin("log","Debug","hello world")
```
## Support
If you encounter any issues with a plugin, please raise a ticket in the plugin's repository.
!!! note
The Wails team does not provide support for third-party plugins.
+1
View File
@@ -141,6 +141,7 @@ nav:
- Next Steps: getting-started/next-steps.md
- Learn More:
- Runtime: learn/runtime.md
- Plugins: learn/plugins.md
- Feedback: getting-started/feedback.md
- Feedback: getting-started/feedback.md
- What's New in v3?: whats-new.md
+1 -1
View File
@@ -67,7 +67,7 @@ func diagnoseEnvironment(f *flags.Doctor) error {
wailsTableData = append(wailsTableData, []string{"Package Manager", info.PM.Name()})
}
err = pterm.DefaultTable.WithData(wailsTableData).Render()
err = pterm.DefaultTable.WithBoxed().WithData(wailsTableData).Render()
if err != nil {
return err
}
+2 -2
View File
@@ -24,10 +24,10 @@
</head>
<body>
<div class="container">
<div class="quarter" style="background-color: lightblue; --webkit-app-region: drag">Draggable</div>
<div class="quarter" style="background-color: lightblue; --wails-draggable: drag">Draggable</div>
<div class="quarter" style="background-color: lightgreen;">Not Draggable</div>
<div class="quarter" style="background-color: lightpink;">Not Draggable</div>
<div class="quarter" style="background-color: lightyellow; --webkit-app-region: drag">Draggable</div>
<div class="quarter" style="background-color: lightyellow; --wails-draggable: drag">Draggable</div>
</div>
</body>
</html>
File diff suppressed because one or more lines are too long
+202
View File
@@ -0,0 +1,202 @@
html {
background-color: rgba(33, 37, 43);
text-align: center;
color: white;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-webkit-touch-callout: none;
}
body {
padding-top: 40px;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
overscroll-behavior: none;
}
.logo {
width: 30%;
height: 20%;
}
/* CSS */
.button-42 {
background-color: initial;
background-image: linear-gradient(-180deg, #555555, #2f2f2f);
border-radius: 6px;
box-shadow: rgba(0, 0, 0, 0.1) 0 2px 4px;
color: #FFFFFF;
cursor: pointer;
display: inline-block;
font-family: Inter,-apple-system,system-ui,Roboto,"Helvetica Neue",Arial,sans-serif;
height: 35px;
line-height: 35px;
outline: 0;
overflow: hidden;
padding: 0 20px;
pointer-events: auto;
position: relative;
text-align: center;
touch-action: manipulation;
user-select: none;
-webkit-user-select: none;
vertical-align: top;
white-space: nowrap;
z-index: 9;
border: 0;
transition: box-shadow .2s;
font-size: medium;
}
p {
font-size: 1rem;
}
.button-42:hover {
background-image: linear-gradient(-180deg, #cb3939, #610000);
}
html {
background-color: rgba(33, 37, 43);
text-align: center;
color: white;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-webkit-touch-callout: none;
}
body {
padding-top: 40px;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
overscroll-behavior: none;
}
.button-42 {
background-color: initial;
background-image: linear-gradient(-180deg, #555555, #2f2f2f);
border-radius: 6px;
box-shadow: rgba(0, 0, 0, 0.1) 0 2px 4px;
color: #FFFFFF;
cursor: pointer;
display: inline-block;
font-family: Inter,-apple-system,system-ui,Roboto,"Helvetica Neue",Arial,sans-serif;
height: 35px;
line-height: 35px;
outline: 0;
overflow: hidden;
padding: 0 20px;
pointer-events: auto;
position: relative;
text-align: center;
touch-action: manipulation;
user-select: none;
-webkit-user-select: none;
vertical-align: top;
white-space: nowrap;
z-index: 9;
border: 0;
transition: box-shadow .2s;
font-size: medium;
}
p {
font-size: 1rem;
}
.button-42:hover {
background-image: linear-gradient(-180deg, #cb3939, #610000);
}
.tab {
overflow: hidden;
background-color: #f1f1f100;
margin-left: 20px;
}
.tab button {
background-color: transparent; /* Make the background transparent */
color: white; /* Make the text white */
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
position: relative; /* Added for the underline */
}
.tab button::after { /* Added for the underline */
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: 0;
left: 0;
background-color: #a40505;
visibility: hidden;
transition: all 0.3s ease-in-out;
}
.tab button:hover::after, /* Added for the underline */
.tab button.active::after { /* Added for the underline */
width: 100%;
visibility: visible;
}
.tab button.active {
background-color: transparent; /* Make the background transparent */
color: red;
}
.tabcontent {
display: none;
padding: 6px 12px;
border-top: none;
}
#sqlresults, #hashresults {
font-family: 'Courier New', Courier, monospace;
min-height: 100px;
}
#selectquery {
width: 90%;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #005467;
color: white;
}
tr:hover {
background-color: #888;
}
.error-message {
color: #FF4B2B; /* Bright red color for better visibility in dark mode */
background-color: #1E1E1E; /* Dark background for the error message */
border: 1px solid #FF4B2B; /* Border color same as text color */
padding: 10px; /* Padding around the text */
margin: 10px 0; /* Margin top and bottom */
border-radius: 5px; /* Rounded corners */
text-align: center; /* Center the text */
}
.narrowColumn {
width: 1%; /* Adjust as needed */
white-space: nowrap;
}
@@ -1,4 +0,0 @@
# Experimental Plugins
This directory contains experimental plugins. These plugins are not supported by the Wails team and are provided as-is.
They may be removed at any time. If you have any thoughts about these plugins, they can be discussed on the [Wails Discord](https://discord.gg/u8JkcWsG).
@@ -15,7 +15,8 @@ Add the plugin to the `Plugins` option in the Applications options:
You can then call the Generate method from the frontend:
```js
wails.Plugin("hashes","Generate","hello world").then((result) => console.log(result))
import {Call} from "/wails/runtime.js";
Call.Plugin("hashes","Generate","hello world").then((result) => console.log(result))
```
This method returns a struct with the following fields:
@@ -6,6 +6,7 @@ import (
"crypto/sha256"
"encoding/hex"
"github.com/wailsapp/wails/v3/pkg/application"
"io/fs"
)
// ---------------- Plugin Setup ----------------
@@ -32,8 +33,8 @@ func (r *Plugin) CallableByJS() []string {
}
}
func (r *Plugin) InjectJS() string {
return ""
func (r *Plugin) Assets() fs.FS {
return nil
}
// ---------------- Plugin Methods ----------------
+5 -7
View File
@@ -2,8 +2,9 @@ package main
import (
"embed"
"log/slog"
"os"
"plugin_demo/plugins/hashes"
"plugin_demo/hashes"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/plugins/kvstore"
@@ -24,6 +25,7 @@ func main() {
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
LogLevel: slog.LevelDebug,
Plugins: map[string]application.Plugin{
"hashes": hashes.NewPlugin(),
"log": log.NewPlugin(),
@@ -34,10 +36,6 @@ func main() {
Filename: "store.json",
AutoSave: true,
}),
//"server": server.NewPlugin(&server.Config{
// Enabled: true,
// Port: 34115,
//}),
"single_instance": single_instance.NewPlugin(&single_instance.Config{
// When true, the original app will be activated when a second instance is launched
ActivateAppOnSubsequentLaunch: true,
@@ -50,8 +48,8 @@ func main() {
})
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
DevToolsEnabled: true,
OpenInspectorOnStartup: true,
Width: 1024,
Height: 768,
})
err := app.Run()
-8
View File
@@ -1,8 +0,0 @@
export namespace HashesPlugin {
export interface Hashes {
md5: string;
sha1: string;
sha256: string;
}
}
@@ -1,4 +0,0 @@
// Generate takes a string and returns a number of hashes for it
export function Generate(input) {
return wails.Plugin("hashes","Generate",input);
}
+1 -1
View File
@@ -1 +1 @@
{"url2":"https://reddit.com"}
{"q":"www","url2":"https://reddit.com"}
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/wailsapp/wails/v3
go 1.22
go 1.21
require (
github.com/atterpac/refresh v0.6.3
+61 -15
View File
@@ -1,12 +1,14 @@
package assetserver
import (
"embed"
"fmt"
"html"
"io/fs"
"net"
"net/http"
"net/http/httptest"
"net/url"
"path"
"strings"
"time"
)
@@ -14,6 +16,7 @@ import (
const (
webViewRequestHeaderWindowId = "x-wails-window-id"
webViewRequestHeaderWindowName = "x-wails-window-name"
pluginPrefix = "/wails/plugin"
)
type RuntimeHandler interface {
@@ -25,13 +28,17 @@ type AssetServer struct {
handler http.Handler
pluginScripts map[string]string
//pluginScripts map[string]string
assetServerWebView
pluginAssets map[string]fs.FS
}
func NewAssetServer(options *Options) (*AssetServer, error) {
result := &AssetServer{options: options}
result := &AssetServer{
options: options,
pluginAssets: make(map[string]fs.FS),
}
userHandler := options.Handler
if userHandler == nil {
@@ -84,8 +91,8 @@ func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userH
// header.Add(HeaderCacheControl, "no-cache")
//}
path := req.URL.Path
switch path {
reqPath := req.URL.Path
switch reqPath {
case "", "/", "/index.html":
recorder := httptest.NewRecorder()
userHandler.ServeHTTP(recorder, req)
@@ -105,9 +112,34 @@ func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userH
}
default:
// Check if this is a plugin script
if script, ok := a.pluginScripts[path]; ok {
a.writeBlob(rw, path, []byte(script))
// Check if this is a plugin asset
if !strings.HasPrefix(reqPath, pluginPrefix) {
userHandler.ServeHTTP(rw, req)
return
}
// Ensure there is 4 parts to the reqPath
parts := strings.SplitN(reqPath, "/", 5)
if len(parts) < 5 {
rw.WriteHeader(http.StatusNotFound)
return
}
// Get the first 3 parts of the reqPath
pluginPath := "/" + path.Join(parts[1], parts[2], parts[3])
// Get the remaining part of the reqPath
fileName := parts[4]
// Check if this is a registered plugin asset
if assetFS, ok := a.pluginAssets[pluginPath]; ok {
// Check if the file exists
file, err := fs.ReadFile(assetFS, fileName)
if err != nil {
a.serveError(rw, err, "Unable to read file %s", reqPath)
return
}
a.writeBlob(rw, reqPath, file)
} else {
userHandler.ServeHTTP(rw, req)
}
@@ -127,14 +159,28 @@ func (a *AssetServer) serveError(rw http.ResponseWriter, err error, msg string,
rw.WriteHeader(http.StatusInternalServerError)
}
func (a *AssetServer) AddPluginScript(pluginName string, script string) {
if a.pluginScripts == nil {
a.pluginScripts = make(map[string]string)
//func (a *AssetServer) AddPluginScript(pluginName string, script string) {
// if a.pluginScripts == nil {
// a.pluginScripts = make(map[string]string)
// }
// pluginName = strings.ReplaceAll(pluginName, "/", "_")
// pluginName = html.EscapeString(pluginName)
// pluginScriptName := fmt.Sprintf("/wails/plugin/%s.js", pluginName)
// a.pluginScripts[pluginScriptName] = script
//}
func (a *AssetServer) AddPluginAssets(pluginPath string, vfs fs.FS) error {
pluginPath = path.Join(pluginPrefix, pluginPath)
_, exists := a.pluginAssets[pluginPath]
if exists {
return fmt.Errorf("plugin path already exists: %s", pluginPath)
}
pluginName = strings.ReplaceAll(pluginName, "/", "_")
pluginName = html.EscapeString(pluginName)
pluginScriptName := fmt.Sprintf("/wails/plugin/%s.js", pluginName)
a.pluginScripts[pluginScriptName] = script
if embedFs, isEmbedFs := vfs.(embed.FS); isEmbedFs {
rootFolder, _ := findEmbedRootPath(embedFs)
vfs, _ = fs.Sub(vfs, path.Clean(rootFolder))
}
a.pluginAssets[pluginPath] = vfs
return nil
}
func GetStartURL(userURL string) (string, error) {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

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