positron.js 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builder.js +12 -2
- package/core/mac/main.swift +7 -1
- package/core/mac/tray.swift +95 -0
- package/core/win/main.cs +12 -1
- package/core/win/tray.cs +142 -0
- package/index.js +16 -4
- package/package.json +1 -1
- package/packager.js +17 -5
- package/tray.js +64 -0
package/builder.js
CHANGED
|
@@ -91,7 +91,17 @@ function performNativeBuild() {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
if (buildingForMac) {
|
|
94
|
-
|
|
94
|
+
|
|
95
|
+
const coreMacDir = path.join(__dirname, "core", "mac");
|
|
96
|
+
|
|
97
|
+
nativeExtensionsMac.push({
|
|
98
|
+
command:"createTray",
|
|
99
|
+
className:"TrayExtension",
|
|
100
|
+
sourceFile:path.join(coreMacDir, "tray.swift")
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// -1 to account for the built-in tray extension
|
|
104
|
+
info(`[Builder] Stitching ${nativeExtensionsMac.length-1} native extensions...`);
|
|
95
105
|
|
|
96
106
|
let registryContent = `// Auto-generated by Positron. Do not edit.\n`;
|
|
97
107
|
registryContent += `func getExtensionRegistry() -> [String: (Int, [String]) -> Void] {\n`;
|
|
@@ -112,7 +122,6 @@ function performNativeBuild() {
|
|
|
112
122
|
|
|
113
123
|
registryContent += `}\n`;
|
|
114
124
|
|
|
115
|
-
const coreMacDir = path.join(__dirname, "core", "mac");
|
|
116
125
|
fs.writeFileSync(path.join(coreMacDir, "Registry.swift"), registryContent);
|
|
117
126
|
|
|
118
127
|
info("[Builder] Compiling native binary...");
|
|
@@ -189,6 +198,7 @@ function performNativeBuild() {
|
|
|
189
198
|
const coreWinDir = path.join(__dirname, "core", "win");
|
|
190
199
|
const extensionsDir = path.join(coreWinDir, "extensions");
|
|
191
200
|
|
|
201
|
+
|
|
192
202
|
// 1. Clean and prepare a staging folder for all native extensions
|
|
193
203
|
if (fs.existsSync(extensionsDir)) fs.rmSync(extensionsDir, { recursive: true, force: true });
|
|
194
204
|
fs.mkdirSync(extensionsDir, { recursive: true });
|
package/core/mac/main.swift
CHANGED
|
@@ -832,6 +832,12 @@ case "addToContentBlocker":
|
|
|
832
832
|
IPCResponse(windowId: windowId, event: args.last ?? "isFocused-reply-\(windowId)", data: ["isFocused": isFocused ? "true" : "false"])
|
|
833
833
|
)
|
|
834
834
|
|
|
835
|
+
case "getFocusedWindowId":
|
|
836
|
+
let focusedWindowId = windows.first(where: { $0.value.isKeyWindow })?.key ?? -1
|
|
837
|
+
AppDelegate.shared?.ipcClient.send(
|
|
838
|
+
IPCResponse(windowId: windowId, event: args.last ?? "getFocusedWindowId-reply-\(windowId)", data: ["focusedWindowId": "\(focusedWindowId)"])
|
|
839
|
+
)
|
|
840
|
+
|
|
835
841
|
case "emitToRenderer":
|
|
836
842
|
guard let window = windows[windowId] else { return }
|
|
837
843
|
guard args.count >= 2 else {
|
|
@@ -1005,7 +1011,7 @@ func makePreloadScript(windowId: Int) -> String {
|
|
|
1005
1011
|
/** Called internally by Swift's evaluateJS to deliver a push message. */
|
|
1006
1012
|
_emit(channel, payload) {
|
|
1007
1013
|
(_listeners[channel] || []).forEach(fn => {
|
|
1008
|
-
try { fn(payload); } catch(e) { console.
|
|
1014
|
+
try { fn(payload); } catch(e) { console.error('[ipc] listener error:', e); }
|
|
1009
1015
|
});
|
|
1010
1016
|
},
|
|
1011
1017
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import Cocoa
|
|
2
|
+
|
|
3
|
+
class TrayManager {
|
|
4
|
+
static let shared = TrayManager()
|
|
5
|
+
var statusItem: NSStatusItem?
|
|
6
|
+
|
|
7
|
+
func setupTray() {
|
|
8
|
+
if statusItem == nil {
|
|
9
|
+
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
10
|
+
if let button = statusItem?.button {
|
|
11
|
+
button.title = "App"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func setMenu(_ menu: NSMenu) {
|
|
17
|
+
statusItem?.menu = menu
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func setTitle(_ title: String) {
|
|
21
|
+
statusItem?.button?.title = title
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func setIcon(_ iconPath: String) {
|
|
25
|
+
guard let button = statusItem?.button else { return printError("Tray button not initialized") }
|
|
26
|
+
|
|
27
|
+
if iconPath.isEmpty {
|
|
28
|
+
DispatchQueue.main.async {
|
|
29
|
+
button.image = nil
|
|
30
|
+
button.imagePosition = .imageLeft
|
|
31
|
+
}
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
guard FileManager.default.fileExists(atPath: iconPath) else {
|
|
36
|
+
printError("Icon path does not exist: \(iconPath)")
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
DispatchQueue.main.async {
|
|
41
|
+
if let img = NSImage(contentsOfFile: iconPath) {
|
|
42
|
+
img.size = NSSize(width: 18, height: 18)
|
|
43
|
+
img.isTemplate = true
|
|
44
|
+
button.image = img
|
|
45
|
+
button.imagePosition = .imageLeft
|
|
46
|
+
} else {
|
|
47
|
+
button.image = nil
|
|
48
|
+
button.imagePosition = .imageLeft
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public struct TrayExtension {
|
|
55
|
+
public static func handle(windowId: Int, args: [String]) {
|
|
56
|
+
|
|
57
|
+
if(args.last == "setTitle") {
|
|
58
|
+
TrayManager.shared.setTitle(args[0])
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if(args.last == "setIcon") {
|
|
63
|
+
TrayManager.shared.setIcon(args[0])
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
guard let descString = args.first,
|
|
68
|
+
let data = descString.data(using: .utf8),
|
|
69
|
+
let desc = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
|
70
|
+
printError("tray setMenu — invalid JSON descriptor")
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if args.last == "setMenu" {
|
|
75
|
+
TrayManager.shared.setMenu(buildContextMenu(from: desc, windowId: windowId))
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
DispatchQueue.main.async {
|
|
80
|
+
|
|
81
|
+
TrayManager.shared.setupTray()
|
|
82
|
+
|
|
83
|
+
let title = args.count > 1 ? args[1] : ""
|
|
84
|
+
TrayManager.shared.setTitle(title)
|
|
85
|
+
|
|
86
|
+
let imagePath = args.count > 2 ? args[2] : nil
|
|
87
|
+
if let imagePath = imagePath {
|
|
88
|
+
TrayManager.shared.setIcon(imagePath)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let menu = buildContextMenu(from: desc, windowId: windowId)
|
|
92
|
+
TrayManager.shared.setMenu(menu)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
package/core/win/main.cs
CHANGED
|
@@ -18,6 +18,7 @@ using System.Net.Sockets;
|
|
|
18
18
|
using Microsoft.VisualBasic;
|
|
19
19
|
using System.Runtime.InteropServices;
|
|
20
20
|
using Microsoft.Win32;
|
|
21
|
+
using System.Linq;
|
|
21
22
|
|
|
22
23
|
class PowerSaveBlocker
|
|
23
24
|
{
|
|
@@ -722,6 +723,16 @@ case "forceCloseWindow":
|
|
|
722
723
|
});
|
|
723
724
|
break;
|
|
724
725
|
|
|
726
|
+
case "getFocusedWindowId":
|
|
727
|
+
int focusedWindowId = WindowsMap.FirstOrDefault(kv => kv.Value.IsActive).Key;
|
|
728
|
+
_ipcClient.Send(new IPCResponse
|
|
729
|
+
{
|
|
730
|
+
windowId = windowId,
|
|
731
|
+
@event = args[^1] ?? "getFocusedWindowId-reply-" + windowId,
|
|
732
|
+
data = new() { { "focusedWindowId", focusedWindowId.ToString() } }
|
|
733
|
+
});
|
|
734
|
+
break;
|
|
735
|
+
|
|
725
736
|
case "showNotification":
|
|
726
737
|
if (args.Count < 2)
|
|
727
738
|
{
|
|
@@ -1094,7 +1105,7 @@ case "setBounds":
|
|
|
1094
1105
|
MenuMap[windowId] = menu;
|
|
1095
1106
|
}
|
|
1096
1107
|
|
|
1097
|
-
|
|
1108
|
+
internal static void PopulateMenu(ItemCollection parentItems, JsonArray items, int windowId, string eventType = "menu-action")
|
|
1098
1109
|
{
|
|
1099
1110
|
foreach (var item in items)
|
|
1100
1111
|
{
|
package/core/win/tray.cs
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Windows.Controls;
|
|
4
|
+
using System.Text.Json;
|
|
5
|
+
using System.Text.Json.Nodes;
|
|
6
|
+
using System.Windows;
|
|
7
|
+
using System.Drawing;
|
|
8
|
+
|
|
9
|
+
namespace PositronWindows
|
|
10
|
+
{
|
|
11
|
+
public class TrayManager
|
|
12
|
+
{
|
|
13
|
+
private static TrayManager? _shared;
|
|
14
|
+
public static TrayManager Shared => _shared ??= new TrayManager();
|
|
15
|
+
|
|
16
|
+
public System.Windows.Forms.NotifyIcon? NotifyIcon { get; private set; }
|
|
17
|
+
private ContextMenu? _wpfContextMenu;
|
|
18
|
+
|
|
19
|
+
public void SetupTray()
|
|
20
|
+
{
|
|
21
|
+
if (NotifyIcon == null)
|
|
22
|
+
{
|
|
23
|
+
NotifyIcon = new System.Windows.Forms.NotifyIcon();
|
|
24
|
+
NotifyIcon.Visible = true;
|
|
25
|
+
NotifyIcon.Text = "App";
|
|
26
|
+
NotifyIcon.Icon = SystemIcons.Application;
|
|
27
|
+
|
|
28
|
+
NotifyIcon.MouseUp += (s, e) =>
|
|
29
|
+
{
|
|
30
|
+
if (e.Button == System.Windows.Forms.MouseButtons.Right || e.Button == System.Windows.Forms.MouseButtons.Left)
|
|
31
|
+
{
|
|
32
|
+
if (_wpfContextMenu != null)
|
|
33
|
+
{
|
|
34
|
+
_wpfContextMenu.IsOpen = true;
|
|
35
|
+
if (Application.Current.MainWindow != null)
|
|
36
|
+
{
|
|
37
|
+
Application.Current.MainWindow.Activate();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public void SetMenu(ContextMenu menu)
|
|
46
|
+
{
|
|
47
|
+
_wpfContextMenu = menu;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public void SetTitle(string title)
|
|
51
|
+
{
|
|
52
|
+
if (NotifyIcon != null && !string.IsNullOrEmpty(title))
|
|
53
|
+
{
|
|
54
|
+
NotifyIcon.Text = title.Length > 63 ? title.Substring(0, 63) : title;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public void SetIcon(string iconPath)
|
|
59
|
+
{
|
|
60
|
+
if (NotifyIcon != null && !string.IsNullOrEmpty(iconPath))
|
|
61
|
+
{
|
|
62
|
+
try
|
|
63
|
+
{
|
|
64
|
+
NotifyIcon.Icon = new Icon(iconPath);
|
|
65
|
+
}
|
|
66
|
+
catch (Exception ex)
|
|
67
|
+
{
|
|
68
|
+
Console.WriteLine($"Failed to set tray icon: {ex.Message}");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public static class TrayExtension
|
|
75
|
+
{
|
|
76
|
+
public static void Handle(int windowId, List<string> args)
|
|
77
|
+
{
|
|
78
|
+
if (args.Count == 0)
|
|
79
|
+
{
|
|
80
|
+
Console.WriteLine("tray:setMenu — missing JSON descriptor");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (args[^1] == "setTitle")
|
|
85
|
+
{
|
|
86
|
+
string title = args[0];
|
|
87
|
+
Application.Current.Dispatcher.Invoke(() =>
|
|
88
|
+
{
|
|
89
|
+
TrayManager.Shared.SetTitle(title);
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (args[^1] == "setIcon")
|
|
95
|
+
{
|
|
96
|
+
string iconPath = args[0];
|
|
97
|
+
Application.Current.Dispatcher.Invoke(() =>
|
|
98
|
+
{
|
|
99
|
+
TrayManager.Shared.SetIcon(iconPath);
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
var descString = args[0];
|
|
105
|
+
var ctxDescriptor = JsonSerializer.Deserialize<JsonArray>(descString);
|
|
106
|
+
if (ctxDescriptor == null)
|
|
107
|
+
{
|
|
108
|
+
Console.WriteLine("tray:setMenu — invalid JSON descriptor");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if(args[^1] == "setMenu")
|
|
113
|
+
{
|
|
114
|
+
Application.Current.Dispatcher.Invoke(() =>
|
|
115
|
+
{
|
|
116
|
+
var contextMenu = new ContextMenu();
|
|
117
|
+
App.PopulateMenu(contextMenu.Items, ctxDescriptor, windowId, "context-menu-action");
|
|
118
|
+
TrayManager.Shared.SetMenu(contextMenu);
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
Application.Current.Dispatcher.Invoke(() =>
|
|
124
|
+
{
|
|
125
|
+
TrayManager.Shared.SetupTray();
|
|
126
|
+
|
|
127
|
+
string? title = args.Count > 1 ? args[1] : "";
|
|
128
|
+
TrayManager.Shared.SetTitle(title);
|
|
129
|
+
|
|
130
|
+
string? imagePath = args.Count > 2 ? args[2] : null;
|
|
131
|
+
if (imagePath != null)
|
|
132
|
+
{
|
|
133
|
+
TrayManager.Shared.SetIcon(imagePath);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var contextMenu = new ContextMenu();
|
|
137
|
+
App.PopulateMenu(contextMenu.Items, ctxDescriptor, windowId, "context-menu-action");
|
|
138
|
+
TrayManager.Shared.SetMenu(contextMenu);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
package/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const { info, error, warn, success } = require("./logs");
|
|
|
13
13
|
|
|
14
14
|
let currMenu = []
|
|
15
15
|
let contextMenu = [];
|
|
16
|
+
let trayMenu = [];
|
|
16
17
|
|
|
17
18
|
const randomPort = () => {
|
|
18
19
|
const min = 1024;
|
|
@@ -180,7 +181,7 @@ ws.on("message", raw => {
|
|
|
180
181
|
win.destroy();
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
|
-
} else if(msg.event == "menu-action" || msg.event == "context-menu-action") {
|
|
184
|
+
} else if(msg.event == "menu-action" || msg.event == "context-menu-action" || msg.event == "tray-menu-action") {
|
|
184
185
|
|
|
185
186
|
const findMenuAction = (items, label, channel) => {
|
|
186
187
|
if (!items || items.length === 0) return null;
|
|
@@ -199,7 +200,12 @@ ws.on("message", raw => {
|
|
|
199
200
|
return null;
|
|
200
201
|
}
|
|
201
202
|
|
|
202
|
-
|
|
203
|
+
let searchMenu = msg.event === "menu-action" ? currMenu : (msg.event === "context-menu-action" ? contextMenu : trayMenu);
|
|
204
|
+
let menuAction = findMenuAction(searchMenu, msg.data.label, msg.data.channel);
|
|
205
|
+
|
|
206
|
+
if (!menuAction && msg.event === "context-menu-action") {
|
|
207
|
+
menuAction = findMenuAction(trayMenu, msg.data.label, msg.data.channel);
|
|
208
|
+
}
|
|
203
209
|
|
|
204
210
|
if (menuAction) {
|
|
205
211
|
menuAction.click();
|
|
@@ -1106,6 +1112,10 @@ async showFileOpenDialog(options = {}) {
|
|
|
1106
1112
|
const app = {
|
|
1107
1113
|
|
|
1108
1114
|
name:"PositronApp",
|
|
1115
|
+
|
|
1116
|
+
setTrayMenu(menuTemplate) {
|
|
1117
|
+
trayMenu = menuTemplate;
|
|
1118
|
+
},
|
|
1109
1119
|
|
|
1110
1120
|
/**
|
|
1111
1121
|
* Quits the application by sending a terminate command to the native layer and then exiting the process. Emits a "before-quit" event before sending the command, and a "quit" event after initiating the quit sequence.
|
|
@@ -1162,8 +1172,10 @@ const app = {
|
|
|
1162
1172
|
* @returns {Promise<Window|null>} The currently focused window, or null if no windows are focused.
|
|
1163
1173
|
*/
|
|
1164
1174
|
async getFocusedWindow() {
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1175
|
+
const getFocused = await this.requestFromNative("getFocusedWindowId");
|
|
1176
|
+
const winId = getFocused?.focusedWindowId ? parseInt(getFocused.focusedWindowId, 10) : -1;
|
|
1177
|
+
const focusedWin = [...activeWindows].find(w => w.id === winId);
|
|
1178
|
+
return focusedWin || { error: "No focused window found" };
|
|
1167
1179
|
},
|
|
1168
1180
|
|
|
1169
1181
|
/**
|
package/package.json
CHANGED
package/packager.js
CHANGED
|
@@ -33,24 +33,27 @@ function performPackager() {
|
|
|
33
33
|
|
|
34
34
|
function handleJavaScriptPipeline(appRoot, resourcesPath) {
|
|
35
35
|
const targetOutputFile = path.join(resourcesPath, "index.js");
|
|
36
|
+
let bundledFiles = [];
|
|
36
37
|
|
|
37
38
|
info(`[Packager] Bundling JavaScript code with esbuild...`);
|
|
38
39
|
try {
|
|
39
|
-
esbuild.buildSync({
|
|
40
|
+
const result = esbuild.buildSync({
|
|
40
41
|
entryPoints: [path.join(appRoot, "index.js")],
|
|
41
42
|
bundle: true,
|
|
42
43
|
platform: "node",
|
|
43
44
|
target: `node${MAJOR_NODE_V}`,
|
|
44
45
|
outfile: targetOutputFile,
|
|
45
|
-
minify:
|
|
46
|
+
minify: true,
|
|
46
47
|
sourcemap: false,
|
|
48
|
+
metafile: true,
|
|
47
49
|
});
|
|
50
|
+
bundledFiles = Object.keys(result.metafile.inputs).map(f => path.resolve(appRoot, f));
|
|
48
51
|
} catch (err) {
|
|
49
52
|
error("Fatal: esbuild bundling failed.");
|
|
50
53
|
process.exit(1);
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
copyAppAssets(appRoot, resourcesPath);
|
|
56
|
+
copyAppAssets(appRoot, resourcesPath, bundledFiles);
|
|
54
57
|
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -212,7 +215,7 @@ async function packageWindows(appRoot, distDir, appName) {
|
|
|
212
215
|
success(`Successfully packaged Windows app directory at: ${outputFolder}`);
|
|
213
216
|
}
|
|
214
217
|
|
|
215
|
-
function copyAppAssets(src, dest) {
|
|
218
|
+
function copyAppAssets(src, dest, ignoredFiles = []) {
|
|
216
219
|
const ignoreList = ["node_modules", "dist", "bin", ".git"];
|
|
217
220
|
|
|
218
221
|
function copyRecursive(currentSrc, currentDest) {
|
|
@@ -221,6 +224,8 @@ function copyAppAssets(src, dest) {
|
|
|
221
224
|
if (ignoreList.includes(item)) continue;
|
|
222
225
|
|
|
223
226
|
const srcPath = path.join(currentSrc, item);
|
|
227
|
+
if (ignoredFiles.includes(srcPath)) continue;
|
|
228
|
+
|
|
224
229
|
const destPath = path.join(currentDest, item);
|
|
225
230
|
const stat = fs.statSync(srcPath);
|
|
226
231
|
|
|
@@ -232,7 +237,6 @@ function copyAppAssets(src, dest) {
|
|
|
232
237
|
fs.mkdirSync(destPath, { recursive: true });
|
|
233
238
|
copyRecursive(srcPath, destPath);
|
|
234
239
|
} else {
|
|
235
|
-
if (item.endsWith(".js")) continue;
|
|
236
240
|
fs.copyFileSync(srcPath, destPath);
|
|
237
241
|
}
|
|
238
242
|
}
|
|
@@ -244,6 +248,14 @@ function copyAppAssets(src, dest) {
|
|
|
244
248
|
const { exec } = require("@yao-pkg/pkg");
|
|
245
249
|
|
|
246
250
|
async function compileWithPkg(bundledJsPath, targetPlatform, outputFolder, appName) {
|
|
251
|
+
|
|
252
|
+
if(process.argv.includes("--no-pkg")) {
|
|
253
|
+
const finalPath = path.join(outputFolder, "index.js");
|
|
254
|
+
fs.copyFileSync(bundledJsPath, finalPath);
|
|
255
|
+
success(`[Packager] Skipped pkg compilation. Copied bundled JavaScript to: ${finalPath}`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
247
259
|
info(`[Packager] Packaging application into a standalone binary...`);
|
|
248
260
|
|
|
249
261
|
let pkgTarget = "";
|
package/tray.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const { app } = require("./index");
|
|
2
|
+
const { Menu } = require("./menu");
|
|
3
|
+
|
|
4
|
+
let createdTray = false;
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
|
|
8
|
+
create(menu, title = "", icon = "") {
|
|
9
|
+
|
|
10
|
+
if(createdTray) {
|
|
11
|
+
console.warn("Tray already created. Use setMenu, setTitle, or setIcon to update the existing tray.");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
createdTray = true;
|
|
16
|
+
|
|
17
|
+
if(menu instanceof Menu) {
|
|
18
|
+
menu = menu.template
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const stripClick = (items) => {
|
|
22
|
+
if (!items) return null;
|
|
23
|
+
return items.map(i => {
|
|
24
|
+
const newItem = { ...i, click: undefined };
|
|
25
|
+
if (newItem.items) {
|
|
26
|
+
newItem.items = stripClick(newItem.items);
|
|
27
|
+
}
|
|
28
|
+
return newItem;
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
app.setTrayMenu(menu);
|
|
33
|
+
app.sendToNative("createTray", [JSON.stringify(stripClick(menu)), title, icon]);
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
setMenu(menu) {
|
|
37
|
+
if(menu instanceof Menu) {
|
|
38
|
+
menu = menu.template
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const stripClick = (items) => {
|
|
42
|
+
if (!items) return null;
|
|
43
|
+
return items.map(i => {
|
|
44
|
+
const newItem = { ...i, click: undefined };
|
|
45
|
+
if (newItem.items) {
|
|
46
|
+
newItem.items = stripClick(newItem.items);
|
|
47
|
+
}
|
|
48
|
+
return newItem;
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
app.setTrayMenu(menu);
|
|
53
|
+
app.sendToNative("createTray", [JSON.stringify(stripClick(menu)), "setMenu"]);
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
setTitle(title) {
|
|
57
|
+
app.sendToNative("createTray", [title, "setTitle"]);
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
setIcon(iconPath) {
|
|
61
|
+
app.sendToNative("createTray", [iconPath, "setIcon"]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|