diff --git a/ChangeLog.md b/ChangeLog.md index d25ce67a..ab117a0c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,8 @@ - Fixed the bug that cause the file transfer to timeout if the user hasn't been quick enough to save his file - Fixed the emoji picker being cut off - Fixed the file transfer sidebar not working + - Instantly reflect changes to the server icon for the bookmark menu bar + - Fixed a bug which prevented the proper context menu appear within the bookmark manage modal * **26.05.21** - Fixed automated builds diff --git a/shared/js/connectionlog/History.ts b/shared/js/connectionlog/History.ts index 31e4ca85..0575c853 100644 --- a/shared/js/connectionlog/History.ts +++ b/shared/js/connectionlog/History.ts @@ -2,9 +2,10 @@ import {LogCategory, logError, logWarn} from "tc-shared/log"; import {tr, tra} from "tc-shared/i18n/localize"; import * as loader from "tc-loader"; import {Stage} from "tc-loader"; -import {ConnectionHandler} from "tc-shared/ConnectionHandler"; +import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler"; import {server_connections} from "tc-shared/ConnectionManager"; import {ServerProperties} from "tc-shared/tree/Server"; +import {Registry} from "tc-events"; export const kUnknownHistoryServerUniqueId = "unknown"; @@ -44,10 +45,17 @@ export type ConnectionHistoryServerInfo = { passwordProtected: boolean } +export interface ConnectionHistoryEvents { + notify_server_info_updated: { serverUniqueId: string, keys: (keyof ConnectionHistoryServerInfo)[] } +} + export class ConnectionHistory { + readonly events: Registry; private database: IDBDatabase; - constructor() { } + constructor() { + this.events = new Registry(); + } async initializeDatabase() { const openRequest = indexedDB.open("connection-log", 1); @@ -306,16 +314,29 @@ export class ConnectionHistory { return; } - await this.updateDatabaseServerInfo(serverUniqueId, databaseValue => { - databaseValue.name = info.name; - databaseValue.iconId = info.iconId; + const changes: (keyof ConnectionHistoryServerInfo)[] = []; + const updateValue = (databaseKey: string, infoKey: keyof ConnectionHistoryServerInfo) => { + if(databaseValue[databaseKey] === info[infoKey]) { + return; + } - databaseValue.clientsOnline = info.clientsOnline; - databaseValue.clientsMax = info.clientsMax; + databaseValue[databaseKey] = info[infoKey]; + changes.push(infoKey); + } - databaseValue.hostBannerUrl = info.hostBannerUrl - databaseValue.hostBannerMode = info.hostBannerMode; + updateValue("name", "name"); + updateValue("iconId", "iconId"); + + updateValue("clientsOnline", "clientsOnline"); + updateValue("clientsMax", "clientsMax"); + + updateValue("hostBannerUrl", "hostBannerUrl"); + updateValue("hostBannerMode", "hostBannerMode"); + + if(changes.length > 0) { + this.events.fire("notify_server_info_updated", { serverUniqueId: serverUniqueId, keys: changes }); + } }); } @@ -542,7 +563,15 @@ class ConnectionHistoryUpdateListener { } private registerConnectionHandler(handler: ConnectionHandler) { - handler.channelTree.server.events.on("notify_properties_updated", event => { + const events = this.listenerConnectionHandler[handler.handlerId] = []; + events.push(handler.channelTree.server.events.on("notify_properties_updated", event => { + switch(handler.connection_state) { + case ConnectionState.UNCONNECTED: + case ConnectionState.DISCONNECTING: + /* We don't want any changes here */ + return; + } + if("virtualserver_unique_identifier" in event.updated_properties) { if(handler.currentConnectId > 0) { this.history.updateConnectionServerUniqueId(handler.currentConnectId, event.server_properties.virtualserver_unique_identifier) @@ -573,7 +602,7 @@ class ConnectionHistoryUpdateListener { break; } } - }); + })); } } @@ -581,7 +610,7 @@ export let connectionHistory: ConnectionHistory; let historyInfoListener: ConnectionHistoryUpdateListener; loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { - priority: 0, + priority: 40, name: "Chat history setup", function: async () => { if(!('indexedDB' in window)) { diff --git a/shared/js/ui/AppController.ts b/shared/js/ui/AppController.ts index 8b2d9710..47571a49 100644 --- a/shared/js/ui/AppController.ts +++ b/shared/js/ui/AppController.ts @@ -127,7 +127,7 @@ export class AppController { } export let appViewController: AppController; -loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { +loader.register_task(Stage.LOADED, { name: "app view", function: async () => { appViewController = new AppController(); @@ -137,5 +137,5 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { (window as any).AppController = AppController; (window as any).appViewController = appViewController; }, - priority: 0 + priority: 100 }); diff --git a/shared/js/ui/frames/control-bar/Button.scss b/shared/js/ui/frames/control-bar/Button.scss index 861f3ba4..7a88c191 100644 --- a/shared/js/ui/frames/control-bar/Button.scss +++ b/shared/js/ui/frames/control-bar/Button.scss @@ -167,16 +167,6 @@ html:root { right: 0; } - .iconContainer { - margin-right: .25em; - - display: flex; - flex-direction: column; - - flex-shrink: 0; - flex-grow: 0; - } - .dropdownEntry { position: relative; @@ -305,4 +295,11 @@ html:root { transform: rotate(45deg); -webkit-transform: rotate(45deg); } +} + +.iconContainer { + margin-right: .25em; + + display: flex; + flex-direction: column; } \ No newline at end of file diff --git a/shared/js/ui/frames/control-bar/Controller.ts b/shared/js/ui/frames/control-bar/Controller.ts index cd873d88..a2f1cb8a 100644 --- a/shared/js/ui/frames/control-bar/Controller.ts +++ b/shared/js/ui/frames/control-bar/Controller.ts @@ -23,6 +23,8 @@ import {connectionHistory} from "tc-shared/connectionlog/History"; import {RemoteIconInfo} from "tc-shared/file/Icons"; import {spawnModalAddCurrentServerToBookmarks} from "tc-shared/ui/modal/bookmarks-add-server/Controller"; import {getAudioBackend, OutputDevice} from "tc-shared/audio/Player"; +import {ignorePromise} from "tc-shared/proto"; +import {LogCategory, logTrace} from "tc-shared/log"; class InfoController { private readonly mode: ControlBarMode; @@ -34,6 +36,8 @@ class InfoController { private handlerRegisteredEvents: (() => void)[] = []; private defaultRecorderListener: () => void; + private bookmarkServerUniqueIds: string[] = []; + constructor(events: Registry, mode: ControlBarMode) { this.events = events; this.mode = mode; @@ -61,6 +65,16 @@ class InfoController { this.sendVideoState("camera"); })); bookmarks.events.on(["notify_bookmark_edited", "notify_bookmark_created", "notify_bookmark_deleted", "notify_bookmarks_imported"], () => this.sendBookmarks()); + events.push(connectionHistory.events.on("notify_server_info_updated", event => { + if(this.bookmarkServerUniqueIds.indexOf(event.serverUniqueId) === -1) { + return; + } + + if(event.keys.indexOf("iconId") !== -1) { + /* An icon for a bookmark has changed. Send the full new list. */ + ignorePromise(this.sendBookmarks()); + } + })); events.push(getVideoDriver().getEvents().on("notify_device_list_changed", () => this.sendCameraList())); events.push(getRecorderBackend().getDeviceList().getEvents().on("notify_list_updated", () => this.sendMicrophoneList())); events.push(defaultRecorderEvents.on("notify_default_recorder_changed", () => { @@ -202,7 +216,9 @@ class InfoController { }); } + /* Note: This method might be executed concurrently */ public async sendBookmarks() { + this.bookmarkServerUniqueIds = []; const bookmarkList = bookmarks.getOrderedRegisteredBookmarks(); const parent: Bookmark[] = []; @@ -216,15 +232,17 @@ class InfoController { let icon: RemoteIconInfo; try { - const connectInfo = await connectionHistory.lastConnectInfo(bookmark.entry.serverAddress, "address"); + const connectInfo = await connectionHistory.lastConnectInfo(bookmark.entry.serverAddress, "address", true); if(connectInfo) { + this.bookmarkServerUniqueIds.push(connectInfo.serverUniqueId); const info = await connectionHistory.queryServerInfo(connectInfo.serverUniqueId); if(info && info.iconId > 0) { icon = { iconId: info.iconId, serverUniqueId: connectInfo.serverUniqueId }; } } - } catch (_) { - /* no need for any error handling */ + } catch (error) { + /* No need to warn in prod build */ + logTrace(LogCategory.BOOKMARKS, "Failed to query last connect info: %o", error); } parentList.push({ diff --git a/shared/js/ui/frames/control-bar/DropDown.tsx b/shared/js/ui/frames/control-bar/DropDown.tsx index cc606451..d35cce5f 100644 --- a/shared/js/ui/frames/control-bar/DropDown.tsx +++ b/shared/js/ui/frames/control-bar/DropDown.tsx @@ -1,5 +1,4 @@ import * as React from "react"; -import {ReactComponentBase} from "tc-shared/ui/react-elements/ReactComponentBase"; import {IconRenderer, RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon"; import {getIconManager, RemoteIconInfo} from "tc-shared/file/Icons"; import {joinClassList} from "tc-shared/ui/react-elements/Helper"; diff --git a/shared/js/ui/frames/menu-bar/MainMenu.ts b/shared/js/ui/frames/menu-bar/MainMenu.ts index c6613fac..f4ee1fce 100644 --- a/shared/js/ui/frames/menu-bar/MainMenu.ts +++ b/shared/js/ui/frames/menu-bar/MainMenu.ts @@ -11,6 +11,7 @@ import {bookmarks} from "tc-shared/Bookmarks"; import {RemoteIconInfo} from "tc-shared/file/Icons"; import {connectionHistory} from "tc-shared/connectionlog/History"; import {spawnModalAddCurrentServerToBookmarks} from "tc-shared/ui/modal/bookmarks-add-server/Controller"; +import {LogCategory, logTrace} from "tc-shared/log"; function renderConnectionItems() { const items: MenuBarEntry[] = []; @@ -52,7 +53,9 @@ function renderConnectionItems() { return items; } +let bookmarkServerUniqueIds: string[] = []; async function renderBookmarkItems() { + bookmarkServerUniqueIds = []; const bookmarkList = bookmarks.getOrderedRegisteredBookmarks(); const bookmarkItems: MenuBarEntry[] = []; @@ -66,15 +69,17 @@ async function renderBookmarkItems() { let icon: RemoteIconInfo; try { - const connectInfo = await connectionHistory.lastConnectInfo(bookmark.entry.serverAddress, "address"); + const connectInfo = await connectionHistory.lastConnectInfo(bookmark.entry.serverAddress, "address", true); if(connectInfo) { + bookmarkServerUniqueIds.push(connectInfo.serverUniqueId); const info = await connectionHistory.queryServerInfo(connectInfo.serverUniqueId); if(info && info.iconId > 0) { icon = { iconId: info.iconId, serverUniqueId: connectInfo.serverUniqueId }; } } - } catch (_) { - /* no need for any error handling */ + } catch (error) { + /* No need to warn in prod build */ + logTrace(LogCategory.BOOKMARKS, "Failed to query last connect info: %o", error); } parentList.push({ @@ -363,6 +368,16 @@ class MenuBarUpdateListener { })); this.generalHandlerEvents.push(server_connections.events().on("notify_active_handler_changed", () => updateMenuBar())); this.generalHandlerEvents.push(bookmarks.events.on(["notify_bookmark_deleted", "notify_bookmark_created", "notify_bookmark_edited", "notify_bookmarks_imported"], () => updateMenuBar())); + this.generalHandlerEvents.push(connectionHistory.events.on("notify_server_info_updated", event => { + if(bookmarkServerUniqueIds.indexOf(event.serverUniqueId) === -1) { + return; + } + + if(event.keys.indexOf("iconId") !== -1) { + /* An icon for a bookmark has changed. Send the menu bar with the new icons. */ + updateMenuBar(); + } + })); server_connections.getAllConnectionHandlers().forEach(handler => this.registerHandlerEvents(handler)); } diff --git a/shared/js/ui/modal/bookmarks/Controller.ts b/shared/js/ui/modal/bookmarks/Controller.ts index f88cafd7..a5e5118b 100644 --- a/shared/js/ui/modal/bookmarks/Controller.ts +++ b/shared/js/ui/modal/bookmarks/Controller.ts @@ -567,7 +567,7 @@ class BookmarkModalController { } return this.bookmarkUniqueServerIds[bookmarkId] = (async () => { - const info = await connectionHistory.lastConnectInfo(bookmark.serverAddress, "address"); + const info = await connectionHistory.lastConnectInfo(bookmark.serverAddress, "address", true); if(!info) { return undefined; } diff --git a/shared/js/ui/modal/bookmarks/Renderer.tsx b/shared/js/ui/modal/bookmarks/Renderer.tsx index 58fb5ca5..e8506e71 100644 --- a/shared/js/ui/modal/bookmarks/Renderer.tsx +++ b/shared/js/ui/modal/bookmarks/Renderer.tsx @@ -109,7 +109,7 @@ const BookmarkListEntryRenderer = React.memo((props: { entry: BookmarkListEntry events.fire("action_connect", { uniqueId: props.entry.uniqueId, newTab: false, closeModal: true }); }} onContextMenu={event => { - event.preventDefault(); + event.stopPropagation(); if(selectedItem.remoteValue?.id !== props.entry.uniqueId) { selectedItem.setValue({ id: props.entry.uniqueId }); @@ -195,9 +195,8 @@ const BookmarkList = React.memo(() => {
{ - event.preventDefault(); - if(bookmarks.length === 0) { + /* We've an extra overlay for not having any bookmarks. */ return; } diff --git a/shared/js/ui/modal/whats-new/Renderer.tsx b/shared/js/ui/modal/whats-new/Renderer.tsx index 4e3534d0..0947e4e4 100644 --- a/shared/js/ui/modal/whats-new/Renderer.tsx +++ b/shared/js/ui/modal/whats-new/Renderer.tsx @@ -4,6 +4,7 @@ import * as dompurify from "dompurify"; import {ChangeLog, ChangeLogEntry, ChangeSet} from "tc-shared/update/ChangeLog"; import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n"; import {guid} from "tc-shared/crypto/uid"; +import moment from "moment"; const {Remarkable} = require("remarkable"); const cssStyle = require("./Renderer.scss"); @@ -108,7 +109,7 @@ export const WhatsNew = (props: { changesUI?: ChangeLog, changesClient?: ChangeL - {versionUIDate} + {moment(__build.timestamp).format("dd.MM.YY")} ); } else if (props.changesUI && props.changesClient) { diff --git a/shared/js/ui/react-elements/Checkbox.tsx b/shared/js/ui/react-elements/Checkbox.tsx index 63cafa20..096d8ce7 100644 --- a/shared/js/ui/react-elements/Checkbox.tsx +++ b/shared/js/ui/react-elements/Checkbox.tsx @@ -29,7 +29,7 @@ export class Checkbox extends React.Component render() { const disabled = typeof this.state.disabled === "boolean" ? this.state.disabled : this.props.disabled; - const checked = typeof this.props.value === "boolean" ? this.props.value : typeof this.state.checked === "boolean" ? this.state.checked : this.props.initialValue; + const checked = (typeof this.props.value === "boolean" ? this.props.value : typeof this.state.checked === "boolean" ? this.state.checked : this.props.initialValue) || false; const disabledClass = disabled ? cssStyle.disabled : ""; return ( diff --git a/shared/js/ui/react-elements/Icon.tsx b/shared/js/ui/react-elements/Icon.tsx index 08b2968b..07028124 100644 --- a/shared/js/ui/react-elements/Icon.tsx +++ b/shared/js/ui/react-elements/Icon.tsx @@ -64,7 +64,7 @@ export const RemoteIconRenderer = React.memo((props: { icon: RemoteIcon | undefi } return ( - + ); case "loading":