Committing updates and pushed version
This commit is contained in:
		
							parent
							
								
									7087514df1
								
							
						
					
					
						commit
						a636515de8
					
				
							
								
								
									
										2
									
								
								github
									
									
									
									
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								github
									
									
									
									
									
								
							| @ -1 +1 @@ | |||||||
| Subproject commit 9c3cc6d05838a03a5827836b300f8bc8e71b26d2 | Subproject commit 7c087d46ad75ff641d5862a57ff13f3e860cc8a4 | ||||||
| @ -122,5 +122,5 @@ function deploy_client() { | |||||||
| #install_npm | #install_npm | ||||||
| #compile_scripts | #compile_scripts | ||||||
| #compile_native | #compile_native | ||||||
| package_client | #package_client | ||||||
| #deploy_client | deploy_client | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ import * as loader from "./../ui-loader"; | |||||||
| import * as url from "url"; | import * as url from "url"; | ||||||
| import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window"; | import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window"; | ||||||
| import {referenceApp, dereferenceApp} from "../AppInstance"; | import {referenceApp, dereferenceApp} from "../AppInstance"; | ||||||
|  | import {closeURLPreview, openURLPreview} from "../url-preview"; | ||||||
| 
 | 
 | ||||||
| // Keep a global reference of the window object, if you don't, the window will
 | // Keep a global reference of the window object, if you don't, the window will
 | ||||||
| // be closed automatically when the JavaScript object is garbage collected.
 | // be closed automatically when the JavaScript object is garbage collected.
 | ||||||
| @ -49,7 +50,7 @@ function spawnMainWindow(rendererEntryPoint: string) { | |||||||
| 
 | 
 | ||||||
|     mainWindow.on('closed', () => { |     mainWindow.on('closed', () => { | ||||||
|         app.releaseSingleInstanceLock(); |         app.releaseSingleInstanceLock(); | ||||||
|         require("../url-preview").close(); |         closeURLPreview(); | ||||||
|         mainWindow = null; |         mainWindow = null; | ||||||
| 
 | 
 | ||||||
|         dereferenceApp(); |         dereferenceApp(); | ||||||
| @ -94,8 +95,7 @@ function spawnMainWindow(rendererEntryPoint: string) { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             console.log("Got new window " + frameName); |             console.log("Got new window " + frameName); | ||||||
|             const url_preview = require("./url-preview"); |             openURLPreview(url_str).then(() => {}); | ||||||
|             url_preview.open_preview(url_str); |  | ||||||
|         } catch(error) { |         } catch(error) { | ||||||
|             console.error("Failed to open preview window for URL %s: %o", url_str, error); |             console.error("Failed to open preview window for URL %s: %o", url_str, error); | ||||||
|             dialog.showErrorBox("Failed to open preview", "Failed to open preview URL: " + url_str + "\nError: " + error); |             dialog.showErrorBox("Failed to open preview", "Failed to open preview URL: " + url_str + "\nError: " + error); | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import {ProxiedClass} from "../../shared/proxy/Definitions"; | |||||||
| import {BrowserWindow, dialog} from "electron"; | import {BrowserWindow, dialog} from "electron"; | ||||||
| import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window"; | import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window"; | ||||||
| import {Arguments, processArguments} from "../../shared/process-arguments"; | import {Arguments, processArguments} from "../../shared/process-arguments"; | ||||||
| import {open_preview} from "../url-preview"; | import {openURLPreview} from "../url-preview"; | ||||||
| import * as path from "path"; | import * as path from "path"; | ||||||
| 
 | 
 | ||||||
| class ProxyImplementation extends ProxiedClass<ExternalModal> implements ExternalModal { | class ProxyImplementation extends ProxiedClass<ExternalModal> implements ExternalModal { | ||||||
| @ -71,15 +71,16 @@ class ProxyImplementation extends ProxiedClass<ExternalModal> implements Externa | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 open_preview(url.toString()); |                 openURLPreview(url.toString()); | ||||||
|             } catch(error) { |             } catch(error) { | ||||||
|                 console.error("Failed to open preview window for URL %s: %o", url_str, error); |                 console.error("Failed to open preview window for URL %s: %o", url_str, error); | ||||||
|                 dialog.showErrorBox("Failed to open preview", "Failed to open preview URL: " + url_str + "\nError: " + error); |                 dialog.showErrorBox("Failed to open preview", "Failed to open preview URL: " + url_str + "\nError: " + error); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         if(processArguments.has_flag(Arguments.DEV_TOOLS)) |         if(processArguments.has_flag(Arguments.DEV_TOOLS)) { | ||||||
|             this.windowInstance.webContents.openDevTools(); |             this.windowInstance.webContents.openDevTools(); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             await this.windowInstance.loadURL(url); |             await this.windowInstance.loadURL(url); | ||||||
|  | |||||||
| @ -1,34 +1,26 @@ | |||||||
| import * as electron from "electron"; | import * as electron from "electron"; | ||||||
| import ipcMain = electron.ipcMain; | import ipcMain = electron.ipcMain; | ||||||
| import BrowserWindow = electron.BrowserWindow; | import BrowserWindow = electron.BrowserWindow; | ||||||
|  | import {NativeMenuBarEntry} from "../../shared/MenuBarDefinitions"; | ||||||
|  | import {Menu, MenuItemConstructorOptions} from "electron"; | ||||||
| 
 | 
 | ||||||
| ipcMain.on('top-menu', (event, menu_template: electron.MenuItemConstructorOptions[]) => { | ipcMain.on("menu-bar", (event, menuBar: NativeMenuBarEntry[]) => { | ||||||
|     const window = BrowserWindow.fromWebContents(event.sender); |     const window = BrowserWindow.fromWebContents(event.sender); | ||||||
| 
 | 
 | ||||||
|     const process_template = (item: electron.MenuItemConstructorOptions) => { |     try { | ||||||
|         if(typeof(item.icon) === "string" && item.icon.startsWith("data:")) |         const processEntry = (entry: NativeMenuBarEntry): MenuItemConstructorOptions => { | ||||||
|             item.icon = electron.nativeImage.createFromDataURL(item.icon); |             return { | ||||||
| 
 |                 type: entry.type === "separator" ? "separator" : entry.children?.length ? "submenu" : "normal", | ||||||
|         item.click = () => window.webContents.send('top-menu', item.id); |                 label: entry.label, | ||||||
|         for(const i of item.submenu as electron.MenuItemConstructorOptions[] || []) { |                 icon: entry.icon ? electron.nativeImage.createFromDataURL(entry.icon).resize({ height: 16, width: 16 }) : undefined, | ||||||
|             process_template(i); |                 enabled: !entry.disabled, | ||||||
|  |                 click: entry.uniqueId && (() => event.sender.send("menu-bar", "item-click", entry.uniqueId)), | ||||||
|  |                 submenu: entry.children?.map(processEntry) | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|     for(const m of menu_template) |         window.setMenu(Menu.buildFromTemplate(menuBar.map(processEntry))); | ||||||
|         process_template(m); |  | ||||||
| 
 |  | ||||||
|     try { |  | ||||||
|         const menu = new electron.Menu(); |  | ||||||
|         for(const m of menu_template) { |  | ||||||
|             try { |  | ||||||
|                 menu.append(new electron.MenuItem(m)); |  | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|                 console.error("Failed to build menu entry: %o\nSource: %o", error, m); |         console.error("failed to set menu bar for %s: %o", window.getTitle(), error); | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         window.setMenu(menu_template.length == 0 ? undefined : menu); |  | ||||||
|     } catch(error) { |  | ||||||
|         console.error("Failed to set window menu: %o", error); |  | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
| @ -5,7 +5,7 @@ import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window"; | |||||||
| let global_window: electron.BrowserWindow; | let global_window: electron.BrowserWindow; | ||||||
| let global_window_promise: Promise<void>; | let global_window_promise: Promise<void>; | ||||||
| 
 | 
 | ||||||
| export async function close() { | export async function closeURLPreview() { | ||||||
|     while(global_window_promise) { |     while(global_window_promise) { | ||||||
|         try { |         try { | ||||||
|             await global_window_promise; |             await global_window_promise; | ||||||
| @ -20,13 +20,14 @@ export async function close() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function open_preview(url: string) { | export async function openURLPreview(url: string) { | ||||||
|     while(global_window_promise) { |     while(global_window_promise) { | ||||||
|         try { |         try { | ||||||
|             await global_window_promise; |             await global_window_promise; | ||||||
|             break; |             break; | ||||||
|         } catch(error) {} /* error will be already logged */ |         } catch(error) {} /* error will be already logged */ | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     if(!global_window) { |     if(!global_window) { | ||||||
|         global_window_promise = (async () => { |         global_window_promise = (async () => { | ||||||
|             global_window = new electron.BrowserWindow({ |             global_window = new electron.BrowserWindow({ | ||||||
|  | |||||||
| @ -1,36 +1,80 @@ | |||||||
| import {ContextMenuEntry, ContextMenuFactory, setGlobalContextMenuFactory} from "tc-shared/ui/ContextMenu"; | import {ContextMenuEntry, ContextMenuFactory, setGlobalContextMenuFactory} from "tc-shared/ui/ContextMenu"; | ||||||
| import * as electron from "electron"; | import * as electron from "electron"; | ||||||
| import {MenuItemConstructorOptions} from "electron"; | import {MenuItemConstructorOptions} from "electron"; | ||||||
| import {clientIconClassToImage} from "./IconHelper"; | import {clientIconClassToImage, remoteIconDatafier, RemoteIconWrapper} from "./IconHelper"; | ||||||
|  | import {getIconManager, RemoteIconInfo} from "tc-shared/file/Icons"; | ||||||
| const {Menu} = electron.remote; | const {Menu} = electron.remote; | ||||||
| 
 | 
 | ||||||
| let currentMenu: electron.Menu; | let currentMenu: ContextMenuInstance; | ||||||
|  | class ContextMenuInstance { | ||||||
|  |     private readonly closeCallback: () => void | undefined; | ||||||
|  |     private readonly menuOptions: MenuItemConstructorOptions[]; | ||||||
|  |     private currentMenu: electron.Menu; | ||||||
|  | 
 | ||||||
|  |     private wrappedIcons: RemoteIconWrapper[] = []; | ||||||
|  |     private wrappedIconListeners: (() => void)[] = []; | ||||||
|  | 
 | ||||||
|  |     constructor(entries: ContextMenuEntry[], closeCallback: () => void | undefined) { | ||||||
|  |         this.closeCallback = closeCallback; | ||||||
|  |         this.menuOptions = entries.map(e => this.wrapEntry(e)).filter(e => !!e); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     destroy() { | ||||||
|  |         this.currentMenu?.closePopup(); | ||||||
|  |         this.currentMenu = undefined; | ||||||
|  | 
 | ||||||
|  |         this.wrappedIconListeners.forEach(callback => callback()); | ||||||
|  |         this.wrappedIcons.forEach(icon => remoteIconDatafier.unrefIcon(icon)); | ||||||
|  | 
 | ||||||
|  |         this.wrappedIcons = []; | ||||||
|  |         this.wrappedIconListeners = []; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     spawn(pageX: number, pageY: number) { | ||||||
|  |         this.currentMenu = Menu.buildFromTemplate(this.menuOptions); | ||||||
|  |         this.currentMenu.popup({ | ||||||
|  |             callback: () => { | ||||||
|  |                 if(this.closeCallback) { | ||||||
|  |                     this.closeCallback(); | ||||||
|  |                 } | ||||||
|  |                 currentMenu = undefined; | ||||||
|  |             }, | ||||||
|  |             x: pageX, | ||||||
|  |             y: pageY, | ||||||
|  |             window: electron.remote.BrowserWindow.getFocusedWindow() | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private wrapEntry(entry: ContextMenuEntry) : MenuItemConstructorOptions { | ||||||
|  |         if(typeof entry.visible === "boolean" && !entry.visible) { return undefined; } | ||||||
|  | 
 | ||||||
|  |         let options: MenuItemConstructorOptions; | ||||||
|  |         let icon: string | RemoteIconInfo | undefined; | ||||||
| 
 | 
 | ||||||
| function mapMenuEntry(entry: ContextMenuEntry) : MenuItemConstructorOptions { |  | ||||||
|         switch (entry.type) { |         switch (entry.type) { | ||||||
|             case "normal": |             case "normal": | ||||||
|             return { |                 icon = entry.icon; | ||||||
|                 type: "normal", |                 options = { | ||||||
|  |                     type: entry.subMenu?.length ? "submenu" : "normal", | ||||||
|                     label: typeof entry.label === "string" ? entry.label : entry.label.text, |                     label: typeof entry.label === "string" ? entry.label : entry.label.text, | ||||||
|                     enabled: entry.enabled, |                     enabled: entry.enabled, | ||||||
|                 visible: entry.visible, |  | ||||||
|                     click: entry.click, |                     click: entry.click, | ||||||
|                 icon: typeof entry.icon === "string" ? clientIconClassToImage(entry.icon) : undefined, |  | ||||||
|                     id: entry.uniqueId, |                     id: entry.uniqueId, | ||||||
|                 submenu: entry.subMenu ? entry.subMenu.map(mapMenuEntry).filter(e => !!e) : undefined |                     submenu: entry.subMenu?.length ? entry.subMenu.map(e => this.wrapEntry(e)).filter(e => !!e) : undefined | ||||||
|                 }; |                 }; | ||||||
|  |                 break; | ||||||
| 
 | 
 | ||||||
|             case "checkbox": |             case "checkbox": | ||||||
|             return { |                 icon = entry.icon; | ||||||
|                 type: "normal", |                 options = { | ||||||
|  |                     type: "checkbox", | ||||||
|                     label: typeof entry.label === "string" ? entry.label : entry.label.text, |                     label: typeof entry.label === "string" ? entry.label : entry.label.text, | ||||||
|                     enabled: entry.enabled, |                     enabled: entry.enabled, | ||||||
|                 visible: entry.visible, |  | ||||||
|                     click: entry.click, |                     click: entry.click, | ||||||
|                     id: entry.uniqueId, |                     id: entry.uniqueId, | ||||||
| 
 |  | ||||||
|                     checked: entry.checked |                     checked: entry.checked | ||||||
|                 }; |                 }; | ||||||
|  |                 break; | ||||||
| 
 | 
 | ||||||
|             case "separator": |             case "separator": | ||||||
|                 return { |                 return { | ||||||
| @ -40,25 +84,40 @@ function mapMenuEntry(entry: ContextMenuEntry) : MenuItemConstructorOptions { | |||||||
|             default: |             default: | ||||||
|                 return undefined; |                 return undefined; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         if(typeof icon === "object") { | ||||||
|  |             const remoteIcon = getIconManager().resolveIcon(icon.iconId, icon.serverUniqueId, icon.handlerId); | ||||||
|  |             const wrapped = remoteIconDatafier.resolveIcon(remoteIcon); | ||||||
|  |             remoteIconDatafier.unrefIcon(wrapped); | ||||||
|  | 
 | ||||||
|  |             /* | ||||||
|  |             // Sadly we can't update the icon on the fly, so we've to live with whatever we have
 | ||||||
|  |             this.wrappedIcons.push(wrapped); | ||||||
|  |             this.wrappedIconListeners.push(wrapped.onDataUrlChange(dataUrl => { | ||||||
|  |                 options.icon = electron.nativeImage.createFromDataURL(dataUrl); | ||||||
|  |             })); | ||||||
|  |             */ | ||||||
|  | 
 | ||||||
|  |             if(wrapped.getDataUrl()) { | ||||||
|  |                 options.icon = electron.remote.nativeImage.createFromDataURL(wrapped.getDataUrl()); | ||||||
|  |             } | ||||||
|  |         } else if(typeof icon === "string") { | ||||||
|  |             options.icon = clientIconClassToImage(icon); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return options; | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| setGlobalContextMenuFactory(new class implements ContextMenuFactory { | setGlobalContextMenuFactory(new class implements ContextMenuFactory { | ||||||
|     closeContextMenu() { |     closeContextMenu() { | ||||||
|         currentMenu?.closePopup(); |         currentMenu?.destroy(); | ||||||
|         currentMenu = undefined; |         currentMenu = undefined; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     spawnContextMenu(position: { pageX: number; pageY: number }, entries: ContextMenuEntry[], callbackClose?: () => void) { |     spawnContextMenu(position: { pageX: number; pageY: number }, entries: ContextMenuEntry[], callbackClose?: () => void) { | ||||||
|         this.closeContextMenu(); |         this.closeContextMenu(); | ||||||
|         currentMenu = Menu.buildFromTemplate(entries.map(mapMenuEntry).filter(e => !!e)); |         currentMenu = new ContextMenuInstance(entries, callbackClose); | ||||||
|         currentMenu.popup({ |         currentMenu.spawn(position.pageX, position.pageY); | ||||||
|             callback: () => { |  | ||||||
|                 callbackClose(); |  | ||||||
|                 currentMenu = undefined; |  | ||||||
|             }, |  | ||||||
|             x: position.pageX, |  | ||||||
|             y: position.pageY, |  | ||||||
|             window: electron.remote.BrowserWindow.getFocusedWindow() |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
| @ -6,9 +6,10 @@ import { | |||||||
|     spriteUrl as kClientSpriteUrl, |     spriteUrl as kClientSpriteUrl, | ||||||
|     spriteWidth as kClientSpriteWidth, |     spriteWidth as kClientSpriteWidth, | ||||||
|     spriteHeight as kClientSpriteHeight, |     spriteHeight as kClientSpriteHeight, | ||||||
|     spriteEntries as kClientSpriteEntries |     spriteEntries as kClientSpriteEntries, ClientIcon | ||||||
| } from "svg-sprites/client-icons"; | } from "svg-sprites/client-icons"; | ||||||
| import {NativeImage} from "electron"; | import {NativeImage} from "electron"; | ||||||
|  | import {RemoteIcon} from "tc-shared/file/Icons"; | ||||||
| 
 | 
 | ||||||
| let nativeSprite: NativeImage; | let nativeSprite: NativeImage; | ||||||
| 
 | 
 | ||||||
| @ -24,6 +25,134 @@ export function clientIconClassToImage(klass: string) : NativeImage { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export class RemoteIconDatafier { | ||||||
|  |     private cachedIcons: {[key: string]:{ refCount: number, icon: RemoteIconWrapper }} = {}; | ||||||
|  |     private cleanupTimer; | ||||||
|  | 
 | ||||||
|  |     constructor() { } | ||||||
|  | 
 | ||||||
|  |     destroy() { | ||||||
|  |         clearTimeout(this.cleanupTimer); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     resolveIcon(icon: RemoteIcon) : RemoteIconWrapper { | ||||||
|  |         const uniqueId = icon.iconId + "-" + icon.serverUniqueId; | ||||||
|  |         if(!this.cachedIcons[uniqueId]) { | ||||||
|  |             this.cachedIcons[uniqueId] = { | ||||||
|  |                 refCount: 0, | ||||||
|  |                 icon: new RemoteIconWrapper(uniqueId, icon) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const cache = this.cachedIcons[uniqueId]; | ||||||
|  |         cache.refCount++; | ||||||
|  |         return cache.icon; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     unrefIcon(icon: RemoteIconWrapper) { | ||||||
|  |         const cache = this.cachedIcons[icon.uniqueId]; | ||||||
|  |         if(!cache) { return; } | ||||||
|  | 
 | ||||||
|  |         cache.refCount--; | ||||||
|  |         if(cache.refCount <= 0) { | ||||||
|  |             if(this.cleanupTimer) { | ||||||
|  |                 clearTimeout(this.cleanupTimer); | ||||||
|  |             } | ||||||
|  |             this.cleanupTimer = setTimeout(() => this.cleanupIcons(), 10 * 1000); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private cleanupIcons() { | ||||||
|  |         this.cleanupTimer = undefined; | ||||||
|  |         for(const key of Object.keys(this.cachedIcons)) { | ||||||
|  |             if(this.cachedIcons[key].refCount <= 0) { | ||||||
|  |                 this.cachedIcons[key].icon.destroy(); | ||||||
|  |                 delete this.cachedIcons[key]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | export const remoteIconDatafier = new RemoteIconDatafier(); | ||||||
|  | 
 | ||||||
|  | export class RemoteIconWrapper { | ||||||
|  |     readonly callbackUpdated: ((newUrl: string) => void)[] = []; | ||||||
|  |     readonly uniqueId: string; | ||||||
|  | 
 | ||||||
|  |     private readonly icon: RemoteIcon; | ||||||
|  |     private readonly callbackStateChanged: () => void; | ||||||
|  |     private dataUrl: string | undefined; | ||||||
|  |     private currentImageUrl: string; | ||||||
|  | 
 | ||||||
|  |     constructor(uniqueId: string, icon: RemoteIcon) { | ||||||
|  |         this.icon = icon; | ||||||
|  |         this.uniqueId = uniqueId; | ||||||
|  |         this.callbackStateChanged = this.handleIconStateChanged.bind(this); | ||||||
|  | 
 | ||||||
|  |         this.icon.events.on("notify_state_changed", this.callbackStateChanged); | ||||||
|  |         this.handleIconStateChanged(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     destroy() { | ||||||
|  |         this.icon.events.off("notify_state_changed", this.callbackStateChanged); | ||||||
|  |         this.currentImageUrl = undefined; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getDataUrl() : string | undefined { return this.dataUrl; } | ||||||
|  | 
 | ||||||
|  |     onDataUrlChange(callback: (newUrl: string) => void) : () => void { | ||||||
|  |         this.callbackUpdated.push(callback); | ||||||
|  |         return () => { | ||||||
|  |             const index = this.callbackUpdated.indexOf(callback); | ||||||
|  |             if(index !== -1) { this.callbackUpdated.splice(index, 1); } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private handleIconStateChanged() { | ||||||
|  |         if(this.icon.getState() === "loaded") { | ||||||
|  |             const imageUrl = this.icon.getImageUrl(); | ||||||
|  |             this.currentImageUrl = this.icon.getImageUrl(); | ||||||
|  | 
 | ||||||
|  |             const image = new Image(); | ||||||
|  |             image.src = imageUrl; | ||||||
|  | 
 | ||||||
|  |             new Promise((resolve, reject) => { | ||||||
|  |                 image.onload = resolve; | ||||||
|  |                 image.onerror = reject; | ||||||
|  |             }).then(() => { | ||||||
|  |                 if(this.currentImageUrl !== imageUrl) { return; } | ||||||
|  | 
 | ||||||
|  |                 const canvas = document.createElement("canvas"); | ||||||
|  |                 if(image.naturalWidth > 1000 || image.naturalHeight > 1000) { | ||||||
|  |                     throw "image dimensions are too large"; | ||||||
|  |                 } else { | ||||||
|  |                     canvas.width = image.naturalWidth; | ||||||
|  |                     canvas.height = image.naturalHeight; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 canvas.getContext("2d").drawImage(image, 0, 0); | ||||||
|  | 
 | ||||||
|  |                 this.setDataUrl(imageUrl, canvas.toDataURL()); | ||||||
|  |             }).catch(() => { | ||||||
|  |                 this.setDataUrl(imageUrl, clientIconClassToImage(ClientIcon.Error).toDataURL()); | ||||||
|  |             }); | ||||||
|  |         } else if(this.icon.getState() === "error") { | ||||||
|  |             this.setDataUrl(undefined, clientIconClassToImage(ClientIcon.Error).toDataURL()); | ||||||
|  |         } else { | ||||||
|  |             this.setDataUrl(undefined, undefined); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private setDataUrl(sourceImageUrl: string | undefined, dataUrl: string) { | ||||||
|  |         if(sourceImageUrl && this.currentImageUrl !== sourceImageUrl) { return; } | ||||||
|  |         this.currentImageUrl = undefined; /* no image is loading any more */ | ||||||
|  | 
 | ||||||
|  |         if(this.dataUrl === dataUrl) { return; } | ||||||
|  | 
 | ||||||
|  |         this.dataUrl = dataUrl; | ||||||
|  |         this.callbackUpdated.forEach(callback => callback(dataUrl)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { | loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { | ||||||
|     priority: 100, |     priority: 100, | ||||||
|     name: "native icon sprite loader", |     name: "native icon sprite loader", | ||||||
|  | |||||||
							
								
								
									
										99
									
								
								modules/renderer/MenuBar.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								modules/renderer/MenuBar.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | |||||||
|  | import * as electron from "electron"; | ||||||
|  | import {MenuBarDriver, MenuBarEntry} from "tc-shared/ui/frames/menu-bar"; | ||||||
|  | import {IpcRendererEvent} from "electron"; | ||||||
|  | import {getIconManager} from "tc-shared/file/Icons"; | ||||||
|  | import {clientIconClassToImage, remoteIconDatafier, RemoteIconWrapper} from "./IconHelper"; | ||||||
|  | import {NativeMenuBarEntry} from "../shared/MenuBarDefinitions"; | ||||||
|  | 
 | ||||||
|  | let uniqueEntryIdIndex = 0; | ||||||
|  | export class NativeMenuBarDriver implements MenuBarDriver { | ||||||
|  |     private readonly ipcChannelListener; | ||||||
|  | 
 | ||||||
|  |     private menuEntries: NativeMenuBarEntry[] = []; | ||||||
|  |     private remoteIconReferences: RemoteIconWrapper[] = []; | ||||||
|  |     private remoteIconListeners: (() => void)[] = []; | ||||||
|  |     private callbacks: {[key: string]: () => void} = {}; | ||||||
|  | 
 | ||||||
|  |     constructor() { | ||||||
|  |         this.ipcChannelListener = this.handleMenuBarEvent.bind(this); | ||||||
|  | 
 | ||||||
|  |         electron.ipcRenderer.on("menu-bar", this.ipcChannelListener); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     destroy() { | ||||||
|  |         electron.ipcRenderer.off("menu-bar", this.ipcChannelListener); | ||||||
|  |         this.internalClearEntries(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private internalClearEntries() { | ||||||
|  |         this.callbacks = {}; | ||||||
|  |         this.menuEntries = []; | ||||||
|  | 
 | ||||||
|  |         this.remoteIconListeners.forEach(callback => callback()); | ||||||
|  |         this.remoteIconListeners = []; | ||||||
|  | 
 | ||||||
|  |         this.remoteIconReferences.forEach(icon => remoteIconDatafier.unrefIcon(icon)); | ||||||
|  |         this.remoteIconReferences = []; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     clearEntries() { | ||||||
|  |         this.internalClearEntries() | ||||||
|  |         electron.ipcRenderer.send("menu-bar", []); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     setEntries(entries: MenuBarEntry[]) { | ||||||
|  |         this.internalClearEntries(); | ||||||
|  |         this.menuEntries = entries.map(e => this.wrapEntry(e)).filter(e => !!e); | ||||||
|  |         electron.ipcRenderer.send("menu-bar", this.menuEntries); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private wrapEntry(entry: MenuBarEntry) : NativeMenuBarEntry { | ||||||
|  |         if(entry.type === "separator") { | ||||||
|  |             return { type: "separator", uniqueId: entry.uniqueId || "item-" + (++uniqueEntryIdIndex) }; | ||||||
|  |         } else if(entry.type === "normal") { | ||||||
|  |             if(typeof entry.visible === "boolean" && !entry.visible) { | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             let result = { | ||||||
|  |                 type: "normal", | ||||||
|  |                 uniqueId: entry.uniqueId || "item-" + (++uniqueEntryIdIndex), | ||||||
|  |                 label: entry.label, | ||||||
|  |                 disabled: entry.disabled, | ||||||
|  |                 children: entry.children?.map(e => this.wrapEntry(e)).filter(e => !!e) | ||||||
|  |             } as NativeMenuBarEntry; | ||||||
|  | 
 | ||||||
|  |             if(entry.click) { | ||||||
|  |                 this.callbacks[result.uniqueId] = entry.click; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(typeof entry.icon === "object") { | ||||||
|  |                 /* we've a remote icon */ | ||||||
|  |                 const remoteIcon = getIconManager().resolveIcon(entry.icon.iconId, entry.icon.serverUniqueId, entry.icon.handlerId); | ||||||
|  |                 const wrapped = remoteIconDatafier.resolveIcon(remoteIcon); | ||||||
|  |                 this.remoteIconReferences.push(wrapped); | ||||||
|  | 
 | ||||||
|  |                 wrapped.onDataUrlChange(url => { | ||||||
|  |                     result.icon = url; | ||||||
|  |                     electron.ipcRenderer.send("menu-bar", this.menuEntries); | ||||||
|  |                 }); | ||||||
|  |                 result.icon = wrapped.getDataUrl(); | ||||||
|  |             } else if(typeof entry.icon === "string") { | ||||||
|  |                 result.icon = clientIconClassToImage(entry.icon).toDataURL(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return result; | ||||||
|  |         } else { | ||||||
|  |             return undefined; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private handleMenuBarEvent(_event: IpcRendererEvent, eventType: string, ...args) { | ||||||
|  |         if(eventType === "item-click") { | ||||||
|  |             const callback = this.callbacks[args[0]]; | ||||||
|  |             if(typeof callback === "function") { | ||||||
|  |                 callback(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,246 +0,0 @@ | |||||||
| import {clientIconClassToImage} from "./IconHelper"; |  | ||||||
| import * as electron from "electron"; |  | ||||||
| import * as mbar from "tc-shared/ui/frames/MenuBar"; |  | ||||||
| import {Arguments, processArguments} from "../shared/process-arguments"; |  | ||||||
| 
 |  | ||||||
| import ipcRenderer = electron.ipcRenderer; |  | ||||||
| import {LocalIcon} from "tc-shared/file/Icons"; |  | ||||||
| namespace native { |  | ||||||
|     import ipcRenderer = electron.ipcRenderer; |  | ||||||
|     let _item_index = 1; |  | ||||||
| 
 |  | ||||||
|     abstract class NativeMenuBase { |  | ||||||
|         protected _handle: NativeMenuBar; |  | ||||||
|         protected _click: () => any; |  | ||||||
|         id: string; |  | ||||||
| 
 |  | ||||||
|         protected constructor(handle: NativeMenuBar, id?: string) { |  | ||||||
|             this._handle = handle; |  | ||||||
|             this.id = id || ("item_" + (_item_index++)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         abstract build() : electron.MenuItemConstructorOptions; |  | ||||||
|         abstract items(): (mbar.MenuItem | mbar.HRItem)[]; |  | ||||||
| 
 |  | ||||||
|         trigger_click() { |  | ||||||
|             if(this._click) |  | ||||||
|                 this._click(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     class NativeMenuItem extends NativeMenuBase implements mbar.MenuItem { |  | ||||||
|         private _items: (NativeMenuItem | NativeHrItem)[] = []; |  | ||||||
|         private _label: string; |  | ||||||
|         private _enabled: boolean = true; |  | ||||||
|         private _visible: boolean = true; |  | ||||||
| 
 |  | ||||||
|         private _icon_data: string; |  | ||||||
| 
 |  | ||||||
|         constructor(handle: NativeMenuBar) { |  | ||||||
|             super(handle); |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         append_hr(): mbar.HRItem { |  | ||||||
|             const item = new NativeHrItem(this._handle); |  | ||||||
|             this._items.push(item); |  | ||||||
|             return item; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         append_item(label: string): mbar.MenuItem { |  | ||||||
|             const item = new NativeMenuItem(this._handle); |  | ||||||
|             item.label(label); |  | ||||||
|             this._items.push(item); |  | ||||||
|             return item; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         click(callback: () => any): this { |  | ||||||
|             this._click = callback; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         delete_item(item: mbar.MenuItem | mbar.HRItem) { |  | ||||||
|             const i_index = this._items.indexOf(item as any); |  | ||||||
|             if(i_index < 0) return; |  | ||||||
|             this._items.splice(i_index, 1); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         disabled(value?: boolean): boolean { |  | ||||||
|             if(typeof(value) === "boolean") |  | ||||||
|                 this._enabled = !value; |  | ||||||
|             return !this._enabled; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         icon(klass?: string | Promise<LocalIcon> | LocalIcon): string { |  | ||||||
|             if(typeof(klass) === "string") { |  | ||||||
|                 const buffer = clientIconClassToImage(klass); |  | ||||||
|                 if(buffer) |  | ||||||
|                     this._icon_data = buffer.toDataURL(); |  | ||||||
|             } |  | ||||||
|             return ""; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         items(): (mbar.MenuItem | mbar.HRItem)[] { |  | ||||||
|             return this._items; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         label(value?: string): string { |  | ||||||
|             if(typeof(value) === "string") |  | ||||||
|                 this._label = value; |  | ||||||
|             return this._label; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         visible(value?: boolean): boolean { |  | ||||||
|             if(typeof(value) === "boolean") |  | ||||||
|                 this._visible = value; |  | ||||||
|             return this._visible; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         build(): Electron.MenuItemConstructorOptions { |  | ||||||
|             return { |  | ||||||
|                 id: this.id, |  | ||||||
| 
 |  | ||||||
|                 label: this._label || "", |  | ||||||
| 
 |  | ||||||
|                 submenu: this._items.length > 0 ? this._items.map(e => e.build()) : undefined, |  | ||||||
|                 enabled: this._enabled, |  | ||||||
|                 visible: this._visible, |  | ||||||
| 
 |  | ||||||
|                 icon: this._icon_data |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     class NativeHrItem extends NativeMenuBase implements mbar.HRItem { |  | ||||||
|         constructor(handle: NativeMenuBar) { |  | ||||||
|             super(handle); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         build(): Electron.MenuItemConstructorOptions { |  | ||||||
|             return { |  | ||||||
|                 type: 'separator', |  | ||||||
|                 id: this.id |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         items(): (mbar.MenuItem | mbar.HRItem)[] { |  | ||||||
|             return []; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function is_similar_deep(a, b) { |  | ||||||
|         if(typeof(a) !== typeof(b)) |  | ||||||
|             return false; |  | ||||||
|         if(typeof(a) !== "object") |  | ||||||
|             return a === b; |  | ||||||
| 
 |  | ||||||
|         const aProps = Object.keys(a); |  | ||||||
|         const bProps = Object.keys(b); |  | ||||||
| 
 |  | ||||||
|         if (aProps.length != bProps.length) |  | ||||||
|             return false; |  | ||||||
| 
 |  | ||||||
|         for (let i = 0; i < aProps.length; i++) { |  | ||||||
|             const propName = aProps[i]; |  | ||||||
| 
 |  | ||||||
|             if(!is_similar_deep(a[propName], b[propName])) |  | ||||||
|                 return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     export class NativeMenuBar implements mbar.MenuBarDriver { |  | ||||||
|         private static _instance: NativeMenuBar; |  | ||||||
| 
 |  | ||||||
|         private menu: electron.Menu; |  | ||||||
|         private _items: NativeMenuItem[] = []; |  | ||||||
|         private _current_menu: electron.MenuItemConstructorOptions[]; |  | ||||||
| 
 |  | ||||||
|         public static instance() : NativeMenuBar { |  | ||||||
|             if(!this._instance) |  | ||||||
|                 this._instance = new NativeMenuBar(); |  | ||||||
|             return this._instance; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         append_item(label: string): mbar.MenuItem { |  | ||||||
|             const item = new NativeMenuItem(this); |  | ||||||
|             item.label(label); |  | ||||||
|             this._items.push(item); |  | ||||||
|             return item; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         delete_item(item: mbar.MenuItem) { |  | ||||||
|             const i_index = this._items.indexOf(item as any); |  | ||||||
|             if(i_index < 0) return; |  | ||||||
|             this._items.splice(i_index, 1); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         flush_changes() { |  | ||||||
|             const target_menu = this.build_menu(); |  | ||||||
|             if(is_similar_deep(target_menu, this._current_menu)) |  | ||||||
|                 return; |  | ||||||
| 
 |  | ||||||
|             this._current_menu = target_menu; |  | ||||||
|             ipcRenderer.send('top-menu', target_menu); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private build_menu() : electron.MenuItemConstructorOptions[] { |  | ||||||
|             return this._items.map(e => e.build()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         items(): mbar.MenuItem[] { |  | ||||||
|             return this._items; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         initialize() { |  | ||||||
|             this.menu = new electron.remote.Menu(); |  | ||||||
|             ipcRenderer.on('top-menu', (event, clicked_item) => { |  | ||||||
|                 console.log("Item %o clicked", clicked_item); |  | ||||||
|                 const check_item = (item: NativeMenuBase) => { |  | ||||||
|                     if(item.id == clicked_item) { |  | ||||||
|                         item.trigger_click(); |  | ||||||
|                         return true; |  | ||||||
|                     } |  | ||||||
|                     for(const child of item.items()) |  | ||||||
|                         if(check_item(child as NativeMenuBase)) |  | ||||||
|                             return true; |  | ||||||
|                 }; |  | ||||||
| 
 |  | ||||||
|                 for(const item of this._items) |  | ||||||
|                     if(check_item(item)) |  | ||||||
|                         return; |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| mbar.set_driver(native.NativeMenuBar.instance()); |  | ||||||
| // @ts-ignore
 |  | ||||||
| mbar.native_actions = { |  | ||||||
|     open_change_log() { |  | ||||||
|         call_basic_action("open-changelog"); |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     check_native_update() { |  | ||||||
|         call_basic_action("check-native-update"); |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     quit() { |  | ||||||
|         call_basic_action("quit"); |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     open_dev_tools() { |  | ||||||
|         call_basic_action("open-dev-tools"); |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     reload_page() { |  | ||||||
|         call_basic_action("reload-window") |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     show_dev_tools() { return processArguments.has_flag(Arguments.DEV_TOOLS); } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| const call_basic_action = (name: string, ...args: any[]) => ipcRenderer.send('basic-action', name, ...args); |  | ||||||
| @ -16,6 +16,7 @@ import {NativeFilter, NStateFilter, NThresholdFilter, NVoiceLevelFilter} from ". | |||||||
| import {IDevice} from "tc-shared/audio/recorder"; | import {IDevice} from "tc-shared/audio/recorder"; | ||||||
| import {LogCategory, logWarn} from "tc-shared/log"; | import {LogCategory, logWarn} from "tc-shared/log"; | ||||||
| import NativeFilterMode = audio.record.FilterMode; | import NativeFilterMode = audio.record.FilterMode; | ||||||
|  | import {Settings, settings} from "tc-shared/settings"; | ||||||
| 
 | 
 | ||||||
| export class NativeInput implements AbstractInput { | export class NativeInput implements AbstractInput { | ||||||
|     static readonly instances = [] as NativeInput[]; |     static readonly instances = [] as NativeInput[]; | ||||||
| @ -37,8 +38,7 @@ export class NativeInput implements AbstractInput { | |||||||
|         this.nativeHandle = audio.record.create_recorder(); |         this.nativeHandle = audio.record.create_recorder(); | ||||||
| 
 | 
 | ||||||
|         this.nativeConsumer = this.nativeHandle.create_consumer(); |         this.nativeConsumer = this.nativeHandle.create_consumer(); | ||||||
|         this.nativeConsumer.toggle_rnnoise(true); |         this.nativeConsumer.toggle_rnnoise(settings.static_global(Settings.KEY_RNNOISE_FILTER)); | ||||||
|         (window as any).consumer = this.nativeConsumer; /* FIXME! */ |  | ||||||
| 
 | 
 | ||||||
|         this.nativeConsumer.callback_ended = () => { |         this.nativeConsumer.callback_ended = () => { | ||||||
|             this.filtered = true; |             this.filtered = true; | ||||||
| @ -245,7 +245,7 @@ export class NativeLevelMeter implements LevelMeter { | |||||||
|             this.nativeRecorder = audio.record.create_recorder(); |             this.nativeRecorder = audio.record.create_recorder(); | ||||||
|             this.nativeConsumer = this.nativeRecorder.create_consumer(); |             this.nativeConsumer = this.nativeRecorder.create_consumer(); | ||||||
| 
 | 
 | ||||||
|             this.nativeConsumer.toggle_rnnoise(true); /* FIXME! */ |             this.nativeConsumer.toggle_rnnoise(settings.static_global(Settings.KEY_RNNOISE_FILTER)); | ||||||
| 
 | 
 | ||||||
|             this.nativeFilter = this.nativeConsumer.create_filter_threshold(.5); |             this.nativeFilter = this.nativeConsumer.create_filter_threshold(.5); | ||||||
|             this.nativeFilter.set_attack_smooth(.75); |             this.nativeFilter.set_attack_smooth(.75); | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								modules/renderer/backend-impl/Backend.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								modules/renderer/backend-impl/Backend.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | import {NativeClientBackend} from "tc-shared/backend/NativeClient"; | ||||||
|  | import {ipcRenderer} from "electron"; | ||||||
|  | import {Arguments, processArguments} from "../../shared/process-arguments"; | ||||||
|  | 
 | ||||||
|  | const call_basic_action = (name: string, ...args: any[]) => ipcRenderer.send('basic-action', name, ...args); | ||||||
|  | export class NativeClientBackendImpl implements NativeClientBackend { | ||||||
|  |     openChangeLog(): void { | ||||||
|  |         call_basic_action("open-changelog"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     openClientUpdater(): void { | ||||||
|  |         call_basic_action("check-native-update"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     openDeveloperTools(): void { | ||||||
|  |         call_basic_action("open-dev-tools"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     quit(): void { | ||||||
|  |         call_basic_action("quit"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     reloadWindow(): void { | ||||||
|  |         call_basic_action("reload-window") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     showDeveloperOptions(): boolean { | ||||||
|  |         return processArguments.has_flag(Arguments.DEV_TOOLS); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								modules/renderer/hooks/Backend.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								modules/renderer/hooks/Backend.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | import {setBackend} from "tc-shared/backend"; | ||||||
|  | import {NativeClientBackendImpl} from "../backend-impl/Backend"; | ||||||
|  | 
 | ||||||
|  | setBackend(new NativeClientBackendImpl()); | ||||||
							
								
								
									
										4
									
								
								modules/renderer/hooks/MenuBar.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								modules/renderer/hooks/MenuBar.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | import {setMenuBarDriver} from "tc-shared/ui/frames/menu-bar"; | ||||||
|  | import {NativeMenuBarDriver} from "../MenuBar"; | ||||||
|  | 
 | ||||||
|  | setMenuBarDriver(new NativeMenuBarDriver()); | ||||||
| @ -52,7 +52,7 @@ loader.register_task(loader.Stage.JAVASCRIPT, { | |||||||
|     priority: 80 |     priority: 80 | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| loader.register_task(loader.Stage.INITIALIZING, { | loader.register_task(loader.Stage.SETUP, { | ||||||
|     name: "teaclient initialize persistent storage", |     name: "teaclient initialize persistent storage", | ||||||
|     function: async () => { |     function: async () => { | ||||||
|         const storage = require("./PersistentLocalStorage"); |         const storage = require("./PersistentLocalStorage"); | ||||||
| @ -154,7 +154,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { | |||||||
|         /* all files which replaces a native driver */ |         /* all files which replaces a native driver */ | ||||||
|         try { |         try { | ||||||
|             await import("./version"); |             await import("./version"); | ||||||
|             await import("./MenuBarHandler"); |             await import("./MenuBar"); | ||||||
|             await import("./ContextMenu"); |             await import("./ContextMenu"); | ||||||
|             await import("./SingleInstanceHandler"); |             await import("./SingleInstanceHandler"); | ||||||
|             await import("./IconHelper"); |             await import("./IconHelper"); | ||||||
| @ -164,6 +164,8 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { | |||||||
|             await import("./hooks/ExternalModal"); |             await import("./hooks/ExternalModal"); | ||||||
|             await import("./hooks/ServerConnection"); |             await import("./hooks/ServerConnection"); | ||||||
|             await import("./hooks/ChangeLogClient"); |             await import("./hooks/ChangeLogClient"); | ||||||
|  |             await import("./hooks/Backend"); | ||||||
|  |             await import("./hooks/MenuBar"); | ||||||
| 
 | 
 | ||||||
|             await import("./UnloadHandler"); |             await import("./UnloadHandler"); | ||||||
|             await import("./WindowsTrayHandler"); |             await import("./WindowsTrayHandler"); | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								modules/shared/MenuBarDefinitions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								modules/shared/MenuBarDefinitions.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | import {NativeImage} from "electron"; | ||||||
|  | 
 | ||||||
|  | export interface NativeMenuBarEntry { | ||||||
|  |     uniqueId: string, | ||||||
|  |     type: "separator" | "normal", | ||||||
|  |     label?: string, | ||||||
|  |     icon?: string, | ||||||
|  |     disabled?: boolean, | ||||||
|  |     children?: NativeMenuBarEntry[] | ||||||
|  | } | ||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "TeaClient", |   "name": "TeaClient", | ||||||
|   "version": "1.4.12", |   "version": "1.4.13", | ||||||
|   "description": "", |   "description": "", | ||||||
|   "main": "main.js", |   "main": "main.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user