mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
[windows] Support Dialog API
This commit is contained in:
parent
e90f5361be
commit
102a8cc5a6
8 changed files with 274 additions and 18 deletions
|
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/fatih/structtag v1.2.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/harry1453/go-common-file-dialog v1.0.0
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/jackmordaunt/icns v1.0.0
|
||||
github.com/leaanthony/clir v1.0.4
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
|||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
|
|
@ -29,8 +31,12 @@ github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgj
|
|||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/harry1453/go-common-file-dialog v1.0.0 h1:fzBAGhRTqWQyJw5xkm0PSsA+d3CBYBrfh+Nayb6U0nM=
|
||||
github.com/harry1453/go-common-file-dialog v1.0.0/go.mod h1:3zwmbo7fy+uYGyaec74mu+Z9DPg0aEt10fSjjPwfyiY=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ type Application struct {
|
|||
|
||||
// Logger
|
||||
logger logger.CustomLogger
|
||||
|
||||
// Window handle (used by windows)
|
||||
hwnd unsafe.Pointer
|
||||
}
|
||||
|
||||
func (a *Application) saveMemoryReference(mem unsafe.Pointer) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ package ffenestri
|
|||
import "C"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
|
||||
|
|
@ -124,15 +126,45 @@ func (c *Client) WindowSetColour(colour int) {
|
|||
|
||||
// OpenDialog will open a dialog with the given title and filter
|
||||
func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
|
||||
filters := []string{}
|
||||
if runtime.GOOS == "darwin" {
|
||||
for _, filter := range dialogOptions.Filters {
|
||||
filters = append(filters, strings.Split(filter.Pattern, ",")...)
|
||||
}
|
||||
}
|
||||
C.OpenDialog(c.app.app,
|
||||
c.app.string2CString(callbackID),
|
||||
c.app.string2CString(dialogOptions.Title),
|
||||
c.app.string2CString(dialogOptions.Filters),
|
||||
c.app.string2CString(strings.Join(filters, ";")),
|
||||
c.app.string2CString(dialogOptions.DefaultFilename),
|
||||
c.app.string2CString(dialogOptions.DefaultDirectory),
|
||||
c.app.bool2Cint(dialogOptions.AllowFiles),
|
||||
c.app.bool2Cint(dialogOptions.AllowDirectories),
|
||||
c.app.bool2Cint(dialogOptions.AllowMultiple),
|
||||
c.app.bool2Cint(false),
|
||||
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
|
||||
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
|
||||
c.app.bool2Cint(dialogOptions.ResolvesAliases),
|
||||
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
|
||||
)
|
||||
}
|
||||
|
||||
// OpenMultipleDialog will open a dialog with the given title and filter
|
||||
func (c *Client) OpenMultipleDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
|
||||
filters := []string{}
|
||||
if runtime.GOOS == "darwin" {
|
||||
for _, filter := range dialogOptions.Filters {
|
||||
filters = append(filters, strings.Split(filter.Pattern, ",")...)
|
||||
}
|
||||
}
|
||||
C.OpenDialog(c.app.app,
|
||||
c.app.string2CString(callbackID),
|
||||
c.app.string2CString(dialogOptions.Title),
|
||||
c.app.string2CString(strings.Join(filters, ";")),
|
||||
c.app.string2CString(dialogOptions.DefaultFilename),
|
||||
c.app.string2CString(dialogOptions.DefaultDirectory),
|
||||
c.app.bool2Cint(dialogOptions.AllowFiles),
|
||||
c.app.bool2Cint(dialogOptions.AllowDirectories),
|
||||
c.app.bool2Cint(true),
|
||||
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
|
||||
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
|
||||
c.app.bool2Cint(dialogOptions.ResolvesAliases),
|
||||
|
|
@ -142,10 +174,16 @@ func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string)
|
|||
|
||||
// SaveDialog will open a dialog with the given title and filter
|
||||
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
|
||||
filters := []string{}
|
||||
if runtime.GOOS == "darwin" {
|
||||
for _, filter := range dialogOptions.Filters {
|
||||
filters = append(filters, strings.Split(filter.Pattern, ",")...)
|
||||
}
|
||||
}
|
||||
C.SaveDialog(c.app.app,
|
||||
c.app.string2CString(callbackID),
|
||||
c.app.string2CString(dialogOptions.Title),
|
||||
c.app.string2CString(dialogOptions.Filters),
|
||||
c.app.string2CString(strings.Join(filters, ";")),
|
||||
c.app.string2CString(dialogOptions.DefaultFilename),
|
||||
c.app.string2CString(dialogOptions.DefaultDirectory),
|
||||
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// +build !windows
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
|
|
@ -10,9 +12,11 @@ import (
|
|||
|
||||
// Dialog defines all Dialog related operations
|
||||
type Dialog interface {
|
||||
Open(dialogOptions *dialogoptions.OpenDialog) []string
|
||||
Save(dialogOptions *dialogoptions.SaveDialog) string
|
||||
Message(dialogOptions *dialogoptions.MessageDialog) string
|
||||
OpenFile(dialogOptions *dialogoptions.OpenDialog) (string, error)
|
||||
OpenMultipleFiles(dialogOptions *dialogoptions.OpenDialog) ([]string, error)
|
||||
OpenDirectory(dialogOptions *dialogoptions.OpenDialog) (string, error)
|
||||
SaveFile(dialogOptions *dialogoptions.SaveDialog) (string, error)
|
||||
Message(dialogOptions *dialogoptions.MessageDialog) (string, error)
|
||||
}
|
||||
|
||||
// dialog exposes the Dialog interface
|
||||
|
|
@ -44,8 +48,32 @@ func (r *dialog) processTitleAndFilter(params ...string) (string, string) {
|
|||
return title, filter
|
||||
}
|
||||
|
||||
func OpenDirectory(dialogOptions *dialogoptions.OpenDialog) (string, error) {
|
||||
|
||||
// Create unique dialog callback
|
||||
uniqueCallback := crypto.RandomID()
|
||||
|
||||
// Subscribe to the respose channel
|
||||
responseTopic := "dialog:opendirectoryselected:" + uniqueCallback
|
||||
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
|
||||
if err != nil {
|
||||
return nil, fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
|
||||
}
|
||||
|
||||
message := "dialog:selectdirectory:open:" + uniqueCallback
|
||||
r.bus.Publish(message, dialogOptions)
|
||||
|
||||
// Wait for result
|
||||
var result *servicebus.Message = <-dialogResponseChannel
|
||||
|
||||
// Delete subscription to response topic
|
||||
r.bus.UnSubscribe(responseTopic)
|
||||
|
||||
return result.Data().(string), nil
|
||||
}
|
||||
|
||||
// Open prompts the user to select a file
|
||||
func (r *dialog) Open(dialogOptions *dialogoptions.OpenDialog) []string {
|
||||
func (r *dialog) OpenFile(dialogOptions *dialogoptions.OpenDialog) (string, error) {
|
||||
|
||||
// Create unique dialog callback
|
||||
uniqueCallback := crypto.RandomID()
|
||||
|
|
@ -54,7 +82,7 @@ func (r *dialog) Open(dialogOptions *dialogoptions.OpenDialog) []string {
|
|||
responseTopic := "dialog:openselected:" + uniqueCallback
|
||||
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
|
||||
return nil, fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
|
||||
}
|
||||
|
||||
message := "dialog:select:open:" + uniqueCallback
|
||||
|
|
@ -66,11 +94,36 @@ func (r *dialog) Open(dialogOptions *dialogoptions.OpenDialog) []string {
|
|||
// Delete subscription to response topic
|
||||
r.bus.UnSubscribe(responseTopic)
|
||||
|
||||
return result.Data().([]string)
|
||||
return result.Data().(string), nil
|
||||
}
|
||||
|
||||
// OpenMultiple prompts the user to select a file
|
||||
func (r *dialog) OpenMultipleFiles(dialogOptions *dialogoptions.OpenDialog) ([]string, error) {
|
||||
|
||||
// Create unique dialog callback
|
||||
uniqueCallback := crypto.RandomID()
|
||||
|
||||
// Subscribe to the respose channel
|
||||
responseTopic := "dialog:openmultipleselected:" + uniqueCallback
|
||||
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
|
||||
if err != nil {
|
||||
return nil, fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
|
||||
}
|
||||
|
||||
message := "dialog:select:openmultiple:" + uniqueCallback
|
||||
r.bus.Publish(message, dialogOptions)
|
||||
|
||||
// Wait for result
|
||||
var result *servicebus.Message = <-dialogResponseChannel
|
||||
|
||||
// Delete subscription to response topic
|
||||
r.bus.UnSubscribe(responseTopic)
|
||||
|
||||
return result.Data().(string), nil
|
||||
}
|
||||
|
||||
// Save prompts the user to select a file
|
||||
func (r *dialog) Save(dialogOptions *dialogoptions.SaveDialog) string {
|
||||
func (r *dialog) SaveFile(dialogOptions *dialogoptions.SaveDialog) (string, error) {
|
||||
|
||||
// Create unique dialog callback
|
||||
uniqueCallback := crypto.RandomID()
|
||||
|
|
@ -79,7 +132,7 @@ func (r *dialog) Save(dialogOptions *dialogoptions.SaveDialog) string {
|
|||
responseTopic := "dialog:saveselected:" + uniqueCallback
|
||||
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
|
||||
return nil, fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
|
||||
}
|
||||
|
||||
message := "dialog:select:save:" + uniqueCallback
|
||||
|
|
@ -91,7 +144,7 @@ func (r *dialog) Save(dialogOptions *dialogoptions.SaveDialog) string {
|
|||
// Delete subscription to response topic
|
||||
r.bus.UnSubscribe(responseTopic)
|
||||
|
||||
return result.Data().(string)
|
||||
return result.Data().(string), nil
|
||||
}
|
||||
|
||||
// Message show a message to the user
|
||||
|
|
|
|||
141
v2/internal/runtime/dialog_windows.go
Normal file
141
v2/internal/runtime/dialog_windows.go
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// +build windows
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
"syscall"
|
||||
|
||||
"github.com/harry1453/go-common-file-dialog/cfd"
|
||||
"github.com/harry1453/go-common-file-dialog/cfdutil"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
dialogoptions "github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
)
|
||||
|
||||
// Dialog defines all Dialog related operations
|
||||
type Dialog interface {
|
||||
OpenFile(dialogOptions *dialogoptions.OpenDialog) (string, error)
|
||||
OpenMultipleFiles(dialogOptions *dialogoptions.OpenDialog) ([]string, error)
|
||||
OpenDirectory(dialogOptions *dialogoptions.OpenDialog) (string, error)
|
||||
Save(dialogOptions *dialogoptions.SaveDialog) (string, error)
|
||||
Message(dialogOptions *dialogoptions.MessageDialog) (string, error)
|
||||
}
|
||||
|
||||
// dialog exposes the Dialog interface
|
||||
type dialog struct {
|
||||
bus *servicebus.ServiceBus
|
||||
}
|
||||
|
||||
// newDialogs creates a new Dialogs struct
|
||||
func newDialog(bus *servicebus.ServiceBus) Dialog {
|
||||
return &dialog{
|
||||
bus: bus,
|
||||
}
|
||||
}
|
||||
|
||||
// processTitleAndFilter return the title and filter from the given params.
|
||||
// title is the first string, filter is the second
|
||||
func (r *dialog) processTitleAndFilter(params ...string) (string, string) {
|
||||
|
||||
var title, filter string
|
||||
|
||||
if len(params) > 0 {
|
||||
title = params[0]
|
||||
}
|
||||
|
||||
if len(params) > 1 {
|
||||
filter = params[1]
|
||||
}
|
||||
|
||||
return title, filter
|
||||
}
|
||||
|
||||
func convertFilters(filters []dialogoptions.FileFilter) []cfd.FileFilter {
|
||||
var result []cfd.FileFilter
|
||||
for _, filter := range filters {
|
||||
result = append(result, cfd.FileFilter(filter))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func pickMultipleFiles(options *dialogoptions.OpenDialog) ([]string, error) {
|
||||
|
||||
results, err := cfdutil.ShowOpenMultipleFilesDialog(cfd.DialogConfig{
|
||||
Title: options.Title,
|
||||
Role: "OpenMultipleFiles",
|
||||
FileFilters: convertFilters(options.Filters),
|
||||
FileName: options.DefaultFilename,
|
||||
Folder: options.DefaultDirectory,
|
||||
})
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (r *dialog) OpenMultipleFiles(options *dialogoptions.OpenDialog) ([]string, error) {
|
||||
return pickMultipleFiles(options)
|
||||
}
|
||||
|
||||
func (r *dialog) OpenDirectory(options *dialogoptions.OpenDialog) (string, error) {
|
||||
return cfdutil.ShowPickFolderDialog(cfd.DialogConfig{
|
||||
Title: options.Title,
|
||||
Role: "PickFolder",
|
||||
Folder: options.DefaultDirectory,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *dialog) OpenFile(options *dialogoptions.OpenDialog) (string, error) {
|
||||
result, err := cfdutil.ShowOpenFileDialog(cfd.DialogConfig{
|
||||
Folder: options.DefaultDirectory,
|
||||
FileFilters: convertFilters(options.Filters),
|
||||
FileName: options.DefaultFilename,
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Save prompts the user to select a file
|
||||
func (r *dialog) Save(options *dialogoptions.SaveDialog) (string, error) {
|
||||
|
||||
result, err := cfdutil.ShowSaveFileDialog(cfd.DialogConfig{
|
||||
Title: options.Title,
|
||||
Role: "SaveFile",
|
||||
FileName: options.DefaultFilename,
|
||||
FileFilters: convertFilters(options.Filters),
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Message show a message to the user
|
||||
func (r *dialog) Message(options *dialogoptions.MessageDialog) (string, error) {
|
||||
|
||||
// TODO: error handling
|
||||
title, err := syscall.UTF16PtrFromString(options.Title)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
message, err := syscall.UTF16PtrFromString(options.Message)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var flags uint32
|
||||
switch options.Type {
|
||||
case dialogoptions.InfoDialog:
|
||||
flags = windows.MB_OK | windows.MB_ICONINFORMATION
|
||||
case dialogoptions.ErrorDialog:
|
||||
flags = windows.MB_ICONERROR | windows.MB_OK
|
||||
case dialogoptions.QuestionDialog:
|
||||
flags = windows.MB_YESNO
|
||||
case dialogoptions.WarningDialog:
|
||||
flags = windows.MB_OK | windows.MB_ICONWARNING
|
||||
}
|
||||
|
||||
result, _ := windows.MessageBox(0, message, title, flags|windows.MB_SYSTEMMODAL)
|
||||
if options.Type == dialogoptions.QuestionDialog {
|
||||
if result == 6 { // IDYES
|
||||
return "Yes", nil
|
||||
}
|
||||
if result == 7 { // IDNO
|
||||
return "No", nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
|
||||
}
|
||||
|
|
@ -140,7 +140,10 @@ func (c *Call) processSystemCall(payload *message.CallMessage, clientID string)
|
|||
if err != nil {
|
||||
c.logger.Error("Error decoding: %s", err)
|
||||
}
|
||||
result := c.runtime.Dialog.Open(dialogOptions)
|
||||
result, err := c.runtime.Dialog.OpenFile(dialogOptions)
|
||||
if err != nil {
|
||||
c.logger.Error("Error: %s", err)
|
||||
}
|
||||
c.sendResult(result, payload, clientID)
|
||||
case "Dialog.Save":
|
||||
dialogOptions := new(dialog.SaveDialog)
|
||||
|
|
@ -148,7 +151,10 @@ func (c *Call) processSystemCall(payload *message.CallMessage, clientID string)
|
|||
if err != nil {
|
||||
c.logger.Error("Error decoding: %s", err)
|
||||
}
|
||||
result := c.runtime.Dialog.Save(dialogOptions)
|
||||
result, err := c.runtime.Dialog.Save(dialogOptions)
|
||||
if err != nil {
|
||||
c.logger.Error("Error: %s", err)
|
||||
}
|
||||
c.sendResult(result, payload, clientID)
|
||||
case "Dialog.Message":
|
||||
dialogOptions := new(dialog.MessageDialog)
|
||||
|
|
@ -156,7 +162,10 @@ func (c *Call) processSystemCall(payload *message.CallMessage, clientID string)
|
|||
if err != nil {
|
||||
c.logger.Error("Error decoding: %s", err)
|
||||
}
|
||||
result := c.runtime.Dialog.Message(dialogOptions)
|
||||
result, err := c.runtime.Dialog.Message(dialogOptions)
|
||||
if err != nil {
|
||||
c.logger.Error("Error: %s", err)
|
||||
}
|
||||
c.sendResult(result, payload, clientID)
|
||||
default:
|
||||
c.logger.Error("Unknown system call: %+v", callName)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
package dialog
|
||||
|
||||
// FileFilter defines a filter for dialog boxes
|
||||
type FileFilter struct {
|
||||
DisplayName string // Filter information EG: "Image Files (*.jpg, *.png)"
|
||||
Pattern string // semi-colon separated list of extensions, EG: "*.jpg;*.png"
|
||||
}
|
||||
|
||||
// OpenDialog contains the options for the OpenDialog runtime method
|
||||
type OpenDialog struct {
|
||||
DefaultDirectory string
|
||||
DefaultFilename string
|
||||
Title string
|
||||
Filters string
|
||||
Filters []FileFilter
|
||||
AllowFiles bool
|
||||
AllowDirectories bool
|
||||
AllowMultiple bool
|
||||
ShowHiddenFiles bool
|
||||
CanCreateDirectories bool
|
||||
ResolvesAliases bool
|
||||
|
|
@ -20,7 +25,7 @@ type SaveDialog struct {
|
|||
DefaultDirectory string
|
||||
DefaultFilename string
|
||||
Title string
|
||||
Filters string
|
||||
Filters []FileFilter
|
||||
ShowHiddenFiles bool
|
||||
CanCreateDirectories bool
|
||||
TreatPackagesAsDirectories bool
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue