-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.go
249 lines (203 loc) · 6.11 KB
/
app.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package main
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
// App struct
type App struct {
ctx context.Context
defaultFilename string
filepath string
fileFilters []runtime.FileFilter
status docStatus
}
type docStatus struct {
saved bool
content string
}
// NewApp creates a new App application struct
func NewApp() *App {
return &App{defaultFilename: "*.txt"}
}
// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
runtime.EventsOn(a.ctx, "onLanguagesLoaded", a.onLanguagesLoaded)
runtime.EventsOn(a.ctx, "onSaveStatusUpdated", a.onSaveStatusUpdated)
runtime.OnFileDrop(a.ctx, a.onFileDropped)
}
// beforeClose is called when the app is about to quit. It returns a boolean
// to determine whether the app should be prevented from closing.
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
// Close as normal if the document is already saved or empty
if a.status.saved || (a.filepath == "" && a.status.content == "") {
return false
}
// Display a dialog to alert the user the current document is unsaved
dialog, err := runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Type: runtime.QuestionDialog,
Title: "Unsaved Changes",
Message: "Quit without saving?",
DefaultButton: "No",
})
if err != nil {
return false
}
return dialog != "Yes"
}
// onLanguagesLoaded builds the file filters used in the file dialogs
func (a *App) onLanguagesLoaded(optionalData ...interface{}) {
a.fileFilters = []runtime.FileFilter{
{DisplayName: "All files", Pattern: "*.*"},
}
// Return early if the arguments are empty
if len(optionalData) == 0 {
return
}
// Assert the data to a string
jsonStr, ok := optionalData[0].(string)
if !ok {
log.Println("Error: Languages value cannot be asserted to string.")
return
}
languages := ParseLanguages(jsonStr)
// Add the languages to the file filters
for _, language := range languages {
fileFilter := runtime.FileFilter{
DisplayName: language.Aliases[0],
Pattern: "*" + strings.Join(language.Extensions, ";*"),
}
a.fileFilters = append(a.fileFilters, fileFilter)
}
}
// onSaveStatusUpdated updates the app's status with the received data
func (a *App) onSaveStatusUpdated(optionalData ...interface{}) {
if len(optionalData) == 0 {
return
}
saved, ok := optionalData[0].(bool)
if !ok {
log.Println("Error: Status value cannot be asserted to boolean.")
return
}
content, ok := optionalData[1].(string)
if !ok {
log.Println("Error: Content value cannot be asserted to string.")
return
}
a.status = docStatus{saved, content}
}
func (a *App) onFileDropped(x, y int, paths []string) {
fmt.Printf("Dropped at %v,%v paths: %v\n", x, y, paths)
// Check if the current file has unsaved content
if !a.status.saved && (a.filepath != "" || a.status.content != "") {
// Display a dialog to alert the user the current document is unsaved
dialog, err := runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Type: runtime.QuestionDialog,
Title: "Unsaved Changes",
Message: "Open new file without saving?",
DefaultButton: "No",
})
if err != nil || dialog != "Yes" {
return
}
}
a.readFile(paths[0])
}
// UpdateDefaultName sets the default file name to the provided string
func (a *App) UpdateDefaultName(filename string) {
a.defaultFilename = filename
}
// NewFile clears the file path, sets the window title, and emits an 'onNewFile' event
func (a *App) NewFile() {
a.filepath = ""
a.defaultFilename = "*.txt"
a.status = docStatus{}
runtime.WindowSetTitle(a.ctx, "gotepad")
runtime.EventsEmit(a.ctx, "onNewFile")
}
// OpenFile opens a file dialog and reads the selected file
func (a *App) OpenFile() {
options := runtime.OpenDialogOptions{
Filters: a.fileFilters,
}
// Open the dialog
filepathStr, err := runtime.OpenFileDialog(a.ctx, options)
if err != nil {
log.Printf("Error retrieving file path. %v", err)
return
} else if filepathStr == "" {
return // Return early if the user cancelled
}
log.Printf("filepath: %v\n", filepathStr)
a.readFile(filepathStr)
}
// readFile is a helper to read the passed in file,
// update the app's file fields
// and emit the "onFileRead" event.
func (a *App) readFile(filepathStr string) {
// Update the app's file fields
a.filepath = filepathStr
a.defaultFilename = "*" + filepath.Ext(filepathStr)
runtime.WindowSetTitle(a.ctx, "gotepad - "+filepath.Base(filepathStr))
// Read the file
data, err := os.ReadFile(filepathStr)
if err != nil {
log.Printf("Error reading file. %v\n", err)
return
}
// Update the status
a.status = docStatus{true, string(data)}
response := Response{
Status: "success",
Message: filepathStr,
Data: string(data),
}
// Emit an event with the read file text attached
runtime.EventsEmit(a.ctx, "onFileRead", response)
}
// SaveAs opens a file dialog and saves the contents at the selected path
func (a *App) SaveAs(contents string) {
options := runtime.SaveDialogOptions{
DefaultFilename: a.defaultFilename,
Filters: a.fileFilters,
}
// Open the dialog
filepathStr, err := runtime.SaveFileDialog(a.ctx, options)
if err != nil {
log.Printf("Error retrieving file path. %v\n", err)
return
} else if filepathStr == "" {
return // Return early if the user cancelled
}
a.filepath = filepathStr
a.Save(contents)
}
// Save writes the contents to the file at the app's filepath
func (a *App) Save(contents string) {
// Check for a valid filepath
if a.filepath == "" {
a.SaveAs(contents)
return
}
log.Printf("Save path: %v", a.filepath)
runtime.WindowSetTitle(a.ctx, "gotepad - "+filepath.Base(a.filepath))
var response Response
// Save the file at the selected file path
err := os.WriteFile(a.filepath, []byte(contents), 0666)
if err != nil {
response.Status = "error"
response.Message = err.Error()
} else {
response.Status = "success"
a.status.saved = true
}
// Emit an event to notify the save status
runtime.EventsEmit(a.ctx, "onFileSaved", response)
}