Compare commits

...

18 Commits

Author SHA1 Message Date
Travis McLane
cbd98b5a1a update scripts/build.sh to run test only on v1 2021-03-20 15:01:09 +11:00
Lea Anthony
c8e0aea69c v2.0.0-alpha.54 2021-03-19 09:17:18 +11:00
Lea Anthony
7c0b236eb0 Fix for hiding window on close 2021-03-19 09:14:49 +11:00
Lea Anthony
16debbd109 v2.0.0-alpha.53 2021-03-18 20:55:26 +11:00
Lea Anthony
39bfa5d910 Support disabling tray menu. Fix font sizing. Tooltip in tray menu support. 2021-03-18 20:54:53 +11:00
Lea Anthony
6424579a9e v2.0.0-alpha.52 2021-03-17 23:30:42 +11:00
Lea Anthony
a962ae6f63 Support rich text in Tray labels 2021-03-17 23:30:08 +11:00
Lea Anthony
c7dee158ba tray menu Icon->Image. Support template images. 2021-03-17 22:24:09 +11:00
Lea Anthony
237d25089d Update CLI banner 2021-03-17 21:59:31 +11:00
Lea Anthony
265328d648 v2.0.0-alpha.51 2021-03-15 06:13:51 +11:00
Lea Anthony
6f40e85a6e Support startup urls when app not running 2021-03-15 06:13:00 +11:00
Lea Anthony
96d8509da3 v2.0.0-alpha.50 2021-03-13 15:17:35 +11:00
Lea Anthony
598445ab0f v2.0.0-alpha.49 2021-03-13 15:00:19 +11:00
Lea Anthony
24788e2fd3 Fix template images 2021-03-13 14:59:12 +11:00
Lea Anthony
bbf4dde43f Support upserting environment variables 2021-03-12 23:41:13 +11:00
Lea Anthony
0afd27ab45 Add FileLogger option 2021-03-12 23:40:35 +11:00
Lea Anthony
d498423ec2 v2.0.0-alpha.48 2021-03-09 22:28:07 +11:00
Mat Ryer
66ce84973c fixes for machines running TouchBar addresses https://github.com/matryer/xbar/issues/610 2021-03-09 11:24:46 +00:00
17 changed files with 366 additions and 105 deletions

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
echo "**** Checking if Wails passes unit tests ****"
if ! go test ./...
if ! go test ./lib/... ./runtime/... ./cmd/...
then
echo ""
echo "ERROR: Unit tests failed!"

View File

@@ -23,14 +23,14 @@ func fatal(message string) {
}
func banner(_ *clir.Cli) string {
return fmt.Sprintf("%s %s - Go/HTML Application Framework", colour.Yellow("Wails"), colour.DarkRed(version))
return fmt.Sprintf("%s %s", colour.Yellow("Wails CLI"), colour.DarkRed(version))
}
func main() {
var err error
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
app := clir.NewCli("Wails", "Go/HTML Appkit", version)
app.SetBannerFunction(banner)

View File

@@ -1,3 +1,3 @@
package main
var version = "v2.0.0-alpha.47"
var version = "v2.0.0-alpha.54"

View File

@@ -128,6 +128,7 @@ struct Application {
int hideToolbarSeparator;
int windowBackgroundIsTranslucent;
int hasURLHandlers;
const char *startupURL;
// Menu
Menu *applicationMenu;
@@ -144,6 +145,9 @@ struct Application {
// shutting down flag
bool shuttingDown;
// Running flag
bool running;
};
// Debug works like sprintf but mutes if the global debug flag is true
@@ -266,7 +270,7 @@ void Hide(struct Application *app) {
if( app->shuttingDown ) return;
ON_MAIN_THREAD(
msg(app->application, s("hide:"));
msg(app->mainWindow, s("orderOut:"));
);
}
@@ -304,8 +308,19 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
// TODO: Check this actually does reduce flicker
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
// We are now running!
app->running = true;
// Notify backend we are ready (system startup)
app->sendMessageToBackend("SS");
const char *readyMessage = "SS";
if( app->startupURL == NULL ) {
app->sendMessageToBackend("SS");
return;
}
readyMessage = concat("SS", app->startupURL);
app->sendMessageToBackend(readyMessage);
MEMFREE(readyMessage);
} else if( strcmp(name, "windowDrag") == 0 ) {
// Guard against null events
@@ -403,6 +418,11 @@ void ExecJS(struct Application *app, const char *js) {
void willFinishLaunching(id self, SEL cmd, id sender) {
struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
// If there are URL Handlers, register a listener for them
if( app->hasURLHandlers ) {
id eventManager = msg(c("NSAppleEventManager"), s("sharedAppleEventManager"));
msg(eventManager, s("setEventHandler:andSelector:forEventClass:andEventID:"), self, s("getUrl:withReplyEvent:"), kInternetEventClass, kAEGetURL);
}
messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}");
}
@@ -427,12 +447,6 @@ void themeChanged(id self, SEL cmd, id sender) {
}
}
// void willFinishLaunching(id self) {
// struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
// Debug(app, "willFinishLaunching called!");
// }
int releaseNSObject(void *const context, struct hashmap_element_s *const e) {
msg(e->data, s("release"));
return -1;
@@ -465,6 +479,10 @@ void DestroyApplication(struct Application *app) {
Debug(app, "Almost a double free for app->bindings");
}
if( app->startupURL != NULL ) {
MEMFREE(app->startupURL);
}
// Remove mouse monitors
if( app->mouseDownMonitor != NULL ) {
msg( c("NSEvent"), s("removeMonitor:"), app->mouseDownMonitor);
@@ -1156,46 +1174,58 @@ void DarkModeEnabled(struct Application *app, const char *callbackID) {
}
void getURL(id self, SEL selector, id event, id replyEvent) {
id desc = msg(event, s("paramDescriptorForKeyword:"), keyDirectObject);
id url = msg(desc, s("stringValue"));
const char* curl = cstr(url);
const char* message = concat("UC", curl);
messageFromWindowCallback(message);
MEMFREE(message);
struct Application *app = (struct Application *)objc_getAssociatedObject(self, "application");
id desc = msg(event, s("paramDescriptorForKeyword:"), keyDirectObject);
id url = msg(desc, s("stringValue"));
const char* curl = cstr(url);
if( curl == NULL ) {
return;
}
// If this was an incoming URL, but we aren't running yet
// save it to return when we complete
if( app->running != true ) {
app->startupURL = STRCOPY(curl);
return;
}
const char* message = concat("UC", curl);
messageFromWindowCallback(message);
MEMFREE(message);
}
void openURLs(id self, SEL selector, id event) {
filelog("\n\nI AM HERE!!!!!\n\n");
}
void createDelegate(struct Application *app) {
// Define delegate
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0);
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
Class appDelegate = objc_allocateClassPair((Class) c("NSResponder"), "AppDelegate", 0);
class_addProtocol(appDelegate, objc_getProtocol("NSTouchBarProvider"));
class_addMethod(appDelegate, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
class_addMethod(appDelegate, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
// All Menu Items use a common callback
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
class_addMethod(appDelegate, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
// If there are URL Handlers, register the callback method
if( app->hasURLHandlers ) {
class_addMethod(delegateClass, s("getUrl:withReplyEvent:"), (IMP) getURL, "i@:@@");
class_addMethod(appDelegate, s("getUrl:withReplyEvent:"), (IMP) getURL, "i@:@@");
}
// Script handler
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
objc_registerClassPair(delegateClass);
class_addMethod(appDelegate, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
objc_registerClassPair(appDelegate);
// Create delegate
id delegate = msg((id)delegateClass, s("new"));
id delegate = msg((id)appDelegate, s("new"));
objc_setAssociatedObject(delegate, "application", (id)app, OBJC_ASSOCIATION_ASSIGN);
// If there are URL Handlers, register a listener for them
if( app->hasURLHandlers ) {
id eventManager = msg(c("NSAppleEventManager"), s("sharedAppleEventManager"));
msg(eventManager, s("setEventHandler:andSelector:forEventClass:andEventID:"), delegate, s("getUrl:withReplyEvent:"), kInternetEventClass, kAEGetURL);
}
// Theme change listener
class_addMethod(delegateClass, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
class_addMethod(appDelegate, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
// Get defaultCenter
id defaultCenter = msg(c("NSDistributedNotificationCenter"), s("defaultCenter"));
@@ -1207,12 +1237,12 @@ void createDelegate(struct Application *app) {
}
bool windowShouldClose(id self, SEL cmd, id sender) {
msg(sender, s("orderBack:"));
msg(sender, s("orderOut:"));
return false;
}
bool windowShouldExit(id self, SEL cmd, id sender) {
msg(sender, s("orderBack:"));
msg(sender, s("orderOut:"));
messageFromWindowCallback("WC");
return false;
}
@@ -1974,6 +2004,10 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->hasURLHandlers = 0;
result->startupURL = NULL;
result->running = false;
return (void*) result;
}

View File

@@ -576,38 +576,7 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
return item;
}
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate) {
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
if( !alternate ) {
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
} else {
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
}
if( tooltip != NULL ) {
msg(item, s("setToolTip:"), str(tooltip));
}
// Process image
if( image != NULL && strlen(image) > 0) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
id nsimage = ALLOC("NSImage");
msg(nsimage, s("initWithData:"), imageData);
if( templateImage ) {
msg(nsimage, s("template"), YES);
}
msg(item, s("setImage:"), nsimage);
}
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
// Process Menu Item attributes
id dictionary = ALLOC_INIT("NSMutableDictionary");
@@ -658,10 +627,46 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
id attributedString = ALLOC("NSMutableAttributedString");
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
msg(dictionary, s("release"));
msg(item, s("setAttributedTitle:"), attributedString);
msg(attributedString, s("autorelease"));
msg(dictionary, s("release"));
return attributedString;
}
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate) {
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
if( !alternate ) {
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
} else {
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
}
if( tooltip != NULL ) {
msg(item, s("setToolTip:"), str(tooltip));
}
// Process image
if( image != NULL && strlen(image) > 0) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
id nsimage = ALLOC("NSImage");
msg(nsimage, s("initWithData:"), imageData);
if( templateImage ) {
msg(nsimage, s("setTemplate:"), YES);
}
msg(item, s("setImage:"), nsimage);
}
id attributedString = createAttributedString(title, fontName, fontSize, RGBA);
msg(item, s("setAttributedTitle:"), attributedString);
msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease"));
@@ -762,7 +767,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
bool templateImage = false;
getJSONBool(item, "MacTemplateImage", &templateImage);
int fontSize = 12;
int fontSize = 0;
getJSONInt(item, "FontSize", &fontSize);
// If we have an accelerator

View File

@@ -109,6 +109,8 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
void processMenuData(Menu *menu, JsonNode *menuData);
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup);
id GetMenu(Menu *menu);
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA);
#endif //ASSETS_C_MENU_DARWIN_H

View File

@@ -31,10 +31,19 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
result->ID = mustJSONString(processedJSON, "ID");
result->label = mustJSONString(processedJSON, "Label");
result->icon = mustJSONString(processedJSON, "Icon");
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
result->icon = mustJSONString(processedJSON, "Image");
result->fontName = getJSONString(processedJSON, "FontName");
result->RGBA = getJSONString(processedJSON, "RGBA");
getJSONBool(processedJSON, "MacTemplateImage", &result->templateImage);
result->fontSize = 0;
getJSONInt(processedJSON, "FontSize", &result->fontSize);
result->tooltip = NULL;
result->tooltip = getJSONString(processedJSON, "Tooltip");
result->disabled = false;
getJSONBool(processedJSON, "Disabled", &result->disabled);
// Create the menu
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
result->menu = NewMenu(processedMenu);
result->delegate = NULL;
@@ -54,7 +63,7 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
}
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled) {
// Exit early if NULL
if( trayMenu->label == NULL ) {
@@ -62,7 +71,15 @@ void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
}
// Update button label
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setTitle:"), str(label));
id attributedString = createAttributedString(label, fontName, fontSize, RGBA);
if( tooltip != NULL ) {
msg(statusBarButton, s("setToolTip:"), str(tooltip));
}
msg(statusBarButton, s("setEnabled:"), !disabled);
msg(statusBarButton, s("setAttributedTitle:"), attributedString);
}
void UpdateTrayIcon(TrayMenu *trayMenu) {
@@ -89,6 +106,10 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(trayMenu->icon), 0);
trayImage = ALLOC("NSImage");
msg(trayImage, s("initWithData:"), imageData);
if( trayMenu->templateImage ) {
msg(trayImage, s("setTemplate:"), YES);
}
}
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
@@ -112,7 +133,7 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
UpdateTrayIcon(trayMenu);
// Update the label if needed
UpdateTrayLabel(trayMenu, trayMenu->label);
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled);
// Update the menu
id menu = GetMenu(trayMenu->menu);

View File

@@ -13,6 +13,14 @@ typedef struct {
const char *label;
const char *icon;
const char *ID;
const char *tooltip;
bool templateImage;
const char *fontName;
int fontSize;
const char *RGBA;
bool disabled;
Menu* menu;
@@ -30,7 +38,7 @@ void DumpTrayMenu(TrayMenu* trayMenu);
void ShowTrayMenu(TrayMenu* trayMenu);
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
void UpdateTrayIcon(TrayMenu *trayMenu);
void UpdateTrayLabel(TrayMenu *trayMenu, const char*);
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled);
void LoadTrayIcons();
void UnloadTrayIcons();

View File

@@ -118,7 +118,17 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
// Check we have this menu
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
UpdateTrayLabel(menu, Label);
const char *fontName = getJSONString(parsedUpdate, "FontName");
const char *RGBA = getJSONString(parsedUpdate, "RGBA");
int fontSize = 0;
getJSONInt(parsedUpdate, "FontSize", &fontSize);
const char *tooltip = getJSONString(parsedUpdate, "Tooltip");
bool disabled = false;
getJSONBool(parsedUpdate, "Disabled", &disabled);
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled);
}

View File

@@ -23,13 +23,19 @@ func generateTrayID() string {
}
type TrayMenu struct {
ID string
Label string
Icon string
menuItemMap *MenuItemMap
menu *menu.Menu
ProcessedMenu *WailsMenu
trayMenu *menu.TrayMenu
ID string
Label string
FontSize int
FontName string
Disabled bool
Tooltip string `json:",omitempty"`
Image string
MacTemplateImage bool
RGBA string
menuItemMap *MenuItemMap
menu *menu.Menu
ProcessedMenu *WailsMenu
trayMenu *menu.TrayMenu
}
func (t *TrayMenu) AsJSON() (string, error) {
@@ -43,11 +49,17 @@ func (t *TrayMenu) AsJSON() (string, error) {
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
result := &TrayMenu{
Label: trayMenu.Label,
Icon: trayMenu.Icon,
menu: trayMenu.Menu,
menuItemMap: NewMenuItemMap(),
trayMenu: trayMenu,
Label: trayMenu.Label,
FontName: trayMenu.FontName,
FontSize: trayMenu.FontSize,
Disabled: trayMenu.Disabled,
Tooltip: trayMenu.Tooltip,
Image: trayMenu.Image,
MacTemplateImage: trayMenu.MacTemplateImage,
menu: trayMenu.Menu,
RGBA: trayMenu.RGBA,
menuItemMap: NewMenuItemMap(),
trayMenu: trayMenu,
}
result.menuItemMap.AddMenu(trayMenu.Menu)
@@ -137,13 +149,27 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
}
type LabelUpdate struct {
ID string
Label string
ID string
Label string
FontName string
FontSize int
RGBA string
Disabled bool
Tooltip string
Image string
MacTemplateImage bool
}
update := &LabelUpdate{
ID: trayID,
Label: trayMenu.Label,
ID: trayID,
Label: trayMenu.Label,
FontName: trayMenu.FontName,
FontSize: trayMenu.FontSize,
Disabled: trayMenu.Disabled,
Tooltip: trayMenu.Tooltip,
Image: trayMenu.Image,
MacTemplateImage: trayMenu.MacTemplateImage,
RGBA: trayMenu.RGBA,
}
data, err := json.Marshal(update)

View File

@@ -40,7 +40,8 @@ func systemMessageParser(message string) (*parsedMessage, error) {
// This is our startup hook - the frontend is now ready
case 'S':
topic := "hooks:startup"
responseMessage = &parsedMessage{Topic: topic, Data: nil}
startupURL := message[1:]
responseMessage = &parsedMessage{Topic: topic, Data: startupURL}
default:
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
}

View File

@@ -34,6 +34,9 @@ type Runtime struct {
// Startup Hook
startupOnce sync.Once
// Service bus
bus *servicebus.ServiceBus
}
// NewRuntime creates a new runtime subsystem
@@ -58,6 +61,7 @@ func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.
runtime: runtime.New(bus),
startupCallback: startupCallback,
ctx: ctx,
bus: bus,
}
return result, nil
@@ -79,7 +83,15 @@ func (r *Runtime) Start() error {
case "startup":
if r.startupCallback != nil {
r.startupOnce.Do(func() {
go r.startupCallback(r.runtime)
go func() {
r.startupCallback(r.runtime)
// If we got a url, publish it now startup completed
url, ok := hooksMessage.Data().(string)
if ok && len(url) > 0 {
r.bus.Publish("url:handler", url)
}
}()
})
} else {
r.logger.Warning("no startup callback registered!")

View File

@@ -223,9 +223,6 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
// Set the directory
cmd.Dir = b.projectData.Path
// Set GO111MODULE environment variable
cmd.Env = append(os.Environ(), "GO111MODULE=on")
// Add CGO flags
// We use the project/build dir as a temporary place for our generated c headers
buildBaseDir, err := fs.RelativeToCwd("build")
@@ -233,7 +230,16 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
return err
}
cmd.Env = append(os.Environ(), fmt.Sprintf("CGO_CFLAGS=-I%s", buildBaseDir))
cmd.Env = os.Environ() // inherit env
// Use upsertEnv so we don't overwrite user's CGO_CFLAGS
cmd.Env = upsertEnv(cmd.Env, "CGO_CFLAGS", func(v string) string {
if v != "" {
v += " "
}
v += "-I" + buildBaseDir
return v
})
// Setup buffers
var stdo, stde bytes.Buffer
@@ -391,3 +397,22 @@ func (b *BaseBuilder) ExtractAssets() (*html.AssetBundle, error) {
// Read in html
return html.NewAssetBundle(b.projectData.HTML)
}
func upsertEnv(env []string, key string, update func(v string) string) []string {
newEnv := make([]string, len(env), len(env)+1)
found := false
for i := range env {
if strings.HasPrefix(env[i], key+"=") {
eqIndex := strings.Index(env[i], "=")
val := env[i][eqIndex+1:]
newEnv[i] = fmt.Sprintf("%s=%v", key, update(val))
found = true
continue
}
newEnv[i] = env[i]
}
if !found {
newEnv = append(newEnv, fmt.Sprintf("%s=%v", key, update("")))
}
return newEnv
}

View File

@@ -0,0 +1,31 @@
package build
import "testing"
func TestUpdateEnv(t *testing.T) {
env := []string{"one=1", "two=a=b", "three="}
newEnv := upsertEnv(env, "two", func(v string) string {
return v + "+added"
})
newEnv = upsertEnv(newEnv, "newVar", func(v string) string {
return "added"
})
newEnv = upsertEnv(newEnv, "three", func(v string) string {
return "3"
})
if len(newEnv) != 4 {
t.Errorf("expected: 4, got: %d", len(newEnv))
}
if newEnv[1] != "two=a=b+added" {
t.Errorf("expected: \"two=a=b+added\", got: %q", newEnv[1])
}
if newEnv[2] != "three=3" {
t.Errorf("expected: \"three=3\", got: %q", newEnv[2])
}
if newEnv[3] != "newVar=added" {
t.Errorf("expected: \"newVar=added\", got: %q", newEnv[3])
}
}

View File

@@ -0,0 +1,66 @@
package logger
import (
"log"
"os"
)
// FileLogger is a utility to log messages to a number of destinations
type FileLogger struct {
filename string
}
// NewFileLogger creates a new Logger.
func NewFileLogger(filename string) Logger {
return &FileLogger{
filename: filename,
}
}
// Print works like Sprintf.
func (l *FileLogger) Print(message string) {
f, err := os.OpenFile(l.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
if _, err := f.WriteString(message); err != nil {
f.Close()
log.Fatal(err)
}
f.Close()
}
func (l *FileLogger) Println(message string) {
l.Print(message + "\n")
}
// Trace level logging. Works like Sprintf.
func (l *FileLogger) Trace(message string) {
l.Println("TRACE | " + message)
}
// Debug level logging. Works like Sprintf.
func (l *FileLogger) Debug(message string) {
l.Println("DEBUG | " + message)
}
// Info level logging. Works like Sprintf.
func (l *FileLogger) Info(message string) {
l.Println("INFO | " + message)
}
// Warning level logging. Works like Sprintf.
func (l *FileLogger) Warning(message string) {
l.Println("WARN | " + message)
}
// Error level logging. Works like Sprintf.
func (l *FileLogger) Error(message string) {
l.Println("ERROR | " + message)
}
// Fatal level logging. Works like Sprintf.
func (l *FileLogger) Fatal(message string) {
l.Println("FATAL | " + message)
os.Exit(1)
}

View File

@@ -29,7 +29,7 @@ type MenuItem struct {
// Callback function when menu clicked
Click Callback `json:"-"`
// Colour
// Text Colour
RGBA string
// Font

View File

@@ -6,11 +6,31 @@ type TrayMenu struct {
// Label is the text we wish to display in the tray
Label string
// Icon is the name of the tray icon we wish to display.
// Image is the name of the tray icon we wish to display.
// These are read up during build from <projectdir>/trayicons and
// the filenames are used as IDs, minus the extension
// EG: <projectdir>/trayicons/main.png can be referenced here with "main"
Icon string
// If the image is not a filename, it will be treated as base64 image data
Image string
// MacTemplateImage indicates that on a Mac, this image is a template image
MacTemplateImage bool
// Text Colour
RGBA string
// Font
FontSize int
FontName string
// Tooltip
Tooltip string
// Callback function when menu clicked
//Click Callback `json:"-"`
// Disabled makes the item unselectable
Disabled bool
// Menu is the initial menu we wish to use for the tray
Menu *Menu