diff --git a/app_scripts.js b/app_scripts.js index e0c0762..d21cff0 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -1327,3 +1327,12 @@ function mediaSetVolume(elementId, volume) { element.volume = volume } } + +function startDowndload(url, filename) { + var element = document.getElementById("ruiDownloader"); + if (element) { + element.href = url; + element.setAttribute("download", filename); + element.click(); + } +} diff --git a/application.go b/application.go index f459379..fb0d3db 100644 --- a/application.go +++ b/application.go @@ -1,15 +1,19 @@ package rui import ( + "bytes" _ "embed" "fmt" "io" "log" "math/rand" "net/http" + "os" "os/exec" + "path/filepath" "runtime" "strconv" + "time" ) //go:embed app_scripts.js @@ -67,6 +71,7 @@ func (app *application) getStartPage() string {
+ `) @@ -129,7 +134,8 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) { filename = filename[:size-1] } - if !serveResourceFile(filename, w, req) { + if !serveResourceFile(filename, w, req) && + !serveDownloadFile(filename, w, req) { w.WriteHeader(http.StatusNotFound) } } @@ -295,3 +301,62 @@ func OpenBrowser(url string) bool { return err != nil } + +type downloadFile struct { + filename string + path string + data []byte +} + +var currentDownloadId = int(rand.Int31()) +var downloadFiles = map[string]downloadFile{} + +func (session *sessionData) startDownload(file downloadFile) { + currentDownloadId++ + id := strconv.Itoa(currentDownloadId) + downloadFiles[id] = file + session.runScript(fmt.Sprintf(`startDowndload("%s", "%s")`, id, file.filename)) +} + +func serveDownloadFile(id string, w http.ResponseWriter, r *http.Request) bool { + if file, ok := downloadFiles[id]; ok { + delete(downloadFiles, id) + if file.data != nil { + http.ServeContent(w, r, file.filename, time.Now(), bytes.NewReader(file.data)) + return true + } else if _, err := os.Stat(file.path); err == nil { + http.ServeFile(w, r, file.path) + return true + } + } + return false +} + +// DownloadFile starts downloading the file on the client side. +func (session *sessionData) DownloadFile(path string) { + if _, err := os.Stat(path); err != nil { + ErrorLog(err.Error()) + return + } + + _, filename := filepath.Split(path) + session.startDownload(downloadFile{ + filename: filename, + path: path, + data: nil, + }) +} + +// DownloadFileData starts downloading the file on the client side. Arguments specify the name of the downloaded file and its contents +func (session *sessionData) DownloadFileData(filename string, data []byte) { + if data == nil { + ErrorLog("Invalid download data. Must be not nil.") + return + } + + session.startDownload(downloadFile{ + filename: filename, + path: "", + data: data, + }) +} diff --git a/demo/filePickerDemo.go b/demo/filePickerDemo.go index 2f5cbf9..819d6c3 100644 --- a/demo/filePickerDemo.go +++ b/demo/filePickerDemo.go @@ -6,18 +6,24 @@ import ( const filePickerDemoText = ` GridLayout { - width = 100%, height = 100%, cell-height = "auto, 1fr", + width = 100%, height = 100%, cell-height = "auto, 1fr", cell-width = "1fr, auto", content = [ FilePicker { id = filePicker, accept = "txt, html" }, + Button { + id = fileDownload, row = 0, column = 1, content = "Download file", disabled = true, + } EditView { - id = selectedFileData, row = 1, type = multiline, read-only = true, wrap = true, + id = selectedFileData, row = 1, column = 0:1, type = multiline, read-only = true, wrap = true, } ] } ` +var downloadedFile []byte = nil +var downloadedFilename = "" + func createFilePickerDemo(session rui.Session) rui.View { view := rui.CreateViewFromText(session, filePickerDemoText) if view == nil { @@ -28,12 +34,20 @@ func createFilePickerDemo(session rui.Session) rui.View { if len(files) > 0 { picker.LoadFile(files[0], func(file rui.FileInfo, data []byte) { if data != nil { + downloadedFile = data + downloadedFilename = files[0].Name rui.Set(view, "selectedFileData", rui.Text, string(data)) + rui.Set(view, "fileDownload", rui.Disabled, false) } else { rui.Set(view, "selectedFileData", rui.Text, rui.LastError()) } }) } }) + + rui.Set(view, "fileDownload", rui.ClickEvent, func() { + view.Session().DownloadFileData(downloadedFilename, downloadedFile) + }) + return view } diff --git a/session.go b/session.go index 46e12a4..8517a8e 100644 --- a/session.go +++ b/session.go @@ -53,6 +53,9 @@ type Session interface { // a description of the error is written to the log Set(viewID, tag string, value interface{}) bool + DownloadFile(path string) + DownloadFileData(filename string, data []byte) + registerAnimation(props []AnimatedProperty) string resolveConstants(value string) (string, bool)