From 142a43b5487f76ea314154b11746002cae88494a Mon Sep 17 00:00:00 2001 From: Sammy-T Date: Thu, 11 Jul 2024 21:27:51 -0400 Subject: [PATCH] Implement read on file drop --- app.go | 82 +++++++++++++++++++++++++++++------------ frontend/src/main.js | 7 +++- frontend/src/styles.css | 4 ++ main.go | 4 ++ termaction.go | 6 +-- 5 files changed, 74 insertions(+), 29 deletions(-) diff --git a/app.go b/app.go index 61b9dca..546a64d 100644 --- a/app.go +++ b/app.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "log" "os" "path/filepath" @@ -14,7 +15,7 @@ import ( type App struct { ctx context.Context defaultFilename string - filePath string + filepath string fileFilters []runtime.FileFilter status docStatus } @@ -36,13 +37,15 @@ func (a *App) startup(ctx context.Context) { 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 == "") { + if a.status.saved || (a.filepath == "" && a.status.content == "") { return false } @@ -113,6 +116,27 @@ func (a *App) onSaveStatusUpdated(optionalData ...interface{}) { 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 @@ -120,7 +144,7 @@ func (a *App) UpdateDefaultName(filename string) { // NewFile clears the file path, sets the window title, and emits an 'onNewFile' event func (a *App) NewFile() { - a.filePath = "" + a.filepath = "" a.defaultFilename = "*.txt" a.status = docStatus{} @@ -134,34 +158,44 @@ func (a *App) OpenFile() { Filters: a.fileFilters, } - response := Response{} - // Open the dialog - filePath, err := runtime.OpenFileDialog(a.ctx, options) + filepathStr, err := runtime.OpenFileDialog(a.ctx, options) if err != nil { log.Printf("Error retrieving file path. %v", err) return - } else if filePath == "" { + } else if filepathStr == "" { return // Return early if the user cancelled } + log.Printf("filepath: %v\n", filepathStr) + + a.readFile(filepathStr) +} - a.filePath = filePath - a.defaultFilename = "*" + filepath.Ext(filePath) - runtime.WindowSetTitle(a.ctx, "gotepad - "+filepath.Base(filePath)) +// 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) - // Read the file at the selected file path - data, err := os.ReadFile(filePath) + 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", err) + log.Printf("Error reading file. %v\n", err) return } // Update the status a.status = docStatus{true, string(data)} - response.Status = "success" - response.Message = filePath - response.Data = 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) @@ -175,33 +209,33 @@ func (a *App) SaveAs(contents string) { } // Open the dialog - filePath, err := runtime.SaveFileDialog(a.ctx, options) + filepathStr, err := runtime.SaveFileDialog(a.ctx, options) if err != nil { - log.Printf("Error retrieving file path. %v", err) + log.Printf("Error retrieving file path. %v\n", err) return - } else if filePath == "" { + } else if filepathStr == "" { return // Return early if the user cancelled } - a.filePath = filePath + 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 == "" { + if a.filepath == "" { a.SaveAs(contents) return } - log.Printf("Save path: %v", a.filePath) - runtime.WindowSetTitle(a.ctx, "gotepad - "+filepath.Base(a.filePath)) + 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) + err := os.WriteFile(a.filepath, []byte(contents), 0666) if err != nil { response.Status = "error" response.Message = err.Error() diff --git a/frontend/src/main.js b/frontend/src/main.js index 222df3f..f7066d3 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,7 +1,7 @@ import './ext/menu'; import './ext/terminal'; import {editor, supportedLangs, setEditorLang} from './ext/editor'; -import {BrowserOpenURL, Environment, EventsEmit, EventsOn} from '../wailsjs/runtime/runtime'; +import {BrowserOpenURL, Environment, EventsEmit, EventsOn, OnFileDrop} from '../wailsjs/runtime/runtime'; import {NewFile, OpenFile, SaveAs, Save, UpdateDefaultName} from '../wailsjs/go/main/App'; import {ReadConfig, OpenConfigFile} from '../wailsjs/go/main/AppConfig'; @@ -380,6 +380,9 @@ EventsOn('onFileSaved', onFileSaved); EventsOn('onRequestSaveAs', () => SaveAs(editor.getValue())); EventsOn('onRequestSave', () => Save(editor.getValue())); +//// TODO: Second arg must be `true` to work until the fix already added to master is released. +OnFileDrop((x, y, paths) => console.log(`wails onfiledrop:`, {x, y}, paths), true); + readPrefs(); initLanguages(); @@ -397,4 +400,4 @@ editor.getContribution('editor.linkDetector').openerService.open = (url) => Brow menuItems.forEach(initMenuItem); document.addEventListener('keydown', onKey); -modals.forEach(initModal); \ No newline at end of file +modals.forEach(initModal); diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 2ead36f..dd89a33 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -11,6 +11,10 @@ html { --menu-list-border-col: var(--menu-list-border-dark); } +main { + --wails-drop-target: drop; +} + @media (prefers-color-scheme: light) { html:not([data-theme=dark]) { background-color: var(--theme-bg-light); diff --git a/main.go b/main.go index 86fdff2..1d1cbcb 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,10 @@ func main() { Height: 650, Assets: assets, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + DragAndDrop: &options.DragAndDrop{ + EnableFileDrop: true, + DisableWebViewDrop: true, + }, OnStartup: func(ctx context.Context) { app.startup(ctx) termAction.startup(ctx) diff --git a/termaction.go b/termaction.go index 0a041b9..77af506 100644 --- a/termaction.go +++ b/termaction.go @@ -29,7 +29,7 @@ func (ta *TerminalAction) startup(ctx context.Context) { runtime.EventsOn(ta.ctx, "onConfigLoaded", ta.onConfigLoaded) } -func (ta *TerminalAction) onDomReady(ctx context.Context) { +func (ta *TerminalAction) onDomReady(_ context.Context) { response := Response{"success", "terminals mapped", ta.terminals} runtime.EventsEmit(ta.ctx, "onTerminalsMapped", response) } @@ -75,8 +75,8 @@ func (ta *TerminalAction) OpenTerminal(name string) { cmd := exec.Command(terminal.CmdRoot, terminal.OpenCmd...) // Set the command's working directory - if len(app.filePath) > 0 { - cmd.Dir = filepath.Dir(app.filePath) + if len(app.filepath) > 0 { + cmd.Dir = filepath.Dir(app.filepath) } // Run the command