This repository has been archived by the owner on Feb 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 262
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Kelp UI: Wrap GUI as a standalone desktop application using Electron (#…
…308) * 1 - open browser when running GUI in production mode * 2 - add server command to kelp in release mode and invoke in root command * 3 - open browser automatically in local mode as well * 4 - incorporated go-astilectron and opened up app * 5 - add astilectron-bootstrap and astilectron-bundler as dependencies * 6 - use astilectron-bootstrap to start up server * 7 - basic build for GUI desktop app using build script * 8 - include logic building UI with multiple GOOS values in build script * 9 - update clean script with change of filesystem file generation * 10 - zip UI output, and wrap into a versioned folder * 11 - remove sleep when serving through desktop * 12 - add quit logic with tray icon (dev mode only) * 13 - write out tray icon from bind file to directory so it can be used * 14 - update README to indicate that astilectron-bundler is a dependency * 15 - generate bind file before compiling binary * 16 - circleci script should also install astilectron-bundler as a dependency * 17 - circleci update cache key since the steps changed * 18 - circleci build kelp before running tests
- Loading branch information
1 parent
da0e5fa
commit b725cba
Showing
17 changed files
with
369 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,5 +7,6 @@ vendor/ | |
build/ | ||
bin/ | ||
.idea | ||
gui/filesystem_vfsdata_release.go | ||
gui/filesystem_vfsdata.go | ||
kelp.prefs | ||
bind_*.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -98,6 +98,7 @@ To compile Kelp from source: | |
* `git clone [email protected]:stellar/kelp.git` | ||
5. Change to the kelp directory and install the dependencies: | ||
* `glide install` | ||
6. Install the [astilectron-bundler][astilectron-bundler] binary in a folder that is in your `PATH` | ||
6. Build the binaries using the provided build script (the _go install_ command will produce a faulty binary): | ||
* `./scripts/build.sh` | ||
7. Confirm one new binary file: | ||
|
@@ -310,6 +311,7 @@ See the [Code of Conduct](CODE_OF_CONDUCT.md). | |
[glide-install]: https://github.com/Masterminds/glide#install | ||
[yarn-install]: https://yarnpkg.com/lang/en/docs/install/ | ||
[nodejs-install]: https://nodejs.org/en/download/ | ||
[astilectron-bundler]: https://github.com/asticode/go-astilectron-bundler | ||
[spread]: https://en.wikipedia.org/wiki/Bid%E2%80%93ask_spread | ||
[hedge]: https://en.wikipedia.org/wiki/Hedge_(finance) | ||
[cmc]: https://coinmarketcap.com/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"app_name": "Kelp", | ||
"icon_path_darwin": "resources/[email protected]", | ||
"icon_path_linux": "resources/[email protected]", | ||
"icon_path_windows": "resources/[email protected]", | ||
"bind": { | ||
"output_path": "./cmd", | ||
"package": "cmd" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,37 @@ | ||
package cmd | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"image" | ||
"image/png" | ||
"log" | ||
"net/http" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
"time" | ||
|
||
"github.com/asticode/go-astilectron" | ||
bootstrap "github.com/asticode/go-astilectron-bootstrap" | ||
"github.com/go-chi/chi" | ||
"github.com/go-chi/chi/middleware" | ||
"github.com/rs/cors" | ||
"github.com/spf13/cobra" | ||
"github.com/stellar/go/clients/horizonclient" | ||
"github.com/stellar/go/support/errors" | ||
"github.com/stellar/kelp/gui" | ||
"github.com/stellar/kelp/gui/backend" | ||
"github.com/stellar/kelp/support/kelpos" | ||
"github.com/stellar/kelp/support/prefs" | ||
) | ||
|
||
const urlOpenDelayMillis = 1500 | ||
const kelpPrefsDirectory = ".kelp" | ||
const kelpAssetsPath = "/assets" | ||
const trayIconName = "[email protected]" | ||
|
||
var serverCmd = &cobra.Command{ | ||
Use: "server", | ||
Short: "Serves the Kelp GUI", | ||
|
@@ -97,12 +109,12 @@ func init() { | |
go runAPIServerDevBlocking(s, *options.port, *options.devAPIPort) | ||
runWithYarn(kos, options) | ||
return | ||
} else { | ||
options.devAPIPort = nil | ||
// the frontend app checks the REACT_APP_API_PORT variable to be set when serving | ||
os.Setenv("REACT_APP_API_PORT", fmt.Sprintf("%d", *options.port)) | ||
} | ||
|
||
options.devAPIPort = nil | ||
// the frontend app checks the REACT_APP_API_PORT variable to be set when serving | ||
os.Setenv("REACT_APP_API_PORT", fmt.Sprintf("%d", *options.port)) | ||
|
||
if env == envDev { | ||
checkHomeDir() | ||
generateStaticFiles(kos) | ||
|
@@ -116,8 +128,22 @@ func init() { | |
|
||
portString := fmt.Sprintf(":%d", *options.port) | ||
log.Printf("Serving frontend and API server on HTTP port: %d\n", *options.port) | ||
e = http.ListenAndServe(portString, r) | ||
log.Fatal(e) | ||
// local mode (non --dev) and release binary should open browser (since --dev already opens browser via yarn and returns) | ||
go func() { | ||
url := fmt.Sprintf("http://localhost:%d", *options.port) | ||
log.Printf("A browser window will open up automatically to %s\n", url) | ||
time.Sleep(urlOpenDelayMillis * time.Millisecond) | ||
openBrowser(kos, url) | ||
}() | ||
|
||
if env == envDev { | ||
e = http.ListenAndServe(portString, r) | ||
if e != nil { | ||
log.Fatal(e) | ||
} | ||
} else { | ||
_ = http.ListenAndServe(portString, r) | ||
} | ||
} | ||
} | ||
|
||
|
@@ -178,3 +204,104 @@ func generateStaticFiles(kos *kelpos.KelpOS) { | |
log.Printf("... finished generating contents of gui/web/build\n") | ||
log.Println() | ||
} | ||
|
||
func writeTrayIcon(kos *kelpos.KelpOS) (string, error) { | ||
binDirectory, e := getBinaryDirectory() | ||
if e != nil { | ||
return "", errors.Wrap(e, "could not get binary directory") | ||
} | ||
log.Printf("binDirectory: %s", binDirectory) | ||
assetsDirPath := filepath.Join(binDirectory, kelpPrefsDirectory, kelpAssetsPath) | ||
log.Printf("assetsDirPath: %s", assetsDirPath) | ||
trayIconPath := filepath.Join(assetsDirPath, trayIconName) | ||
log.Printf("trayIconPath: %s", trayIconPath) | ||
if _, e := os.Stat(trayIconPath); !os.IsNotExist(e) { | ||
// file exists, don't write again | ||
return trayIconPath, nil | ||
} | ||
|
||
trayIconBytes, e := resourcesKelpIcon18xPngBytes() | ||
if e != nil { | ||
return "", errors.Wrap(e, "could not fetch tray icon image bytes") | ||
} | ||
|
||
img, _, e := image.Decode(bytes.NewReader(trayIconBytes)) | ||
if e != nil { | ||
return "", errors.Wrap(e, "could not decode bytes as image data") | ||
} | ||
|
||
// create dir if not exists | ||
if _, e := os.Stat(assetsDirPath); os.IsNotExist(e) { | ||
log.Printf("making assetsDirPath: %s ...", assetsDirPath) | ||
e = kos.Mkdir(assetsDirPath) | ||
if e != nil { | ||
return "", errors.Wrap(e, "could not make directories for assetsDirPath: "+assetsDirPath) | ||
} | ||
log.Printf("... made assetsDirPath (%s)", assetsDirPath) | ||
} | ||
|
||
trayIconFile, e := os.Create(trayIconPath) | ||
if e != nil { | ||
return "", errors.Wrap(e, "could not create tray icon file") | ||
} | ||
defer trayIconFile.Close() | ||
|
||
e = png.Encode(trayIconFile, img) | ||
if e != nil { | ||
return "", errors.Wrap(e, "could not write png encoded icon") | ||
} | ||
|
||
return trayIconPath, nil | ||
} | ||
|
||
func getBinaryDirectory() (string, error) { | ||
return filepath.Abs(filepath.Dir(os.Args[0])) | ||
} | ||
|
||
func openBrowser(kos *kelpos.KelpOS, url string) { | ||
trayIconPath, e := writeTrayIcon(kos) | ||
if e != nil { | ||
log.Fatal(errors.Wrap(e, "could not write tray icon")) | ||
} | ||
|
||
e = bootstrap.Run(bootstrap.Options{ | ||
AstilectronOptions: astilectron.Options{ | ||
AppName: "Kelp", | ||
AppIconDefaultPath: "resources/[email protected]", | ||
}, | ||
Debug: false, | ||
Windows: []*bootstrap.Window{&bootstrap.Window{ | ||
Homepage: url, | ||
Options: &astilectron.WindowOptions{ | ||
Center: astilectron.PtrBool(true), | ||
Width: astilectron.PtrInt(1280), | ||
Height: astilectron.PtrInt(960), | ||
Closable: astilectron.PtrBool(false), | ||
}, | ||
}}, | ||
TrayOptions: &astilectron.TrayOptions{ | ||
Image: astilectron.PtrStr(trayIconPath), | ||
}, | ||
TrayMenuOptions: []*astilectron.MenuItemOptions{ | ||
&astilectron.MenuItemOptions{ | ||
Label: astilectron.PtrStr("Quit"), | ||
Visible: astilectron.PtrBool(true), | ||
Enabled: astilectron.PtrBool(true), | ||
OnClick: astilectron.Listener(func(e astilectron.Event) (deleteListener bool) { | ||
quit() | ||
return false | ||
}), | ||
}, | ||
}, | ||
}) | ||
if e != nil { | ||
log.Fatal(e) | ||
} | ||
|
||
quit() | ||
} | ||
|
||
func quit() { | ||
log.Printf("quitting...") | ||
os.Exit(0) | ||
} |
Oops, something went wrong.