5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 15:11:53 +08:00

Move go-common-file-dialog into project. Fix Title bug.

This commit is contained in:
Lea Anthony 2022-06-01 21:00:52 +10:00
parent 614f50d5d3
commit 6a07cbd505
27 changed files with 1125 additions and 26 deletions

View File

@ -53,8 +53,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/idgen v1.0.0/go.mod h1:4nBZnt8ml/f/ic/EVQuLxuj817RccT2fyrUaZFxrcVA=

View File

@ -12,7 +12,6 @@ module changeme
github.com/labstack/echo/v4 v4.7.2 // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
github.com/leaanthony/typescriptify-golang-structs v0.1.7 // indirect

View File

@ -18,7 +18,6 @@ github.com/jchv/go-winloader v0.0.0-20200815041850-dec1ee9a7fd5 // indirect
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
github.com/leaanthony/typescriptify-golang-structs v0.1.7 // indirect
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect

View File

@ -81,8 +81,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/idgen v1.0.0/go.mod h1:4nBZnt8ml/f/ic/EVQuLxuj817RccT2fyrUaZFxrcVA=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=

View File

@ -18,7 +18,6 @@ github.com/jchv/go-winloader v0.0.0-20200815041850-dec1ee9a7fd5 // indirect
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
github.com/leaanthony/typescriptify-golang-structs v0.1.7 // indirect
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect

View File

@ -81,8 +81,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/idgen v1.0.0/go.mod h1:4nBZnt8ml/f/ic/EVQuLxuj817RccT2fyrUaZFxrcVA=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=

View File

@ -17,7 +17,6 @@ require (
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/debme v1.2.1
github.com/leaanthony/go-ansi-parser v1.0.1
github.com/leaanthony/go-common-file-dialog v1.0.3
github.com/leaanthony/gosod v1.0.3
github.com/leaanthony/idgen v1.0.0
github.com/leaanthony/slicer v1.5.0

View File

@ -79,8 +79,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/idgen v1.0.0 h1:IZreR+JGEzFV4yeVuBZA25gM0keUoFy+RDUldncQ+Jw=

View File

@ -14,7 +14,7 @@ import (
"strconv"
"syscall"
"github.com/leaanthony/go-common-file-dialog/cfd"
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/cfd"
"github.com/wailsapp/wails/v2/pkg/runtime"
"golang.org/x/sys/windows"

View File

@ -4,8 +4,8 @@
package windows
import (
"github.com/leaanthony/go-common-file-dialog/cfd"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/cfd"
"golang.org/x/sys/windows"
"syscall"
)

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Harry Phillips
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,31 @@
# Common File Dialog bindings for Golang
[Project Home](https://github.com/harry1453/go-common-file-dialog)
This library contains bindings for Windows Vista and
newer's [Common File Dialogs](https://docs.microsoft.com/en-us/windows/win32/shell/common-file-dialog), which is the
standard system dialog for selecting files or folders to open or save.
The Common File Dialogs have to be accessed via
the [COM Interface](https://en.wikipedia.org/wiki/Component_Object_Model), normally via C++ or via bindings (like in C#)
.
This library contains bindings for Golang. **It does not require CGO**, and contains empty stubs for non-windows
platforms (so is safe to compile and run on platforms other than windows, but will just return errors at runtime).
This can be very useful if you want to quickly get a file selector in your Golang application. The `cfdutil` package
contains utility functions with a single call to open and configure a dialog, and then get the result from it. Examples
for this are in [`_examples/usingutil`](_examples/usingutil). Or, if you want finer control over the dialog's operation,
you can use the base package. Examples for this are in [`_examples/notusingutil`](_examples/notusingutil).
This library is available under the MIT license.
Currently supported features:
* Open File Dialog (to open a single file)
* Open Multiple Files Dialog (to open multiple files)
* Open Folder Dialog
* Save File Dialog
* Dialog "roles" to allow Windows to remember different "last locations" for different types of dialog
* Set dialog Title, Default Folder and Initial Folder
* Set dialog File Filters

View File

@ -0,0 +1,72 @@
// Cross-platform.
// Common File Dialogs
package cfd
type Dialog interface {
// Show the dialog to the user.
// Blocks until the user has closed the dialog.
Show() error
// Sets the dialog's parent window. Use 0 to set the dialog to have no parent window.
SetParentWindowHandle(hwnd uintptr)
// Show the dialog to the user.
// Blocks until the user has closed the dialog and returns their selection.
// Returns an error if the user cancelled the dialog.
// Do not use for the Open Multiple Files dialog. Use ShowAndGetResults instead.
ShowAndGetResult() (string, error)
// Sets the title of the dialog window.
SetTitle(title string) error
// Sets the "role" of the dialog. This is used to derive the dialog's GUID, which the
// OS will use to differentiate it from dialogs that are intended for other purposes.
// This means that, for example, a dialog with role "Import" will have a different
// previous location that it will open to than a dialog with role "Open". Can be any string.
SetRole(role string) error
// Sets the folder used as a default if there is not a recently used folder value available
SetDefaultFolder(defaultFolder string) error
// Sets the folder that the dialog always opens to.
// If this is set, it will override the "default folder" behaviour and the dialog will always open to this folder.
SetFolder(folder string) error
// Gets the selected file or folder path, as an absolute path eg. "C:\Folder\file.txt"
// Do not use for the Open Multiple Files dialog. Use GetResults instead.
GetResult() (string, error)
// Sets the file name, I.E. the contents of the file name text box.
// For Select Folder Dialog, sets folder name.
SetFileName(fileName string) error
// Release the resources allocated to this Dialog.
// Should be called when the dialog is finished with.
Release() error
}
type FileDialog interface {
Dialog
// Set the list of file filters that the user can select.
SetFileFilters(fileFilter []FileFilter) error
// Set the selected item from the list of file filters (set using SetFileFilters) by its index. Defaults to 0 (the first item in the list) if not called.
SetSelectedFileFilterIndex(index uint) error
// Sets the default extension applied when a user does not provide one as part of the file name.
// If the user selects a different file filter, the default extension will be automatically updated to match the new file filter.
// For Open / Open Multiple File Dialog, this only has an effect when the user specifies a file name with no extension and a file with the default extension exists.
// For Save File Dialog, this extension will be used whenever a user does not specify an extension.
SetDefaultExtension(defaultExtension string) error
}
type OpenFileDialog interface {
FileDialog
}
type OpenMultipleFilesDialog interface {
FileDialog
// Show the dialog to the user.
// Blocks until the user has closed the dialog and returns the selected files.
ShowAndGetResults() ([]string, error)
// Gets the selected file paths, as absolute paths eg. "C:\Folder\file.txt"
GetResults() ([]string, error)
}
type SelectFolderDialog interface {
Dialog
}
type SaveFileDialog interface { // TODO Properties
FileDialog
}

View File

@ -0,0 +1,28 @@
//go:build !windows
// +build !windows
package cfd
import "fmt"
var unsupportedError = fmt.Errorf("common file dialogs are only available on windows")
// TODO doc
func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) {
return nil, unsupportedError
}
// TODO doc
func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) {
return nil, unsupportedError
}
// TODO doc
func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) {
return nil, unsupportedError
}
// TODO doc
func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) {
return nil, unsupportedError
}

View File

@ -0,0 +1,79 @@
//go:build windows
// +build windows
package cfd
import "github.com/go-ole/go-ole"
func initialize() {
// Swallow error
_ = ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE)
}
// TODO doc
func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) {
initialize()
openDialog, err := newIFileOpenDialog()
if err != nil {
return nil, err
}
err = config.apply(openDialog)
if err != nil {
return nil, err
}
return openDialog, nil
}
// TODO doc
func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) {
initialize()
openDialog, err := newIFileOpenDialog()
if err != nil {
return nil, err
}
err = config.apply(openDialog)
if err != nil {
return nil, err
}
err = openDialog.setIsMultiselect(true)
if err != nil {
return nil, err
}
return openDialog, nil
}
// TODO doc
func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) {
initialize()
openDialog, err := newIFileOpenDialog()
if err != nil {
return nil, err
}
err = config.apply(openDialog)
if err != nil {
return nil, err
}
err = openDialog.setPickFolders(true)
if err != nil {
return nil, err
}
return openDialog, nil
}
// TODO doc
func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) {
initialize()
saveDialog, err := newIFileSaveDialog()
if err != nil {
return nil, err
}
err = config.apply(saveDialog)
if err != nil {
return nil, err
}
return saveDialog, nil
}

View File

@ -0,0 +1,120 @@
// Cross-platform.
package cfd
type FileFilter struct {
// The display name of the filter (That is shown to the user)
DisplayName string
// The filter pattern. Eg. "*.txt;*.png" to select all txt and png files, "*.*" to select any files, etc.
Pattern string
}
type DialogConfig struct {
// The title of the dialog
Title string
// The role of the dialog. This is used to derive the dialog's GUID, which the
// OS will use to differentiate it from dialogs that are intended for other purposes.
// This means that, for example, a dialog with role "Import" will have a different
// previous location that it will open to than a dialog with role "Open". Can be any string.
Role string
// The default folder - the folder that is used the first time the user opens it
// (after the first time their last used location is used).
DefaultFolder string
// The initial folder - the folder that the dialog always opens to if not empty.
// If this is not empty, it will override the "default folder" behaviour and
// the dialog will always open to this folder.
Folder string
// The file filters that restrict which types of files the dialog is able to choose.
// Ignored by Select Folder Dialog.
FileFilters []FileFilter
// Sets the initially selected file filter. This is an index of FileFilters.
// Ignored by Select Folder Dialog.
SelectedFileFilterIndex uint
// The initial name of the file (I.E. the text in the file name text box) when the user opens the dialog.
// For the Select Folder Dialog, this sets the initial folder name.
FileName string
// The default extension applied when a user does not provide one as part of the file name.
// If the user selects a different file filter, the default extension will be automatically updated to match the new file filter.
// For Open / Open Multiple File Dialog, this only has an effect when the user specifies a file name with no extension and a file with the default extension exists.
// For Save File Dialog, this extension will be used whenever a user does not specify an extension.
// Ignored by Select Folder Dialog.
DefaultExtension string
// ParentWindowHandle is the handle (HWND) to the parent window of the dialog.
// If left as 0 / nil, the dialog will have no parent window.
ParentWindowHandle uintptr
}
var defaultFilters = []FileFilter{
{
DisplayName: "All Files (*.*)",
Pattern: "*.*",
},
}
func (config *DialogConfig) apply(dialog Dialog) (err error) {
if config.Title != "" {
err = dialog.SetTitle(config.Title)
if err != nil {
return
}
}
if config.Role != "" {
err = dialog.SetRole(config.Role)
if err != nil {
return
}
}
if config.Folder != "" {
err = dialog.SetFolder(config.Folder)
if err != nil {
return
}
}
if config.DefaultFolder != "" {
err = dialog.SetDefaultFolder(config.DefaultFolder)
if err != nil {
return
}
}
if config.FileName != "" {
err = dialog.SetFileName(config.FileName)
if err != nil {
return
}
}
dialog.SetParentWindowHandle(config.ParentWindowHandle)
if dialog, ok := dialog.(FileDialog); ok {
var fileFilters []FileFilter
if config.FileFilters != nil && len(config.FileFilters) > 0 {
fileFilters = config.FileFilters
} else {
fileFilters = defaultFilters
}
err = dialog.SetFileFilters(fileFilters)
if err != nil {
return
}
if config.SelectedFileFilterIndex != 0 {
err = dialog.SetSelectedFileFilterIndex(config.SelectedFileFilterIndex)
if err != nil {
return
}
}
if config.DefaultExtension != "" {
err = dialog.SetDefaultExtension(config.DefaultExtension)
if err != nil {
return
}
}
}
return
}

View File

@ -0,0 +1,7 @@
package cfd
import "errors"
var (
ErrorCancelled = errors.New("cancelled by user")
)

View File

@ -0,0 +1,197 @@
//go:build windows
// +build windows
package cfd
import (
"github.com/go-ole/go-ole"
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/util"
"syscall"
"unsafe"
)
var (
fileOpenDialogCLSID = ole.NewGUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}")
fileOpenDialogIID = ole.NewGUID("{d57c7288-d4ad-4768-be02-9d969532d960}")
)
type iFileOpenDialog struct {
vtbl *iFileOpenDialogVtbl
parentWindowHandle uintptr
}
type iFileOpenDialogVtbl struct {
iFileDialogVtbl
GetResults uintptr // func (ppenum **IShellItemArray) HRESULT
GetSelectedItems uintptr
}
func newIFileOpenDialog() (*iFileOpenDialog, error) {
if unknown, err := ole.CreateInstance(fileOpenDialogCLSID, fileOpenDialogIID); err == nil {
return (*iFileOpenDialog)(unsafe.Pointer(unknown)), nil
} else {
return nil, err
}
}
func (fileOpenDialog *iFileOpenDialog) Show() error {
return fileOpenDialog.vtbl.show(unsafe.Pointer(fileOpenDialog), fileOpenDialog.parentWindowHandle)
}
func (fileOpenDialog *iFileOpenDialog) SetParentWindowHandle(hwnd uintptr) {
fileOpenDialog.parentWindowHandle = hwnd
}
func (fileOpenDialog *iFileOpenDialog) ShowAndGetResult() (string, error) {
isMultiselect, err := fileOpenDialog.isMultiselect()
if err != nil {
return "", err
}
if isMultiselect {
// We should panic as this error is caused by the developer using the library
panic("use ShowAndGetResults for open multiple files dialog")
}
if err := fileOpenDialog.Show(); err != nil {
return "", err
}
return fileOpenDialog.GetResult()
}
func (fileOpenDialog *iFileOpenDialog) ShowAndGetResults() ([]string, error) {
isMultiselect, err := fileOpenDialog.isMultiselect()
if err != nil {
return nil, err
}
if !isMultiselect {
// We should panic as this error is caused by the developer using the library
panic("use ShowAndGetResult for open single file dialog")
}
if err := fileOpenDialog.Show(); err != nil {
return nil, err
}
return fileOpenDialog.GetResults()
}
func (fileOpenDialog *iFileOpenDialog) SetTitle(title string) error {
return fileOpenDialog.vtbl.setTitle(unsafe.Pointer(fileOpenDialog), title)
}
func (fileOpenDialog *iFileOpenDialog) GetResult() (string, error) {
isMultiselect, err := fileOpenDialog.isMultiselect()
if err != nil {
return "", err
}
if isMultiselect {
// We should panic as this error is caused by the developer using the library
panic("use GetResults for open multiple files dialog")
}
return fileOpenDialog.vtbl.getResultString(unsafe.Pointer(fileOpenDialog))
}
func (fileOpenDialog *iFileOpenDialog) Release() error {
return fileOpenDialog.vtbl.release(unsafe.Pointer(fileOpenDialog))
}
func (fileOpenDialog *iFileOpenDialog) SetDefaultFolder(defaultFolderPath string) error {
return fileOpenDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath)
}
func (fileOpenDialog *iFileOpenDialog) SetFolder(defaultFolderPath string) error {
return fileOpenDialog.vtbl.setFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath)
}
func (fileOpenDialog *iFileOpenDialog) SetFileFilters(filter []FileFilter) error {
return fileOpenDialog.vtbl.setFileTypes(unsafe.Pointer(fileOpenDialog), filter)
}
func (fileOpenDialog *iFileOpenDialog) SetRole(role string) error {
return fileOpenDialog.vtbl.setClientGuid(unsafe.Pointer(fileOpenDialog), util.StringToUUID(role))
}
// This should only be callable when the user asks for a multi select because
// otherwise they will be given the Dialog interface which does not expose this function.
func (fileOpenDialog *iFileOpenDialog) GetResults() ([]string, error) {
isMultiselect, err := fileOpenDialog.isMultiselect()
if err != nil {
return nil, err
}
if !isMultiselect {
// We should panic as this error is caused by the developer using the library
panic("use GetResult for open single file dialog")
}
return fileOpenDialog.vtbl.getResultsStrings(unsafe.Pointer(fileOpenDialog))
}
func (fileOpenDialog *iFileOpenDialog) SetDefaultExtension(defaultExtension string) error {
return fileOpenDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileOpenDialog), defaultExtension)
}
func (fileOpenDialog *iFileOpenDialog) SetFileName(initialFileName string) error {
return fileOpenDialog.vtbl.setFileName(unsafe.Pointer(fileOpenDialog), initialFileName)
}
func (fileOpenDialog *iFileOpenDialog) SetSelectedFileFilterIndex(index uint) error {
return fileOpenDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileOpenDialog), index)
}
func (fileOpenDialog *iFileOpenDialog) setPickFolders(pickFolders bool) error {
const FosPickfolders = 0x20
if pickFolders {
return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosPickfolders)
} else {
return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosPickfolders)
}
}
const FosAllowMultiselect = 0x200
func (fileOpenDialog *iFileOpenDialog) isMultiselect() (bool, error) {
options, err := fileOpenDialog.vtbl.getOptions(unsafe.Pointer(fileOpenDialog))
if err != nil {
return false, err
}
return options&FosAllowMultiselect != 0, nil
}
func (fileOpenDialog *iFileOpenDialog) setIsMultiselect(isMultiselect bool) error {
if isMultiselect {
return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect)
} else {
return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect)
}
}
func (vtbl *iFileOpenDialogVtbl) getResults(objPtr unsafe.Pointer) (*iShellItemArray, error) {
var shellItemArray *iShellItemArray
ret, _, _ := syscall.Syscall(vtbl.GetResults,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(&shellItemArray)),
0)
return shellItemArray, hresultToError(ret)
}
func (vtbl *iFileOpenDialogVtbl) getResultsStrings(objPtr unsafe.Pointer) ([]string, error) {
shellItemArray, err := vtbl.getResults(objPtr)
if err != nil {
return nil, err
}
if shellItemArray == nil {
return nil, ErrorCancelled
}
defer shellItemArray.vtbl.release(unsafe.Pointer(shellItemArray))
count, err := shellItemArray.vtbl.getCount(unsafe.Pointer(shellItemArray))
if err != nil {
return nil, err
}
var results []string
for i := uintptr(0); i < count; i++ {
newItem, err := shellItemArray.vtbl.getItemAt(unsafe.Pointer(shellItemArray), i)
if err != nil {
return nil, err
}
results = append(results, newItem)
}
return results, nil
}

View File

@ -0,0 +1,93 @@
//go:build windows
// +build windows
package cfd
import (
"github.com/go-ole/go-ole"
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/util"
"unsafe"
)
var (
saveFileDialogCLSID = ole.NewGUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}")
saveFileDialogIID = ole.NewGUID("{84bccd23-5fde-4cdb-aea4-af64b83d78ab}")
)
type iFileSaveDialog struct {
vtbl *iFileSaveDialogVtbl
parentWindowHandle uintptr
}
type iFileSaveDialogVtbl struct {
iFileDialogVtbl
SetSaveAsItem uintptr
SetProperties uintptr
SetCollectedProperties uintptr
GetProperties uintptr
ApplyProperties uintptr
}
func newIFileSaveDialog() (*iFileSaveDialog, error) {
if unknown, err := ole.CreateInstance(saveFileDialogCLSID, saveFileDialogIID); err == nil {
return (*iFileSaveDialog)(unsafe.Pointer(unknown)), nil
} else {
return nil, err
}
}
func (fileSaveDialog *iFileSaveDialog) Show() error {
return fileSaveDialog.vtbl.show(unsafe.Pointer(fileSaveDialog), fileSaveDialog.parentWindowHandle)
}
func (fileSaveDialog *iFileSaveDialog) SetParentWindowHandle(hwnd uintptr) {
fileSaveDialog.parentWindowHandle = hwnd
}
func (fileSaveDialog *iFileSaveDialog) ShowAndGetResult() (string, error) {
if err := fileSaveDialog.Show(); err != nil {
return "", err
}
return fileSaveDialog.GetResult()
}
func (fileSaveDialog *iFileSaveDialog) SetTitle(title string) error {
return fileSaveDialog.vtbl.setTitle(unsafe.Pointer(fileSaveDialog), title)
}
func (fileSaveDialog *iFileSaveDialog) GetResult() (string, error) {
return fileSaveDialog.vtbl.getResultString(unsafe.Pointer(fileSaveDialog))
}
func (fileSaveDialog *iFileSaveDialog) Release() error {
return fileSaveDialog.vtbl.release(unsafe.Pointer(fileSaveDialog))
}
func (fileSaveDialog *iFileSaveDialog) SetDefaultFolder(defaultFolderPath string) error {
return fileSaveDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileSaveDialog), defaultFolderPath)
}
func (fileSaveDialog *iFileSaveDialog) SetFolder(defaultFolderPath string) error {
return fileSaveDialog.vtbl.setFolder(unsafe.Pointer(fileSaveDialog), defaultFolderPath)
}
func (fileSaveDialog *iFileSaveDialog) SetFileFilters(filter []FileFilter) error {
return fileSaveDialog.vtbl.setFileTypes(unsafe.Pointer(fileSaveDialog), filter)
}
func (fileSaveDialog *iFileSaveDialog) SetRole(role string) error {
return fileSaveDialog.vtbl.setClientGuid(unsafe.Pointer(fileSaveDialog), util.StringToUUID(role))
}
func (fileSaveDialog *iFileSaveDialog) SetDefaultExtension(defaultExtension string) error {
return fileSaveDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileSaveDialog), defaultExtension)
}
func (fileSaveDialog *iFileSaveDialog) SetFileName(initialFileName string) error {
return fileSaveDialog.vtbl.setFileName(unsafe.Pointer(fileSaveDialog), initialFileName)
}
func (fileSaveDialog *iFileSaveDialog) SetSelectedFileFilterIndex(index uint) error {
return fileSaveDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileSaveDialog), index)
}

View File

@ -0,0 +1,53 @@
//go:build windows
// +build windows
package cfd
import (
"github.com/go-ole/go-ole"
"syscall"
"unsafe"
)
var (
procSHCreateItemFromParsingName = syscall.NewLazyDLL("Shell32.dll").NewProc("SHCreateItemFromParsingName")
iidShellItem = ole.NewGUID("43826d1e-e718-42ee-bc55-a1e261c37bfe")
)
type iShellItem struct {
vtbl *iShellItemVtbl
}
type iShellItemVtbl struct {
iUnknownVtbl
BindToHandler uintptr
GetParent uintptr
GetDisplayName uintptr // func (sigdnName SIGDN, ppszName *LPWSTR) HRESULT
GetAttributes uintptr
Compare uintptr
}
func newIShellItem(path string) (*iShellItem, error) {
var shellItem *iShellItem
pathPtr := ole.SysAllocString(path)
ret, _, _ := procSHCreateItemFromParsingName.Call(
uintptr(unsafe.Pointer(pathPtr)),
0,
uintptr(unsafe.Pointer(iidShellItem)),
uintptr(unsafe.Pointer(&shellItem)))
return shellItem, hresultToError(ret)
}
func (vtbl *iShellItemVtbl) getDisplayName(objPtr unsafe.Pointer) (string, error) {
var ptr *uint16
ret, _, _ := syscall.Syscall(vtbl.GetDisplayName,
2,
uintptr(objPtr),
0x80058000, // SIGDN_FILESYSPATH
uintptr(unsafe.Pointer(&ptr)))
if err := hresultToError(ret); err != nil {
return "", err
}
defer ole.CoTaskMemFree(uintptr(unsafe.Pointer(ptr)))
return ole.LpOleStrToString(ptr), nil
}

View File

@ -0,0 +1,67 @@
//go:build windows
// +build windows
package cfd
import (
"github.com/go-ole/go-ole"
"syscall"
"unsafe"
)
const (
iidShellItemArrayGUID = "{b63ea76d-1f85-456f-a19c-48159efa858b}"
)
var (
iidShellItemArray *ole.GUID
)
func init() {
iidShellItemArray, _ = ole.IIDFromString(iidShellItemArrayGUID)
}
type iShellItemArray struct {
vtbl *iShellItemArrayVtbl
}
type iShellItemArrayVtbl struct {
iUnknownVtbl
BindToHandler uintptr
GetPropertyStore uintptr
GetPropertyDescriptionList uintptr
GetAttributes uintptr
GetCount uintptr // func (pdwNumItems *DWORD) HRESULT
GetItemAt uintptr // func (dwIndex DWORD, ppsi **IShellItem) HRESULT
EnumItems uintptr
}
func (vtbl *iShellItemArrayVtbl) getCount(objPtr unsafe.Pointer) (uintptr, error) {
var count uintptr
ret, _, _ := syscall.Syscall(vtbl.GetCount,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(&count)),
0)
if err := hresultToError(ret); err != nil {
return 0, err
}
return count, nil
}
func (vtbl *iShellItemArrayVtbl) getItemAt(objPtr unsafe.Pointer, index uintptr) (string, error) {
var shellItem *iShellItem
ret, _, _ := syscall.Syscall(vtbl.GetItemAt,
2,
uintptr(objPtr),
index,
uintptr(unsafe.Pointer(&shellItem)))
if err := hresultToError(ret); err != nil {
return "", err
}
if shellItem == nil {
return "", ErrorCancelled
}
defer shellItem.vtbl.release(unsafe.Pointer(shellItem))
return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem))
}

View File

@ -0,0 +1,48 @@
//go:build windows
// +build windows
package cfd
type comDlgFilterSpec struct {
pszName *int16
pszSpec *int16
}
type iUnknownVtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
}
type iModalWindowVtbl struct {
iUnknownVtbl
Show uintptr // func (hwndOwner HWND) HRESULT
}
type iFileDialogVtbl struct {
iModalWindowVtbl
SetFileTypes uintptr // func (cFileTypes UINT, rgFilterSpec *COMDLG_FILTERSPEC) HRESULT
SetFileTypeIndex uintptr // func(iFileType UINT) HRESULT
GetFileTypeIndex uintptr
Advise uintptr
Unadvise uintptr
SetOptions uintptr // func (fos FILEOPENDIALOGOPTIONS) HRESULT
GetOptions uintptr // func (pfos *FILEOPENDIALOGOPTIONS) HRESULT
SetDefaultFolder uintptr // func (psi *IShellItem) HRESULT
SetFolder uintptr // func (psi *IShellItem) HRESULT
GetFolder uintptr
GetCurrentSelection uintptr
SetFileName uintptr // func (pszName LPCWSTR) HRESULT
GetFileName uintptr
SetTitle uintptr // func(pszTitle LPCWSTR) HRESULT
SetOkButtonLabel uintptr
SetFileNameLabel uintptr
GetResult uintptr // func (ppsi **IShellItem) HRESULT
AddPlace uintptr
SetDefaultExtension uintptr // func (pszDefaultExtension LPCWSTR) HRESULT
// This can only be used from a callback.
Close uintptr
SetClientGuid uintptr // func (guid REFGUID) HRESULT
ClearClientData uintptr
SetFilter uintptr
}

View File

@ -0,0 +1,227 @@
//go:build windows
// +build windows
package cfd
import (
"fmt"
"github.com/go-ole/go-ole"
"strings"
"syscall"
"unsafe"
)
func hresultToError(hr uintptr) error {
if hr < 0 {
return ole.NewError(hr)
}
return nil
}
func (vtbl *iUnknownVtbl) release(objPtr unsafe.Pointer) error {
ret, _, _ := syscall.Syscall(vtbl.Release,
0,
uintptr(objPtr),
0,
0)
return hresultToError(ret)
}
func (vtbl *iModalWindowVtbl) show(objPtr unsafe.Pointer, hwnd uintptr) error {
ret, _, _ := syscall.Syscall(vtbl.Show,
1,
uintptr(objPtr),
hwnd,
0)
return hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) setFileTypes(objPtr unsafe.Pointer, filters []FileFilter) error {
cFileTypes := len(filters)
if cFileTypes < 0 {
return fmt.Errorf("must specify at least one filter")
}
comDlgFilterSpecs := make([]comDlgFilterSpec, cFileTypes)
for i := 0; i < cFileTypes; i++ {
filter := &filters[i]
comDlgFilterSpecs[i] = comDlgFilterSpec{
pszName: ole.SysAllocString(filter.DisplayName),
pszSpec: ole.SysAllocString(filter.Pattern),
}
}
ret, _, _ := syscall.Syscall(vtbl.SetFileTypes,
2,
uintptr(objPtr),
uintptr(cFileTypes),
uintptr(unsafe.Pointer(&comDlgFilterSpecs[0])))
return hresultToError(ret)
}
// Options are:
// FOS_OVERWRITEPROMPT = 0x2,
// FOS_STRICTFILETYPES = 0x4,
// FOS_NOCHANGEDIR = 0x8,
// FOS_PICKFOLDERS = 0x20,
// FOS_FORCEFILESYSTEM = 0x40,
// FOS_ALLNONSTORAGEITEMS = 0x80,
// FOS_NOVALIDATE = 0x100,
// FOS_ALLOWMULTISELECT = 0x200,
// FOS_PATHMUSTEXIST = 0x800,
// FOS_FILEMUSTEXIST = 0x1000,
// FOS_CREATEPROMPT = 0x2000,
// FOS_SHAREAWARE = 0x4000,
// FOS_NOREADONLYRETURN = 0x8000,
// FOS_NOTESTFILECREATE = 0x10000,
// FOS_HIDEMRUPLACES = 0x20000,
// FOS_HIDEPINNEDPLACES = 0x40000,
// FOS_NODEREFERENCELINKS = 0x100000,
// FOS_OKBUTTONNEEDSINTERACTION = 0x200000,
// FOS_DONTADDTORECENT = 0x2000000,
// FOS_FORCESHOWHIDDEN = 0x10000000,
// FOS_DEFAULTNOMINIMODE = 0x20000000,
// FOS_FORCEPREVIEWPANEON = 0x40000000,
// FOS_SUPPORTSTREAMABLEITEMS = 0x80000000
func (vtbl *iFileDialogVtbl) setOptions(objPtr unsafe.Pointer, options uint32) error {
ret, _, _ := syscall.Syscall(vtbl.SetOptions,
1,
uintptr(objPtr),
uintptr(options),
0)
return hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) getOptions(objPtr unsafe.Pointer) (uint32, error) {
var options uint32
ret, _, _ := syscall.Syscall(vtbl.GetOptions,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(&options)),
0)
return options, hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) addOption(objPtr unsafe.Pointer, option uint32) error {
if options, err := vtbl.getOptions(objPtr); err == nil {
return vtbl.setOptions(objPtr, options|option)
} else {
return err
}
}
func (vtbl *iFileDialogVtbl) removeOption(objPtr unsafe.Pointer, option uint32) error {
if options, err := vtbl.getOptions(objPtr); err == nil {
return vtbl.setOptions(objPtr, options&^option)
} else {
return err
}
}
func (vtbl *iFileDialogVtbl) setDefaultFolder(objPtr unsafe.Pointer, path string) error {
shellItem, err := newIShellItem(path)
if err != nil {
return err
}
defer shellItem.vtbl.release(unsafe.Pointer(shellItem))
ret, _, _ := syscall.Syscall(vtbl.SetDefaultFolder,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(shellItem)),
0)
return hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) setFolder(objPtr unsafe.Pointer, path string) error {
shellItem, err := newIShellItem(path)
if err != nil {
return err
}
defer shellItem.vtbl.release(unsafe.Pointer(shellItem))
ret, _, _ := syscall.Syscall(vtbl.SetFolder,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(shellItem)),
0)
return hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) setTitle(objPtr unsafe.Pointer, title string) error {
titlePtr := ole.SysAllocString(title)
ret, _, _ := syscall.Syscall(vtbl.SetTitle,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(titlePtr)),
0)
return hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) close(objPtr unsafe.Pointer) error {
ret, _, _ := syscall.Syscall(vtbl.Close,
1,
uintptr(objPtr),
0,
0)
return hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) getResult(objPtr unsafe.Pointer) (*iShellItem, error) {
var shellItem *iShellItem
ret, _, _ := syscall.Syscall(vtbl.GetResult,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(&shellItem)),
0)
return shellItem, hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) getResultString(objPtr unsafe.Pointer) (string, error) {
shellItem, err := vtbl.getResult(objPtr)
if err != nil {
return "", err
}
if shellItem == nil {
return "", ErrorCancelled
}
defer shellItem.vtbl.release(unsafe.Pointer(shellItem))
return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem))
}
func (vtbl *iFileDialogVtbl) setClientGuid(objPtr unsafe.Pointer, guid *ole.GUID) error {
ret, _, _ := syscall.Syscall(vtbl.SetClientGuid,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(guid)),
0)
return hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) setDefaultExtension(objPtr unsafe.Pointer, defaultExtension string) error {
if defaultExtension[0] == '.' {
defaultExtension = strings.TrimPrefix(defaultExtension, ".")
}
defaultExtensionPtr := ole.SysAllocString(defaultExtension)
ret, _, _ := syscall.Syscall(vtbl.SetDefaultExtension,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(defaultExtensionPtr)),
0)
return hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) setFileName(objPtr unsafe.Pointer, fileName string) error {
fileNamePtr := ole.SysAllocString(fileName)
ret, _, _ := syscall.Syscall(vtbl.SetFileName,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(fileNamePtr)),
0)
return hresultToError(ret)
}
func (vtbl *iFileDialogVtbl) setSelectedFileFilterIndex(objPtr unsafe.Pointer, index uint) error {
ret, _, _ := syscall.Syscall(vtbl.SetFileTypeIndex,
1,
uintptr(objPtr),
uintptr(index+1), // SetFileTypeIndex counts from 1
0)
return hresultToError(ret)
}

View File

@ -0,0 +1,45 @@
package cfdutil
import (
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/cfd"
)
// TODO doc
func ShowOpenFileDialog(config cfd.DialogConfig) (string, error) {
dialog, err := cfd.NewOpenFileDialog(config)
if err != nil {
return "", err
}
defer dialog.Release()
return dialog.ShowAndGetResult()
}
// TODO doc
func ShowOpenMultipleFilesDialog(config cfd.DialogConfig) ([]string, error) {
dialog, err := cfd.NewOpenMultipleFilesDialog(config)
if err != nil {
return nil, err
}
defer dialog.Release()
return dialog.ShowAndGetResults()
}
// TODO doc
func ShowPickFolderDialog(config cfd.DialogConfig) (string, error) {
dialog, err := cfd.NewSelectFolderDialog(config)
if err != nil {
return "", err
}
defer dialog.Release()
return dialog.ShowAndGetResult()
}
// TODO doc
func ShowSaveFileDialog(config cfd.DialogConfig) (string, error) {
dialog, err := cfd.NewSaveFileDialog(config)
if err != nil {
return "", err
}
defer dialog.Release()
return dialog.ShowAndGetResult()
}

View File

@ -0,0 +1,10 @@
package util
import (
"github.com/go-ole/go-ole"
"github.com/google/uuid"
)
func StringToUUID(str string) *ole.GUID {
return ole.NewGUID(uuid.NewSHA1(uuid.Nil, []byte(str)).String())
}

View File

@ -0,0 +1,14 @@
package util
import (
"github.com/go-ole/go-ole"
"testing"
)
func TestStringToUUID(t *testing.T) {
generated := *StringToUUID("TestTestTest")
expected := *ole.NewGUID("7933985F-2C87-5A5B-A26E-5D0326829AC2")
if generated != expected {
t.Errorf("not equal. expected %s, found %s", expected.String(), generated.String())
}
}

View File

@ -28,7 +28,6 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -91,7 +90,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -132,7 +131,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -172,7 +171,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -213,7 +212,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -254,7 +253,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -297,7 +296,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -341,7 +340,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -443,7 +442,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -484,7 +483,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -525,7 +524,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
@ -569,7 +568,7 @@ require (
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/go-common-file-dialog v1.0.3 // indirect
github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2 v0.0.0-20211007092718-65d2f028ef2d // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect