mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Introduce way to install update directly from tray. Make application updater unit testable... (#5433)
* Extract product name as injectable Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make tray items comply with Open Closed Principle Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Replace duplicated overrides with global Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add behaviour for navigating to preferences using tray Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce a tray item for updating application Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak more naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant indirection Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak more naming Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce injectable for package.json being side-effect Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Relocate file to directory containing feature Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using injectable for limiting side effect Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing injection token for implementation of tray item Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove resetting state for update is ready to be installed for being unclear Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make label of tray item reactive Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract updating is enabled to separate injectable Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for tray Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Expand scope of behaviour for updating using tray also contain checking for updates Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement checking of updates from multiple update channels Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Start installing updates automatically when quitting application Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Show application window when checking of updates has happened Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Show notifications and dialog for downloading update Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement naive notifications for version updates Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement checking of Electron specific updates as responsibility Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement downloading of Electron specific updates as responsibility Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce competition for channel abstraction Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant global override Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix typing after enabling strict mode Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce abstraction for a state that is shared between environments Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract states of application update to be usable from all environments Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Handle failing download of update Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make code for window visibility actually work Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate code for sending messages between processes to a window Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Split bloated dependency in smaller pieces Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make state of download progress accessible from all environments Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Rename files for accuracy Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move channel abstraction to more global directory Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Enhance typing of channels and sync-box Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate channel abstraction types Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update asyncFn to support strict mode Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix snapshot after rebase Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global override Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce injection token for channels to allow injecting all of them at once Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add notifications about change in update status Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Rename property for accuracy Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak code style Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make notifications unit testable in behaviours Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add implementation for asking boolean over processes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Reorganize responsibilities for checking updates Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Reorganize tests for installing update under separate scenarios Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make stuff happening when root frame is rendered unit testable Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce periodical check for updates Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Allow downgrading app versions Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using competition for checking of updates in application menu Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make test less prone to fail for wrong reason Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant boilerplate Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make tests for specific migrations less prone to failing for wrong reason Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move shared stuff under common Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using single source of truth for selected update channel Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract tests for installing update from different update channels to separate scenario Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global override Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using release channel of installed application version as default value for selected update channel Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate usage of channel abstraction to same implementation Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make Channel abstraction support return values Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix direct calling of runnables Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Synchronize initial values of sync boxes when window starts Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing global override Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak message of question from user Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate names of directories Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add TODO Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove unimplemented scenario from test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Simplify test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Improve name of test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant overrides Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix code style Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make Animate deterministic in unit tests Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Simplify naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify more naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify even more naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify more and more naming Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Add todo for cleaning unacceptable code encountered Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Improve name of behaviour Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make unit test more strict Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Enhance name of behaviour Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce dependency to get random IDs Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make asking of boolean value from user not require explicit ID for question Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Simplify code for asking of boolean value from user Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make setting of initial state for sync boxes not trigger irrelevant messaging to main Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make a channel have default type for sent and returned message Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce higher order function to log errors in decorated functions Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Export type for error logging Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak test name Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce higher order function for suppressing errors Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Relocate some explicit error handlings to proper level of abstraction Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make higher order function for logging errors support asynchronous rejecting with non error instance Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make overridden version of application exactly the one required by unit test Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Mark injectable causing side effects Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Revert not required changes Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make code for asserting a promise more strict Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make dependencies readonly Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove duplication for disposers Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement initial values for sync-boxes Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Separate concept of message and request channels Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Introduce tests for requesting from channel in renderer Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement requesting from renderer in main Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Revert "Implement requesting from renderer in main" This reverts commit d3e7899d7900516f3dbfacdb317a453202318305. Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak typing of request channel listeners to get rid of unexpected undefined Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove unused variable Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Tweak timing of sentry setup Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Require messages for MessageChannels be JsonValues for serialization Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Require requests and responses for RequestChannels be JsonValues for serialization Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make different MessageChannels not require explicit "extends JsonObject" Note: Non-escaped lint breaks type here for forcing interface over type. Reasonable effort brought no understanding for what is the relevant difference between the two. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make a primitive argument an object for readability Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make typing of higher order function for error suppression not lie Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Serialize messages in channels to make IPC not blow up Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce a way to make intentional orphan promises uniform, controllable and deliberate Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Make downloading of update and what follows more deliberate as orphan promise Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Move utility function under directory Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Move another utility function under directory Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix incorrect name of file Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove redundant code Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Kill dead code Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Round percentage of update download progress in tray Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix rebase conflicts Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix CheckForUpdate type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> Co-authored-by: Iku-turso <mikko.aspiala@gmail.com> Co-authored-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
229 files changed
+8652
-1217
No files matched your search
-20
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import ipcRendererInjectable from "./ipc-renderer/ipc-renderer.injectable";
|
||||
import { getValueFromRegisteredChannel } from "./get-value-from-registered-channel";
|
||||
import type { Channel } from "../../../common/ipc-channel/channel";
|
||||
|
||||
export type GetValueFromRegisteredChannel = <TChannel extends Channel<TInstance>, TInstance>(channel: TChannel) => Promise<TChannel["_template"]>;
|
||||
|
||||
const getValueFromRegisteredChannelInjectable = getInjectable({
|
||||
id: "get-value-from-registered-channel",
|
||||
|
||||
instantiate: (di) => getValueFromRegisteredChannel({
|
||||
ipcRenderer: di.inject(ipcRendererInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export default getValueFromRegisteredChannelInjectable;
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { IpcRenderer } from "electron";
|
||||
import type { Channel } from "../../../common/ipc-channel/channel";
|
||||
|
||||
interface Dependencies {
|
||||
ipcRenderer: IpcRenderer;
|
||||
}
|
||||
|
||||
export const getValueFromRegisteredChannel = ({ ipcRenderer }: Dependencies) =>
|
||||
<TChannel extends Channel<TInstance>, TInstance>(
|
||||
channel: TChannel,
|
||||
): Promise<TChannel["_template"]> =>
|
||||
ipcRenderer.invoke(channel.name);
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import ipcRendererInjectable from "./ipc-renderer/ipc-renderer.injectable";
|
||||
import type {
|
||||
IpcChannelListener,
|
||||
} from "../../ipc-channel-listeners/ipc-channel-listener-injection-token";
|
||||
|
||||
const registerIpcChannelListenerInjectable = getInjectable({
|
||||
id: "register-ipc-channel-listener",
|
||||
|
||||
instantiate: (di) => {
|
||||
const ipc = di.inject(ipcRendererInjectable);
|
||||
|
||||
return ({ channel, handle }: IpcChannelListener) => {
|
||||
ipc.on(channel.name, (_, data) => {
|
||||
handle(data);
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default registerIpcChannelListenerInjectable;
|
||||
@@ -3,27 +3,29 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { appPathsIpcChannel } from "../../common/app-paths/app-path-injection-token";
|
||||
import getValueFromRegisteredChannelInjectable from "./get-value-from-registered-channel/get-value-from-registered-channel.injectable";
|
||||
import appPathsStateInjectable from "../../common/app-paths/app-paths-state.injectable";
|
||||
import { beforeFrameStartsInjectionToken } from "../before-frame-starts/before-frame-starts-injection-token";
|
||||
import appPathsChannelInjectable from "../../common/app-paths/app-paths-channel.injectable";
|
||||
import { requestFromChannelInjectionToken } from "../../common/utils/channel/request-from-channel-injection-token";
|
||||
|
||||
const setupAppPathsInjectable = getInjectable({
|
||||
id: "setup-app-paths",
|
||||
|
||||
instantiate: (di) => ({
|
||||
run: async () => {
|
||||
const getValueFromRegisteredChannel = di.inject(
|
||||
getValueFromRegisteredChannelInjectable,
|
||||
);
|
||||
instantiate: (di) => {
|
||||
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
|
||||
const appPathsChannel = di.inject(appPathsChannelInjectable);
|
||||
const appPathsState = di.inject(appPathsStateInjectable);
|
||||
|
||||
const syncAppPaths = await getValueFromRegisteredChannel(appPathsIpcChannel);
|
||||
return {
|
||||
run: async () => {
|
||||
const appPaths = await requestFromChannel(
|
||||
appPathsChannel,
|
||||
);
|
||||
|
||||
const appPathsState = di.inject(appPathsStateInjectable);
|
||||
|
||||
appPathsState.set(syncAppPaths);
|
||||
},
|
||||
}),
|
||||
appPathsState.set(appPaths);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: beforeFrameStartsInjectionToken,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { ApplicationUpdateStatusChannel, ApplicationUpdateStatusEventId } from "../../common/application-update/application-update-status-channel.injectable";
|
||||
import applicationUpdateStatusChannelInjectable from "../../common/application-update/application-update-status-channel.injectable";
|
||||
import showInfoNotificationInjectable from "../components/notifications/show-info-notification.injectable";
|
||||
import type { MessageChannelListener } from "../../common/utils/channel/message-channel-listener-injection-token";
|
||||
import { messageChannelListenerInjectionToken } from "../../common/utils/channel/message-channel-listener-injection-token";
|
||||
|
||||
const applicationUpdateStatusListenerInjectable = getInjectable({
|
||||
id: "application-update-status-listener",
|
||||
|
||||
instantiate: (di): MessageChannelListener<ApplicationUpdateStatusChannel> => {
|
||||
const channel = di.inject(applicationUpdateStatusChannelInjectable);
|
||||
const showInfoNotification = di.inject(showInfoNotificationInjectable);
|
||||
|
||||
const eventHandlers: Record<ApplicationUpdateStatusEventId, { handle: (version?: string) => void }> = {
|
||||
"checking-for-updates": {
|
||||
handle: () => {
|
||||
showInfoNotification("Checking for updates...");
|
||||
},
|
||||
},
|
||||
|
||||
"no-updates-available": {
|
||||
handle: () => {
|
||||
showInfoNotification("No new updates available");
|
||||
},
|
||||
},
|
||||
|
||||
"download-for-update-started": {
|
||||
handle: (version) => {
|
||||
showInfoNotification(`Download for version ${version} started...`);
|
||||
},
|
||||
},
|
||||
|
||||
"download-for-update-failed": {
|
||||
handle: () => {
|
||||
showInfoNotification("Download of update failed");
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
channel,
|
||||
|
||||
handler: ({ eventId, version }) => {
|
||||
eventHandlers[eventId].handle(version);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: messageChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default applicationUpdateStatusListenerInjectable;
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { AskBooleanQuestionChannel } from "../../common/ask-boolean/ask-boolean-question-channel.injectable";
|
||||
import askBooleanQuestionChannelInjectable from "../../common/ask-boolean/ask-boolean-question-channel.injectable";
|
||||
import showInfoNotificationInjectable from "../components/notifications/show-info-notification.injectable";
|
||||
import { Button } from "../components/button";
|
||||
import React from "react";
|
||||
import { messageToChannelInjectionToken } from "../../common/utils/channel/message-to-channel-injection-token";
|
||||
import askBooleanAnswerChannelInjectable from "../../common/ask-boolean/ask-boolean-answer-channel.injectable";
|
||||
import notificationsStoreInjectable from "../components/notifications/notifications-store.injectable";
|
||||
import type { MessageChannelListener } from "../../common/utils/channel/message-channel-listener-injection-token";
|
||||
import { messageChannelListenerInjectionToken } from "../../common/utils/channel/message-channel-listener-injection-token";
|
||||
|
||||
const askBooleanQuestionChannelListenerInjectable = getInjectable({
|
||||
id: "ask-boolean-question-channel-listener",
|
||||
|
||||
instantiate: (di): MessageChannelListener<AskBooleanQuestionChannel> => {
|
||||
const questionChannel = di.inject(askBooleanQuestionChannelInjectable);
|
||||
const showInfoNotification = di.inject(showInfoNotificationInjectable);
|
||||
const messageToChannel = di.inject(messageToChannelInjectionToken);
|
||||
const answerChannel = di.inject(askBooleanAnswerChannelInjectable);
|
||||
const notificationsStore = di.inject(notificationsStoreInjectable);
|
||||
|
||||
const sendAnswerFor = (id: string) => (value: boolean) => {
|
||||
messageToChannel(answerChannel, { id, value });
|
||||
};
|
||||
|
||||
const closeNotification = (notificationId: string) => {
|
||||
notificationsStore.remove(notificationId);
|
||||
};
|
||||
|
||||
const sendAnswerAndCloseNotificationFor = (sendAnswer: (value: boolean) => void, notificationId: string) => (value: boolean) => () => {
|
||||
sendAnswer(value);
|
||||
closeNotification(notificationId);
|
||||
};
|
||||
|
||||
return {
|
||||
channel: questionChannel,
|
||||
|
||||
handler: ({ id: questionId, title, question }) => {
|
||||
const notificationId = `ask-boolean-for-${questionId}`;
|
||||
|
||||
const sendAnswer = sendAnswerFor(questionId);
|
||||
const sendAnswerAndCloseNotification = sendAnswerAndCloseNotificationFor(sendAnswer, notificationId);
|
||||
|
||||
showInfoNotification(
|
||||
<AskBoolean
|
||||
id={questionId}
|
||||
title={title}
|
||||
message={question}
|
||||
onNo={sendAnswerAndCloseNotification(false)}
|
||||
onYes={sendAnswerAndCloseNotification(true)}
|
||||
/>,
|
||||
|
||||
{
|
||||
id: notificationId,
|
||||
timeout: 0,
|
||||
onClose: () => sendAnswer(false),
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: messageChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default askBooleanQuestionChannelListenerInjectable;
|
||||
|
||||
const AskBoolean = ({
|
||||
id,
|
||||
title,
|
||||
message,
|
||||
onNo,
|
||||
onYes,
|
||||
}: {
|
||||
id: string;
|
||||
title: string;
|
||||
message: string;
|
||||
onNo: () => void;
|
||||
onYes: () => void;
|
||||
}) => (
|
||||
<div className="flex column gaps" data-testid={`ask-boolean-${id}`}>
|
||||
<b>{title}</b>
|
||||
<p>{message}</p>
|
||||
|
||||
<div className="flex gaps row align-left box grow">
|
||||
<Button
|
||||
light
|
||||
label="Yes"
|
||||
onClick={onYes}
|
||||
data-testid={`ask-boolean-${id}-button-yes`}
|
||||
/>
|
||||
|
||||
<Button
|
||||
active
|
||||
outlined
|
||||
label="No"
|
||||
data-testid={`ask-boolean-${id}-button-no`}
|
||||
onClick={onNo}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -46,8 +46,7 @@ import { init } from "@sentry/electron/renderer";
|
||||
import kubernetesClusterCategoryInjectable from "../common/catalog/categories/kubernetes-cluster.injectable";
|
||||
import autoRegistrationInjectable from "../common/k8s-api/api-manager/auto-registration.injectable";
|
||||
import assert from "assert";
|
||||
import { beforeFrameStartsInjectionToken } from "./before-frame-starts/before-frame-starts-injection-token";
|
||||
import { runManyFor } from "../common/runnable/run-many-for";
|
||||
import startFrameInjectable from "./start-frame/start-frame.injectable";
|
||||
|
||||
configurePackages(); // global packages
|
||||
registerCustomThemes(); // monaco editor themes
|
||||
@@ -68,9 +67,9 @@ export async function bootstrap(di: DiContainer) {
|
||||
initializeSentryReporting(init);
|
||||
}
|
||||
|
||||
const beforeFrameStarts = runManyFor(di)(beforeFrameStartsInjectionToken);
|
||||
const startFrame = di.inject(startFrameInjectable);
|
||||
|
||||
await beforeFrameStarts();
|
||||
await startFrame();
|
||||
|
||||
// TODO: Consolidate import time side-effect to setup time
|
||||
bindEvents();
|
||||
|
||||
@@ -26,8 +26,6 @@ import appVersionInjectable from "../../../common/get-configuration-file-model/a
|
||||
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
|
||||
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
||||
import { computed } from "mobx";
|
||||
import ipcRendererInjectable from "../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
|
||||
|
||||
mockWindow();
|
||||
@@ -107,13 +105,7 @@ describe("<Catalog />", () => {
|
||||
catalogEntityItem = createMockCatalogEntity(onRun);
|
||||
catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||
|
||||
UserStore.createInstance(); // TODO: replace with DI
|
||||
|
||||
di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry);
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
|
||||
emitEvent = jest.fn();
|
||||
|
||||
@@ -129,7 +121,6 @@ describe("<Catalog />", () => {
|
||||
|
||||
afterEach(() => {
|
||||
CatalogEntityDetailRegistry.resetInstance();
|
||||
UserStore.resetInstance();
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
mockFs.restore();
|
||||
|
||||
@@ -7,7 +7,6 @@ import "@testing-library/jest-dom/extend-expect";
|
||||
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import fse from "fs-extra";
|
||||
import React from "react";
|
||||
import { UserStore } from "../../../../common/user-store";
|
||||
import type { ExtensionDiscovery } from "../../../../extensions/extension-discovery/extension-discovery";
|
||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
||||
import { ConfirmDialog } from "../../confirm-dialog";
|
||||
@@ -96,13 +95,10 @@ describe("Extensions", () => {
|
||||
});
|
||||
|
||||
extensionDiscovery.uninstallExtension = jest.fn(() => Promise.resolve());
|
||||
|
||||
UserStore.createInstance();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
UserStore.resetInstance();
|
||||
});
|
||||
|
||||
it("disables uninstall and disable buttons while uninstalling", async () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { UserStore } from "../../../common/user-store";
|
||||
import { Input } from "../input";
|
||||
import { Switch } from "../switch";
|
||||
import moment from "moment-timezone";
|
||||
import { updateChannels, defaultExtensionRegistryUrl, defaultUpdateChannel, defaultLocaleTimezone, defaultExtensionRegistryUrlLocation } from "../../../common/user-store/preferences-helpers";
|
||||
import { defaultExtensionRegistryUrl, defaultLocaleTimezone, defaultExtensionRegistryUrlLocation } from "../../../common/user-store/preferences-helpers";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { runInAction } from "mobx";
|
||||
import { isUrl } from "../input/input_validators";
|
||||
@@ -24,11 +24,17 @@ import { Preferences } from "./preferences";
|
||||
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
|
||||
import themeStoreInjectable from "../../themes/store.injectable";
|
||||
import { defaultThemeId } from "../../../common/vars";
|
||||
import { updateChannels } from "../../../common/application-update/update-channels";
|
||||
import { map, toPairs } from "lodash/fp";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import type { SelectedUpdateChannel } from "../../../common/application-update/selected-update-channel/selected-update-channel.injectable";
|
||||
import selectedUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/selected-update-channel.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
appPreferenceItems: IComputedValue<RegisteredAppPreference[]>;
|
||||
userStore: UserStore;
|
||||
themeStore: ThemeStore;
|
||||
selectedUpdateChannel: SelectedUpdateChannel;
|
||||
}
|
||||
|
||||
const timezoneOptions = moment.tz.names()
|
||||
@@ -36,10 +42,16 @@ const timezoneOptions = moment.tz.names()
|
||||
value: timezone,
|
||||
label: timezone.replace("_", " "),
|
||||
}));
|
||||
const updateChannelOptions = Array.from(updateChannels, ([channel, { label }]) => ({
|
||||
value: channel,
|
||||
label,
|
||||
}));
|
||||
|
||||
const updateChannelOptions = pipeline(
|
||||
toPairs(updateChannels),
|
||||
|
||||
map(([, channel]) => ({
|
||||
value: channel.id,
|
||||
label: channel.label,
|
||||
})),
|
||||
);
|
||||
|
||||
const extensionInstallRegistryOptions = [
|
||||
{
|
||||
value: "default",
|
||||
@@ -55,7 +67,7 @@ const extensionInstallRegistryOptions = [
|
||||
},
|
||||
] as const;
|
||||
|
||||
const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems, userStore, themeStore }) => {
|
||||
const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems, userStore, themeStore, selectedUpdateChannel }) => {
|
||||
const [customUrl, setCustomUrl] = React.useState(userStore.extensionRegistryUrl.customUrl || "");
|
||||
const themeOptions = [
|
||||
{
|
||||
@@ -144,8 +156,8 @@ const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems, us
|
||||
<Select
|
||||
id="update-channel-input"
|
||||
options={updateChannelOptions}
|
||||
value={userStore.updateChannel}
|
||||
onChange={value => userStore.updateChannel = value?.value ?? defaultUpdateChannel}
|
||||
value={selectedUpdateChannel.value.get().id}
|
||||
onChange={(selected) => selectedUpdateChannel.setValue(selected?.value) }
|
||||
themeName="lens"
|
||||
/>
|
||||
</section>
|
||||
@@ -175,6 +187,7 @@ export const Application = withInjectables<Dependencies>(
|
||||
appPreferenceItems: di.inject(appPreferencesInjectable),
|
||||
userStore: di.inject(userStoreInjectable),
|
||||
themeStore: di.inject(themeStoreInjectable),
|
||||
selectedUpdateChannel: di.inject(selectedUpdateChannelInjectable),
|
||||
}),
|
||||
},
|
||||
);
|
||||
-5
@@ -12,7 +12,6 @@ import type { DiRender } from "../../../test-utils/renderFor";
|
||||
import { renderFor } from "../../../test-utils/renderFor";
|
||||
import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../../stores-apis-can-be-created.injectable";
|
||||
import ipcRendererInjectable from "../../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
|
||||
describe("ClusterRoleBindingDialog tests", () => {
|
||||
let render: DiRender;
|
||||
@@ -21,10 +20,6 @@ describe("ClusterRoleBindingDialog tests", () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import { renderFor } from "../../../test-utils/renderFor";
|
||||
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../../stores-apis-can-be-created.injectable";
|
||||
import ipcRendererInjectable from "../../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
|
||||
describe("RoleBindingDialog tests", () => {
|
||||
let render: DiRender;
|
||||
@@ -23,10 +22,6 @@ describe("RoleBindingDialog tests", () => {
|
||||
|
||||
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import React from "react";
|
||||
import { observable, makeObservable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { cssNames, noop } from "../../utils";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import requestAnimationFrameInjectable from "./request-animation-frame.injectable";
|
||||
|
||||
export type AnimateName = "opacity" | "slide-right" | "opacity-scale" | string;
|
||||
|
||||
@@ -21,8 +23,12 @@ export interface AnimateProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
requestAnimationFrame: (callback: () => void) => void;
|
||||
}
|
||||
|
||||
@observer
|
||||
class DefaultedAnimate extends React.Component<AnimateProps & typeof DefaultedAnimate.defaultProps> {
|
||||
class DefaultedAnimate extends React.Component<AnimateProps & Dependencies & typeof DefaultedAnimate.defaultProps> {
|
||||
static defaultProps = {
|
||||
name: "opacity",
|
||||
enter: true,
|
||||
@@ -38,7 +44,7 @@ class DefaultedAnimate extends React.Component<AnimateProps & typeof DefaultedAn
|
||||
leave: false,
|
||||
};
|
||||
|
||||
constructor(props: AnimateProps & typeof DefaultedAnimate.defaultProps) {
|
||||
constructor(props: AnimateProps & Dependencies & typeof DefaultedAnimate.defaultProps) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
@@ -69,7 +75,8 @@ class DefaultedAnimate extends React.Component<AnimateProps & typeof DefaultedAn
|
||||
|
||||
enter() {
|
||||
this.isVisible = true; // triggers render() to apply css-animation in existing dom
|
||||
requestAnimationFrame(() => {
|
||||
|
||||
this.props.requestAnimationFrame(() => {
|
||||
this.statusClassName.enter = true;
|
||||
this.props.onEnter();
|
||||
});
|
||||
@@ -115,4 +122,17 @@ class DefaultedAnimate extends React.Component<AnimateProps & typeof DefaultedAn
|
||||
}
|
||||
}
|
||||
|
||||
export const Animate = (props: AnimateProps) => <DefaultedAnimate {...props} />;
|
||||
export const NonInjectedAnimate = (props: AnimateProps & Dependencies) => <DefaultedAnimate {...props} />;
|
||||
|
||||
export const Animate = withInjectables<Dependencies, AnimateProps>(
|
||||
NonInjectedAnimate,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
requestAnimationFrame: di.inject(requestAnimationFrameInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
const requestAnimationFrameInjectable = getInjectable({
|
||||
id: "request-animation-frame",
|
||||
instantiate: () => (callback: () => void) => requestAnimationFrame(callback),
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default requestAnimationFrameInjectable;
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import hostedClusterIdInjectable from "../../../../common/cluster-store/hosted-cluster-id.injectable";
|
||||
import ipcRendererInjectable from "../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import type { DockStore, DockTab } from "../dock/store";
|
||||
import { TabKind } from "../dock/store";
|
||||
@@ -26,10 +25,6 @@ describe("DockStore", () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(hostedClusterIdInjectable, () => "some-cluster-id");
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
|
||||
dockStore = di.inject(dockStoreInjectable);
|
||||
|
||||
@@ -20,7 +20,6 @@ import getConfigurationFileModelInjectable from "../../../../common/get-configur
|
||||
import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import assert from "assert";
|
||||
import hostedClusterIdInjectable from "../../../../common/cluster-store/hosted-cluster-id.injectable";
|
||||
import ipcRendererInjectable from "../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@@ -83,10 +82,6 @@ describe("<DockTabs />", () => {
|
||||
directoryForUserDataInjectable,
|
||||
() => "some-test-suite-specific-directory-for-user-data",
|
||||
);
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
|
||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||
di.permitSideEffects(appVersionInjectable);
|
||||
|
||||
@@ -22,7 +22,6 @@ import { SearchStore } from "../../../../search-store/search-store";
|
||||
import getConfigurationFileModelInjectable from "../../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable from "../../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import assert from "assert";
|
||||
import ipcRendererInjectable from "../../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@@ -132,10 +131,6 @@ describe("<LogResourceSelector />", () => {
|
||||
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
di.override(callForLogsInjectable, () => () => Promise.resolve("some-logs"));
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
|
||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||
di.permitSideEffects(appVersionInjectable);
|
||||
|
||||
@@ -19,7 +19,6 @@ import getConfigurationFileModelInjectable from "../../../../common/get-configur
|
||||
import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import type { HotbarStore } from "../../../../common/hotbars/store";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
|
||||
import ipcRendererInjectable from "../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
|
||||
const mockHotbars: Partial<Record<string, any>> = {
|
||||
"1": {
|
||||
@@ -47,10 +46,6 @@ describe("<HotbarRemoveCommand />", () => {
|
||||
|
||||
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
|
||||
di.permitSideEffects(hotbarStoreInjectable);
|
||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { NotificationsStore } from "./notifications.store";
|
||||
|
||||
const notificationsStoreInjectable = getInjectable({
|
||||
id: "notifications-store",
|
||||
instantiate: () => new NotificationsStore(),
|
||||
});
|
||||
|
||||
export default notificationsStoreInjectable;
|
||||
@@ -91,5 +91,3 @@ export class NotificationsStore {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const notificationsStore = new NotificationsStore();
|
||||
@@ -10,61 +10,25 @@ import { reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
|
||||
import { cssNames, prevDefault } from "../../utils";
|
||||
import type { Notification, NotificationMessage } from "./notifications.store";
|
||||
import { notificationsStore, NotificationStatus } from "./notifications.store";
|
||||
import type { Notification, NotificationMessage, NotificationsStore } from "./notifications.store";
|
||||
import { NotificationStatus } from "./notifications.store";
|
||||
import { Animate } from "../animate";
|
||||
import { Icon } from "../icon";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { asLegacyGlobalForExtensionApi } from "../../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import notificationsStoreInjectable from "./notifications-store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
store: NotificationsStore;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Notifications extends React.Component {
|
||||
class NonInjectedNotifications extends React.Component<Dependencies> {
|
||||
public elem: HTMLDivElement | null = null;
|
||||
|
||||
static ok(message: NotificationMessage) {
|
||||
return notificationsStore.add({
|
||||
message,
|
||||
timeout: 2_500,
|
||||
status: NotificationStatus.OK,
|
||||
});
|
||||
}
|
||||
|
||||
static checkedError(message: unknown, fallback: string, customOpts?: Partial<Omit<Notification, "message">>) {
|
||||
if (typeof message === "string" || message instanceof Error || message instanceof JsonApiErrorParsed) {
|
||||
return Notifications.error(message, customOpts);
|
||||
}
|
||||
|
||||
console.warn("Unknown notification error message, falling back to default", message);
|
||||
|
||||
return Notifications.error(fallback, customOpts);
|
||||
}
|
||||
|
||||
static error(message: NotificationMessage, customOpts: Partial<Omit<Notification, "message">> = {}) {
|
||||
return notificationsStore.add({
|
||||
message,
|
||||
timeout: 10_000,
|
||||
status: NotificationStatus.ERROR,
|
||||
...customOpts,
|
||||
});
|
||||
}
|
||||
|
||||
static shortInfo(message: NotificationMessage, customOpts: Partial<Omit<Notification, "message">> = {}) {
|
||||
return this.info(message, {
|
||||
timeout: 5_000,
|
||||
...customOpts,
|
||||
});
|
||||
}
|
||||
|
||||
static info(message: NotificationMessage, customOpts: Partial<Omit<Notification, "message">> = {}) {
|
||||
return notificationsStore.add({
|
||||
status: NotificationStatus.INFO,
|
||||
timeout: 0,
|
||||
message,
|
||||
...customOpts,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => notificationsStore.notifications.length, () => {
|
||||
reaction(() => this.props.store.notifications.length, () => {
|
||||
this.scrollToLastNotification();
|
||||
}, { delay: 250 }),
|
||||
]);
|
||||
@@ -74,7 +38,7 @@ export class Notifications extends React.Component {
|
||||
if (!this.elem) {
|
||||
return;
|
||||
}
|
||||
this.elem.scrollTo({
|
||||
this.elem.scrollTo?.({
|
||||
top: this.elem.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
@@ -91,7 +55,7 @@ export class Notifications extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { notifications, remove, addAutoHideTimer, removeAutoHideTimer } = notificationsStore;
|
||||
const { notifications, remove, addAutoHideTimer, removeAutoHideTimer } = this.props.store;
|
||||
|
||||
return (
|
||||
<div className="Notifications flex column align-flex-end" ref={e => this.elem = e}>
|
||||
@@ -114,6 +78,7 @@ export class Notifications extends React.Component {
|
||||
<Icon
|
||||
material="close"
|
||||
className="close"
|
||||
data-testid={`close-notification-for-${id}`}
|
||||
onClick={prevDefault(() => {
|
||||
remove(id);
|
||||
onClose?.();
|
||||
@@ -128,3 +93,68 @@ export class Notifications extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const Notifications = withInjectables<Dependencies>(
|
||||
NonInjectedNotifications,
|
||||
|
||||
{
|
||||
getProps: (di) => ({
|
||||
store: di.inject(notificationsStoreInjectable),
|
||||
}),
|
||||
},
|
||||
) as React.FC & {
|
||||
ok: (message: NotificationMessage) => () => void;
|
||||
checkedError: (message: unknown, fallback: string, customOpts?: Partial<Omit<Notification, "message">>) => () => void;
|
||||
error: (message: NotificationMessage, customOpts?: Partial<Omit<Notification, "message">>) => () => void;
|
||||
shortInfo: (message: NotificationMessage, customOpts?: Partial<Omit<Notification, "message">>) => () => void;
|
||||
info: (message: NotificationMessage, customOpts?: Partial<Omit<Notification, "message">>) => () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const _notificationStore = asLegacyGlobalForExtensionApi(notificationsStoreInjectable);
|
||||
|
||||
Notifications.ok = (message: NotificationMessage) => {
|
||||
return _notificationStore.add({
|
||||
message,
|
||||
timeout: 2_500,
|
||||
status: NotificationStatus.OK,
|
||||
});
|
||||
};
|
||||
|
||||
Notifications.checkedError = (message, fallback, customOpts = {}) => {
|
||||
if (typeof message === "string" || message instanceof Error || message instanceof JsonApiErrorParsed) {
|
||||
return Notifications.error(message, customOpts);
|
||||
}
|
||||
|
||||
console.warn("Unknown notification error message, falling back to default", message);
|
||||
|
||||
return Notifications.error(fallback, customOpts);
|
||||
};
|
||||
|
||||
Notifications.error = (message, customOpts= {}) => {
|
||||
return _notificationStore.add({
|
||||
message,
|
||||
timeout: 10_000,
|
||||
status: NotificationStatus.ERROR,
|
||||
...customOpts,
|
||||
});
|
||||
};
|
||||
|
||||
Notifications.shortInfo = (message, customOpts = {}) => {
|
||||
return Notifications.info(message, {
|
||||
timeout: 5_000,
|
||||
...customOpts,
|
||||
});
|
||||
};
|
||||
|
||||
Notifications.info = (message, customOpts = {}) => {
|
||||
return _notificationStore.add({
|
||||
status: NotificationStatus.INFO,
|
||||
timeout: 0,
|
||||
message,
|
||||
...customOpts,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { NotificationMessage, Notification } from "./notifications.store";
|
||||
import { NotificationStatus } from "./notifications.store";
|
||||
import notificationsStoreInjectable from "./notifications-store.injectable";
|
||||
|
||||
const showInfoNotificationInjectable = getInjectable({
|
||||
id: "show-info-notification",
|
||||
|
||||
instantiate: (di) => {
|
||||
const notificationsStore = di.inject(notificationsStoreInjectable);
|
||||
|
||||
return (message: NotificationMessage, customOpts: Partial<Omit<Notification, "message">> = {}) =>
|
||||
notificationsStore.add({
|
||||
status: NotificationStatus.INFO,
|
||||
timeout: 5000,
|
||||
message,
|
||||
...customOpts,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default showInfoNotificationInjectable;
|
||||
@@ -17,7 +17,6 @@ import { computed } from "mobx";
|
||||
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import ipcRendererInjectable from "../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
ipcRenderer: {
|
||||
@@ -38,11 +37,7 @@ describe("<Select />", () => {
|
||||
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
di.override(rendererExtensionsInjectable, () => computed(() => [] as LensRendererExtension[]));
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
|
||||
|
||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||
di.permitSideEffects(appVersionInjectable);
|
||||
});
|
||||
|
||||
@@ -18,14 +18,13 @@ import type { RenderResult } from "@testing-library/react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import type { KubeResource } from "../../../common/rbac";
|
||||
import { Sidebar } from "../layout/sidebar";
|
||||
import { getDisForUnitTesting } from "../../../test-utils/get-dis-for-unit-testing";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import clusterStoreInjectable from "../../../common/cluster-store/cluster-store.injectable";
|
||||
import type { ClusterStore } from "../../../common/cluster-store/cluster-store";
|
||||
import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
|
||||
import currentRouteComponentInjectable from "../../routes/current-route-component.injectable";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { flatMap, compact, join, get, filter } from "lodash/fp";
|
||||
import { flatMap, compact, join, get, filter, find, map, matches } from "lodash/fp";
|
||||
import preferenceNavigationItemsInjectable from "../+preferences/preferences-navigation/preference-navigation-items.injectable";
|
||||
import navigateToPreferencesInjectable from "../../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
|
||||
import type { MenuItemOpts } from "../../../main/menu/application-menu-items.injectable";
|
||||
@@ -44,6 +43,14 @@ import { flushPromises } from "../../../common/test-utils/flush-promises";
|
||||
import type { NamespaceStore } from "../+namespaces/store";
|
||||
import namespaceStoreInjectable from "../+namespaces/store.injectable";
|
||||
import historyInjectable from "../../navigation/history.injectable";
|
||||
import type { TrayMenuItem } from "../../../main/tray/tray-menu-item/tray-menu-item-injection-token";
|
||||
import electronTrayInjectable from "../../../main/tray/electron-tray/electron-tray.injectable";
|
||||
import applicationWindowInjectable from "../../../main/start-main-application/lens-window/application-window/application-window.injectable";
|
||||
import { Notifications } from "../notifications/notifications";
|
||||
import broadcastThatRootFrameIsRenderedInjectable from "../../frames/root-frame/broadcast-that-root-frame-is-rendered.injectable";
|
||||
import { getDiForUnitTesting as getRendererDi } from "../../getDiForUnitTesting";
|
||||
import { getDiForUnitTesting as getMainDi } from "../../../main/getDiForUnitTesting";
|
||||
import { overrideChannels } from "../../../test-utils/channel-fakes/override-channels";
|
||||
|
||||
type Callback = (dis: DiContainers) => void | Promise<void>;
|
||||
|
||||
@@ -56,6 +63,11 @@ export interface ApplicationBuilder {
|
||||
beforeRender: (callback: Callback) => ApplicationBuilder;
|
||||
render: () => Promise<RenderResult>;
|
||||
|
||||
tray: {
|
||||
click: (id: string) => Promise<void>;
|
||||
get: (id: string) => TrayMenuItem | undefined;
|
||||
};
|
||||
|
||||
applicationMenu: {
|
||||
click: (path: string) => Promise<void>;
|
||||
};
|
||||
@@ -80,14 +92,23 @@ interface DiContainers {
|
||||
|
||||
interface Environment {
|
||||
renderSidebar: () => React.ReactNode;
|
||||
beforeRender: () => void;
|
||||
onAllowKubeResource: () => void;
|
||||
}
|
||||
|
||||
export const getApplicationBuilder = () => {
|
||||
const { rendererDi, mainDi } = getDisForUnitTesting({
|
||||
const mainDi = getMainDi({
|
||||
doGeneralOverrides: true,
|
||||
});
|
||||
|
||||
const overrideChannelsForWindow = overrideChannels(mainDi);
|
||||
|
||||
const rendererDi = getRendererDi({
|
||||
doGeneralOverrides: true,
|
||||
});
|
||||
|
||||
overrideChannelsForWindow(rendererDi);
|
||||
|
||||
const dis = { rendererDi, mainDi };
|
||||
|
||||
const clusterStoreStub = {
|
||||
@@ -110,6 +131,12 @@ export const getApplicationBuilder = () => {
|
||||
application: {
|
||||
renderSidebar: () => null,
|
||||
|
||||
beforeRender: () => {
|
||||
const nofifyThatRootFrameIsRendered = rendererDi.inject(broadcastThatRootFrameIsRenderedInjectable);
|
||||
|
||||
nofifyThatRootFrameIsRendered();
|
||||
},
|
||||
|
||||
onAllowKubeResource: () => {
|
||||
throw new Error(
|
||||
"Tried to allow kube resource when environment is not cluster frame.",
|
||||
@@ -119,6 +146,7 @@ export const getApplicationBuilder = () => {
|
||||
|
||||
clusterFrame: {
|
||||
renderSidebar: () => <Sidebar />,
|
||||
beforeRender: () => {},
|
||||
onAllowKubeResource: () => {},
|
||||
} as Environment,
|
||||
};
|
||||
@@ -138,6 +166,17 @@ export const getApplicationBuilder = () => {
|
||||
computed(() => []),
|
||||
);
|
||||
|
||||
let trayMenuItemsStateFake: TrayMenuItem[];
|
||||
|
||||
mainDi.override(electronTrayInjectable, () => ({
|
||||
start: () => {},
|
||||
stop: () => {},
|
||||
|
||||
setMenuItems: (items) => {
|
||||
trayMenuItemsStateFake = items;
|
||||
},
|
||||
}));
|
||||
|
||||
let allowedResourcesState: IObservableArray<KubeResource>;
|
||||
let rendered: RenderResult;
|
||||
|
||||
@@ -180,6 +219,32 @@ export const getApplicationBuilder = () => {
|
||||
},
|
||||
},
|
||||
|
||||
tray: {
|
||||
get: (id: string) => {
|
||||
return trayMenuItemsStateFake.find(matches({ id }));
|
||||
},
|
||||
|
||||
click: async (id: string) => {
|
||||
const menuItem = pipeline(
|
||||
trayMenuItemsStateFake,
|
||||
find((menuItem) => menuItem.id === id),
|
||||
);
|
||||
|
||||
if (!menuItem) {
|
||||
const availableIds = pipeline(
|
||||
trayMenuItemsStateFake,
|
||||
filter(item => !!item.click),
|
||||
map(item => item.id),
|
||||
join(", "),
|
||||
);
|
||||
|
||||
throw new Error(`Tried to click tray menu item with ID ${id} which does not exist. Available IDs are: "${availableIds}"`);
|
||||
}
|
||||
|
||||
await menuItem.click?.();
|
||||
},
|
||||
},
|
||||
|
||||
preferences: {
|
||||
close: () => {
|
||||
const link = rendered.getByTestId("close-preferences");
|
||||
@@ -318,6 +383,10 @@ export const getApplicationBuilder = () => {
|
||||
|
||||
await startMainApplication();
|
||||
|
||||
const applicationWindow = mainDi.inject(applicationWindowInjectable);
|
||||
|
||||
await applicationWindow.show();
|
||||
|
||||
const startFrame = rendererDi.inject(startFrameInjectable);
|
||||
|
||||
await startFrame();
|
||||
@@ -330,6 +399,8 @@ export const getApplicationBuilder = () => {
|
||||
await callback(dis);
|
||||
}
|
||||
|
||||
environment.beforeRender();
|
||||
|
||||
rendered = render(
|
||||
<Router history={history}>
|
||||
{environment.renderSidebar()}
|
||||
@@ -345,6 +416,8 @@ export const getApplicationBuilder = () => {
|
||||
return <Component />;
|
||||
}}
|
||||
</Observer>
|
||||
|
||||
<Notifications />
|
||||
</Router>,
|
||||
);
|
||||
|
||||
|
||||
@@ -3,15 +3,25 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { render, act } from "@testing-library/react";
|
||||
import { act } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { UpdateButton } from "../update-button";
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import type { DiRender } from "../../test-utils/renderFor";
|
||||
import { renderFor } from "../../test-utils/renderFor";
|
||||
|
||||
const update = jest.fn();
|
||||
|
||||
describe("<UpdateButton/>", () => {
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
update.mockClear();
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { messageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token";
|
||||
import rootFrameIsRenderedChannelInjectable from "../../../common/root-frame-rendered-channel/root-frame-rendered-channel.injectable";
|
||||
|
||||
const broadcastThatRootFrameIsRenderedInjectable = getInjectable({
|
||||
id: "broadcast-that-root-frame-is-rendered",
|
||||
|
||||
instantiate: (di) => {
|
||||
const messageToChannel = di.inject(messageToChannelInjectionToken);
|
||||
const rootFrameIsRenderedChannel = di.inject(rootFrameIsRenderedChannelInjectable);
|
||||
|
||||
return () => {
|
||||
messageToChannel(rootFrameIsRenderedChannel);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default broadcastThatRootFrameIsRenderedInjectable;
|
||||
@@ -5,7 +5,7 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { initRootFrame } from "./init-root-frame";
|
||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
||||
import ipcRendererInjectable from "../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
import ipcRendererInjectable from "../../../utils/channel/ipc-renderer.injectable";
|
||||
import bindProtocolAddRouteHandlersInjectable from "../../../protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable";
|
||||
import lensProtocolRouterRendererInjectable from "../../../protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable";
|
||||
import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
|
||||
|
||||
@@ -11,17 +11,22 @@ import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import { Notifications } from "../../components/notifications";
|
||||
import { ConfirmDialog } from "../../components/confirm-dialog";
|
||||
import { CommandContainer } from "../../components/command-palette/command-container";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { IpcRendererNavigationEvents } from "../../navigation/events";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import broadcastThatRootFrameIsRenderedInjectable from "./broadcast-that-root-frame-is-rendered.injectable";
|
||||
|
||||
// Todo: remove import-time side-effect.
|
||||
injectSystemCAs();
|
||||
|
||||
interface Dependencies {
|
||||
broadcastThatRootFrameIsRendered: () => void;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class RootFrame extends React.Component {
|
||||
class NonInjectedRootFrame extends React.Component<Dependencies> {
|
||||
static displayName = "RootFrame";
|
||||
|
||||
componentDidMount() {
|
||||
ipcRenderer.send(IpcRendererNavigationEvents.LOADED);
|
||||
this.props.broadcastThatRootFrameIsRendered();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -37,3 +42,14 @@ export class RootFrame extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const RootFrame = withInjectables<Dependencies>(
|
||||
NonInjectedRootFrame,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
broadcastThatRootFrameIsRendered: di.inject(broadcastThatRootFrameIsRenderedInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -7,12 +7,11 @@ import glob from "glob";
|
||||
import { memoize, noop } from "lodash/fp";
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import getValueFromRegisteredChannelInjectable from "./app-paths/get-value-from-registered-channel/get-value-from-registered-channel.injectable";
|
||||
import requestFromChannelInjectable from "./utils/channel/request-from-channel.injectable";
|
||||
import loggerInjectable from "../common/logger.injectable";
|
||||
import { overrideFsWithFakes } from "../test-utils/override-fs-with-fakes";
|
||||
import { createMemoryHistory } from "history";
|
||||
import registerIpcChannelListenerInjectable from "./app-paths/get-value-from-registered-channel/register-ipc-channel-listener.injectable";
|
||||
import focusWindowInjectable from "./ipc-channel-listeners/focus-window.injectable";
|
||||
import focusWindowInjectable from "./navigation/focus-window.injectable";
|
||||
import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable";
|
||||
import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store";
|
||||
import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
|
||||
@@ -32,19 +31,22 @@ import { joinPathsFake } from "../common/test-utils/join-paths-fake";
|
||||
import hotbarStoreInjectable from "../common/hotbars/store.injectable";
|
||||
import terminalSpawningPoolInjectable from "./components/dock/terminal/terminal-spawning-pool.injectable";
|
||||
import hostedClusterIdInjectable from "../common/cluster-store/hosted-cluster-id.injectable";
|
||||
import type { GetDiForUnitTestingOptions } from "../test-utils/get-dis-for-unit-testing";
|
||||
import historyInjectable from "./navigation/history.injectable";
|
||||
import { ApiManager } from "../common/k8s-api/api-manager";
|
||||
import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable";
|
||||
import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable";
|
||||
import apiManagerInjectable from "../common/k8s-api/api-manager/manager.injectable";
|
||||
import ipcRendererInjectable
|
||||
from "./app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
import ipcRendererInjectable from "./utils/channel/ipc-renderer.injectable";
|
||||
import type { IpcRenderer } from "electron";
|
||||
import setupOnApiErrorListenersInjectable from "./api/setup-on-api-errors.injectable";
|
||||
import { observable } from "mobx";
|
||||
import defaultShellInjectable from "./components/+preferences/default-shell.injectable";
|
||||
import appVersionInjectable from "../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import provideInitialValuesForSyncBoxesInjectable from "./utils/sync-box/provide-initial-values-for-sync-boxes.injectable";
|
||||
import requestAnimationFrameInjectable from "./components/animate/request-animation-frame.injectable";
|
||||
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
|
||||
|
||||
export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
|
||||
export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
|
||||
const {
|
||||
doGeneralOverrides = false,
|
||||
} = opts;
|
||||
@@ -65,6 +67,7 @@ export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
|
||||
di.preventSideEffects();
|
||||
|
||||
if (doGeneralOverrides) {
|
||||
di.override(getRandomIdInjectable, () => () => "some-irrelevant-random-id");
|
||||
di.override(isMacInjectable, () => true);
|
||||
di.override(isWindowsInjectable, () => false);
|
||||
di.override(isLinuxInjectable, () => false);
|
||||
@@ -75,8 +78,12 @@ export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
|
||||
di.override(getAbsolutePathInjectable, () => getAbsolutePathFake);
|
||||
di.override(joinPathsInjectable, () => joinPathsFake);
|
||||
|
||||
di.override(appVersionInjectable, () => "1.0.0");
|
||||
|
||||
di.override(historyInjectable, () => createMemoryHistory());
|
||||
|
||||
di.override(requestAnimationFrameInjectable, () => (callback) => callback());
|
||||
|
||||
di.override(lensResourcesDirInjectable, () => "/irrelevant");
|
||||
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
@@ -99,6 +106,9 @@ export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
|
||||
di.override(clusterStoreInjectable, () => ({ getById: (id): Cluster => ({}) as Cluster }) as ClusterStore);
|
||||
|
||||
di.override(setupOnApiErrorListenersInjectable, () => ({ run: () => {} }));
|
||||
di.override(provideInitialValuesForSyncBoxesInjectable, () => ({ run: () => {} }));
|
||||
|
||||
di.override(defaultShellInjectable, () => "some-default-shell");
|
||||
|
||||
di.override(
|
||||
userStoreInjectable,
|
||||
@@ -114,8 +124,7 @@ export const getDiForUnitTesting = (opts: GetDiForUnitTestingOptions = {}) => {
|
||||
|
||||
di.override(apiManagerInjectable, () => new ApiManager());
|
||||
|
||||
di.override(getValueFromRegisteredChannelInjectable, () => () => Promise.resolve(undefined as never));
|
||||
di.override(registerIpcChannelListenerInjectable, () => () => undefined);
|
||||
di.override(requestFromChannelInjectable, () => () => Promise.resolve(undefined as never));
|
||||
|
||||
overrideFsWithFakes(di);
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { Channel } from "../../common/ipc-channel/channel";
|
||||
|
||||
|
||||
export interface IpcChannelListener {
|
||||
channel: Channel<unknown>;
|
||||
handle: (value: any) => void;
|
||||
}
|
||||
|
||||
export const ipcChannelListenerInjectionToken =
|
||||
getInjectionToken<IpcChannelListener>({
|
||||
id: "ipc-channel-listener-injection-token",
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { ipcChannelListenerInjectionToken } from "./ipc-channel-listener-injection-token";
|
||||
import registerIpcChannelListenerInjectable from "../app-paths/get-value-from-registered-channel/register-ipc-channel-listener.injectable";
|
||||
import { beforeFrameStartsInjectionToken } from "../before-frame-starts/before-frame-starts-injection-token";
|
||||
|
||||
const registerIpcChannelListenersInjectable = getInjectable({
|
||||
id: "register-ipc-channel-listeners",
|
||||
|
||||
instantiate: di => ({
|
||||
run: async () => {
|
||||
const registerIpcChannelListener = di.inject(registerIpcChannelListenerInjectable);
|
||||
|
||||
const listeners = di.injectMany(ipcChannelListenerInjectionToken);
|
||||
|
||||
listeners.forEach(listener => {
|
||||
registerIpcChannelListener(listener);
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
injectionToken: beforeFrameStartsInjectionToken,
|
||||
});
|
||||
|
||||
export default registerIpcChannelListenersInjectable;
|
||||
@@ -5,17 +5,19 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import navigateToEntitySettingsInjectable from "../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
|
||||
import type { ListNamespaceForbiddenArgs } from "../../common/ipc/cluster";
|
||||
import { Notifications, notificationsStore } from "../components/notifications";
|
||||
import { Notifications } from "../components/notifications";
|
||||
import { ClusterStore } from "../../common/cluster-store/cluster-store";
|
||||
import { Button } from "../components/button";
|
||||
import type { IpcRendererEvent } from "electron";
|
||||
import React from "react";
|
||||
import notificationsStoreInjectable from "../components/notifications/notifications-store.injectable";
|
||||
|
||||
const listNamespacesForbiddenHandlerInjectable = getInjectable({
|
||||
id: "list-namespaces-forbidden-handler",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToEntitySettings = di.inject(navigateToEntitySettingsInjectable);
|
||||
const notificationsStore = di.inject(notificationsStoreInjectable);
|
||||
|
||||
const notificationLastDisplayedAt = new Map<string, number>();
|
||||
const intervalBetweenNotifications = 1000 * 60; // 60s
|
||||
|
||||
@@ -3,89 +3,14 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import type { IpcRendererEvent } from "electron";
|
||||
import { ipcRenderer } from "electron";
|
||||
import type { UpdateAvailableFromMain, BackchannelArg } from "../../common/ipc";
|
||||
import { areArgsUpdateAvailableFromMain, UpdateAvailableChannel, onCorrect, ipcRendererOn, AutoUpdateChecking, AutoUpdateNoUpdateAvailable } from "../../common/ipc";
|
||||
import { Notifications, notificationsStore } from "../components/notifications";
|
||||
import { Button } from "../components/button";
|
||||
import { isMac } from "../../common/vars";
|
||||
import { onCorrect } from "../../common/ipc";
|
||||
import { Notifications } from "../components/notifications";
|
||||
import { defaultHotbarCells } from "../../common/hotbars/types";
|
||||
import { type ListNamespaceForbiddenArgs, clusterListNamespaceForbiddenChannel, isListNamespaceForbiddenArgs } from "../../common/ipc/cluster";
|
||||
import { hotbarTooManyItemsChannel } from "../../common/ipc/hotbar";
|
||||
|
||||
function sendToBackchannel(backchannel: string, notificationId: string, data: BackchannelArg): void {
|
||||
notificationsStore.remove(notificationId);
|
||||
ipcRenderer.send(backchannel, data);
|
||||
}
|
||||
|
||||
function RenderYesButtons(props: { backchannel: string; notificationId: string }) {
|
||||
if (isMac) {
|
||||
/**
|
||||
* auto-updater's "installOnQuit" is not applicable for macOS as per their docs.
|
||||
*
|
||||
* See: https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/src/AppUpdater.ts#L27-L32
|
||||
*/
|
||||
return (
|
||||
<Button
|
||||
light
|
||||
label="Yes"
|
||||
onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: true })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
light
|
||||
label="Yes, now"
|
||||
onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: true })}
|
||||
/>
|
||||
<Button
|
||||
active
|
||||
outlined
|
||||
label="Yes, later"
|
||||
onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: false })}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, updateInfo]: UpdateAvailableFromMain): void {
|
||||
const notificationId = `update-available:${updateInfo.version}`;
|
||||
|
||||
Notifications.info(
|
||||
(
|
||||
<div className="flex column gaps">
|
||||
<b>Update Available</b>
|
||||
<p>
|
||||
{"Version "}
|
||||
{updateInfo.version}
|
||||
{" of Lens IDE is available and ready to be installed. Would you like to update now?"}
|
||||
</p>
|
||||
<p>Lens should restart automatically, if it doesn't please restart manually. Installed extensions might require updating.</p>
|
||||
<div className="flex gaps row align-left box grow">
|
||||
<RenderYesButtons backchannel={backchannel} notificationId={notificationId} />
|
||||
<Button
|
||||
active
|
||||
outlined
|
||||
label="No"
|
||||
onClick={() => sendToBackchannel(backchannel, notificationId, { doUpdate: false })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
id: notificationId,
|
||||
onClose() {
|
||||
sendToBackchannel(backchannel, notificationId, { doUpdate: false });
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function HotbarTooManyItemsHandler(): void {
|
||||
Notifications.error(`Cannot have more than ${defaultHotbarCells} items pinned to a hotbar`);
|
||||
}
|
||||
@@ -98,12 +23,6 @@ interface Dependencies {
|
||||
}
|
||||
|
||||
export const registerIpcListeners = ({ listNamespacesForbiddenHandler }: Dependencies) => () => {
|
||||
onCorrect({
|
||||
source: ipcRenderer,
|
||||
channel: UpdateAvailableChannel,
|
||||
listener: UpdateAvailableHandler,
|
||||
verifier: areArgsUpdateAvailableFromMain,
|
||||
});
|
||||
onCorrect({
|
||||
source: ipcRenderer,
|
||||
channel: clusterListNamespaceForbiddenChannel,
|
||||
@@ -115,11 +34,4 @@ export const registerIpcListeners = ({ listNamespacesForbiddenHandler }: Depende
|
||||
channel: hotbarTooManyItemsChannel,
|
||||
listener: HotbarTooManyItemsHandler,
|
||||
verifier: (args: unknown[]): args is [] => args.length === 0,
|
||||
});
|
||||
ipcRendererOn(AutoUpdateChecking, () => {
|
||||
Notifications.shortInfo("Checking for updates");
|
||||
});
|
||||
ipcRendererOn(AutoUpdateNoUpdateAvailable, () => {
|
||||
Notifications.shortInfo("No update is currently available");
|
||||
});
|
||||
};
|
||||
});};
|
||||
File renamed without changes.
+14
-12
@@ -3,26 +3,29 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { ipcChannelListenerInjectionToken } from "./ipc-channel-listener-injection-token";
|
||||
import { appNavigationIpcChannel, clusterFrameNavigationIpcChannel } from "../../common/front-end-routing/navigation-ipc-channel";
|
||||
import currentlyInClusterFrameInjectable from "../routes/currently-in-cluster-frame.injectable";
|
||||
import { navigateToUrlInjectionToken } from "../../common/front-end-routing/navigate-to-url-injection-token";
|
||||
import appNavigationChannelInjectable from "../../common/front-end-routing/app-navigation-channel.injectable";
|
||||
import clusterFrameNavigationChannelInjectable from "../../common/front-end-routing/cluster-frame-navigation-channel.injectable";
|
||||
import focusWindowInjectable from "./focus-window.injectable";
|
||||
import { navigateToUrlInjectionToken } from "../../common/front-end-routing/navigate-to-url-injection-token";
|
||||
import { messageChannelListenerInjectionToken } from "../../common/utils/channel/message-channel-listener-injection-token";
|
||||
|
||||
const navigationListenerInjectable = getInjectable({
|
||||
id: "navigation-listener",
|
||||
const navigationChannelListenerInjectable = getInjectable({
|
||||
id: "navigation-channel-listener",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToUrl = di.inject(navigateToUrlInjectionToken);
|
||||
const currentlyInClusterFrame = di.inject(currentlyInClusterFrameInjectable);
|
||||
const appNavigationChannel = di.inject(appNavigationChannelInjectable);
|
||||
const clusterFrameNavigationChannel = di.inject(clusterFrameNavigationChannelInjectable);
|
||||
const focusWindow = di.inject(focusWindowInjectable);
|
||||
const navigateToUrl = di.inject(navigateToUrlInjectionToken);
|
||||
|
||||
return {
|
||||
channel: currentlyInClusterFrame
|
||||
? clusterFrameNavigationIpcChannel
|
||||
: appNavigationIpcChannel,
|
||||
? clusterFrameNavigationChannel
|
||||
: appNavigationChannel,
|
||||
|
||||
handle: (url: string) => {
|
||||
handler: (url: string) => {
|
||||
navigateToUrl(url);
|
||||
|
||||
if (!currentlyInClusterFrame) {
|
||||
@@ -31,8 +34,7 @@ const navigationListenerInjectable = getInjectable({
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: ipcChannelListenerInjectionToken,
|
||||
injectionToken: messageChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default navigationListenerInjectable;
|
||||
export default navigationChannelListenerInjectable;
|
||||
@@ -7,18 +7,22 @@ import { aboutPortForwarding } from "./port-forward-notify";
|
||||
import navigateToPortForwardsInjectable from "../../common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable";
|
||||
import hostedClusterIdInjectable from "../../common/cluster-store/hosted-cluster-id.injectable";
|
||||
import assert from "assert";
|
||||
import notificationsStoreInjectable from "../components/notifications/notifications-store.injectable";
|
||||
|
||||
const aboutPortForwardingInjectable = getInjectable({
|
||||
id: "about-port-forwarding",
|
||||
|
||||
instantiate: (di) => {
|
||||
const hostedClusterId = di.inject(hostedClusterIdInjectable);
|
||||
const notificationsStore = di.inject(notificationsStoreInjectable);
|
||||
const navigateToPortForwards = di.inject(navigateToPortForwardsInjectable);
|
||||
|
||||
assert(hostedClusterId, "Only allowed to notify about port forward errors within a cluster frame");
|
||||
|
||||
return aboutPortForwarding({
|
||||
navigateToPortForwards: di.inject(navigateToPortForwardsInjectable),
|
||||
navigateToPortForwards,
|
||||
hostedClusterId,
|
||||
notificationsStore,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,18 +7,22 @@ import { notifyErrorPortForwarding } from "./port-forward-notify";
|
||||
import navigateToPortForwardsInjectable from "../../common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable";
|
||||
import hostedClusterIdInjectable from "../../common/cluster-store/hosted-cluster-id.injectable";
|
||||
import assert from "assert";
|
||||
import notificationsStoreInjectable from "../components/notifications/notifications-store.injectable";
|
||||
|
||||
const notifyErrorPortForwardingInjectable = getInjectable({
|
||||
id: "notify-error-port-forwarding",
|
||||
|
||||
instantiate: (di) => {
|
||||
const hostedClusterId = di.inject(hostedClusterIdInjectable);
|
||||
const notificationsStore = di.inject(notificationsStoreInjectable);
|
||||
const navigateToPortForwards = di.inject(navigateToPortForwardsInjectable);
|
||||
|
||||
assert(hostedClusterId, "Only allowed to notify about port forward errors within a cluster frame");
|
||||
|
||||
return notifyErrorPortForwarding({
|
||||
navigateToPortForwards: di.inject(navigateToPortForwardsInjectable),
|
||||
navigateToPortForwards,
|
||||
hostedClusterId,
|
||||
notificationsStore,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,17 +5,20 @@
|
||||
|
||||
import React from "react";
|
||||
import { Button } from "../components/button";
|
||||
import { Notifications, notificationsStore } from "../components/notifications";
|
||||
import type { NotificationsStore } from "../components/notifications";
|
||||
import { Notifications } from "../components/notifications";
|
||||
import type { NavigateToPortForwards } from "../../common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable";
|
||||
|
||||
interface AboutPortForwardingDependencies {
|
||||
navigateToPortForwards: NavigateToPortForwards;
|
||||
hostedClusterId: string;
|
||||
notificationsStore: NotificationsStore;
|
||||
}
|
||||
|
||||
export const aboutPortForwarding = ({
|
||||
navigateToPortForwards,
|
||||
hostedClusterId,
|
||||
notificationsStore,
|
||||
}: AboutPortForwardingDependencies) => () => {
|
||||
const notificationId = `port-forward-notification-${hostedClusterId}`;
|
||||
|
||||
@@ -49,12 +52,14 @@ export const aboutPortForwarding = ({
|
||||
interface NotifyErrorPortForwardingDependencies {
|
||||
navigateToPortForwards: NavigateToPortForwards;
|
||||
hostedClusterId: string;
|
||||
notificationsStore: NotificationsStore;
|
||||
}
|
||||
|
||||
|
||||
export const notifyErrorPortForwarding = ({
|
||||
navigateToPortForwards,
|
||||
hostedClusterId,
|
||||
notificationsStore,
|
||||
}: NotifyErrorPortForwardingDependencies) => (msg: string) => {
|
||||
const notificationId = `port-forward-error-notification-${hostedClusterId}`;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import userStoreInjectable from "../../common/user-store/user-store.injectable";
|
||||
import ipcRendererInjectable from "../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
import ipcRendererInjectable from "../utils/channel/ipc-renderer.injectable";
|
||||
import { ThemeStore } from "./store";
|
||||
|
||||
const themeStoreInjectable = getInjectable({
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import ipcRendererInjectable from "../ipc-renderer.injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IpcRendererEvent } from "electron";
|
||||
import { enlistMessageChannelListenerInjectionToken } from "../../../../common/utils/channel/enlist-message-channel-listener-injection-token";
|
||||
import { tentativeParseJson } from "../../../../common/utils/tentative-parse-json";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
|
||||
const enlistMessageChannelListenerInjectable = getInjectable({
|
||||
id: "enlist-message-channel-listener-for-renderer",
|
||||
|
||||
instantiate: (di) => {
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
return ({ channel, handler }) => {
|
||||
const nativeCallback = (_: IpcRendererEvent, message: unknown) => {
|
||||
pipeline(
|
||||
message,
|
||||
tentativeParseJson,
|
||||
handler,
|
||||
);
|
||||
};
|
||||
|
||||
ipcRenderer.on(channel.id, nativeCallback);
|
||||
|
||||
return () => {
|
||||
ipcRenderer.off(channel.id, nativeCallback);
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: enlistMessageChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default enlistMessageChannelListenerInjectable;
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import type { EnlistMessageChannelListener } from "../../../../common/utils/channel/enlist-message-channel-listener-injection-token";
|
||||
import { enlistMessageChannelListenerInjectionToken } from "../../../../common/utils/channel/enlist-message-channel-listener-injection-token";
|
||||
import type { IpcRendererEvent, IpcRenderer } from "electron";
|
||||
import ipcRendererInjectable from "../ipc-renderer.injectable";
|
||||
|
||||
describe("enlist message channel listener in renderer", () => {
|
||||
let enlistMessageChannelListener: EnlistMessageChannelListener;
|
||||
let ipcRendererStub: IpcRenderer;
|
||||
let onMock: jest.Mock;
|
||||
let offMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
onMock = jest.fn();
|
||||
offMock = jest.fn();
|
||||
|
||||
ipcRendererStub = {
|
||||
on: onMock,
|
||||
off: offMock,
|
||||
} as unknown as IpcRenderer;
|
||||
|
||||
di.override(ipcRendererInjectable, () => ipcRendererStub);
|
||||
|
||||
enlistMessageChannelListener = di.inject(
|
||||
enlistMessageChannelListenerInjectionToken,
|
||||
);
|
||||
});
|
||||
|
||||
describe("when called", () => {
|
||||
let handlerMock: jest.Mock;
|
||||
let disposer: () => void;
|
||||
|
||||
beforeEach(() => {
|
||||
handlerMock = jest.fn();
|
||||
|
||||
disposer = enlistMessageChannelListener({
|
||||
channel: { id: "some-channel-id" },
|
||||
handler: handlerMock,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not call handler yet", () => {
|
||||
expect(handlerMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("registers the listener", () => {
|
||||
expect(onMock).toHaveBeenCalledWith(
|
||||
"some-channel-id",
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not de-register the listener yet", () => {
|
||||
expect(offMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when message arrives", () => {
|
||||
beforeEach(() => {
|
||||
onMock.mock.calls[0][1]({} as IpcRendererEvent, "some-message");
|
||||
});
|
||||
|
||||
it("calls the handler with the message", () => {
|
||||
expect(handlerMock).toHaveBeenCalledWith("some-message");
|
||||
});
|
||||
|
||||
it("when disposing the listener, de-registers the listener", () => {
|
||||
disposer();
|
||||
|
||||
expect(offMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
it("given number as message, when message arrives, calls the handler with the message", () => {
|
||||
onMock.mock.calls[0][1]({} as IpcRendererEvent, 42);
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith(42);
|
||||
});
|
||||
|
||||
it("given boolean as message, when message arrives, calls the handler with the message", () => {
|
||||
onMock.mock.calls[0][1]({} as IpcRendererEvent, true);
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("given stringified object as message, when message arrives, calls the handler with the message", () => {
|
||||
onMock.mock.calls[0][1]({} as IpcRendererEvent, JSON.stringify({ some: "object" }));
|
||||
|
||||
expect(handlerMock).toHaveBeenCalledWith({ some: "object" });
|
||||
});
|
||||
});
|
||||
});
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { enlistRequestChannelListenerInjectionToken } from "../../../../common/utils/channel/enlist-request-channel-listener-injection-token";
|
||||
|
||||
const enlistRequestChannelListenerInjectable = getInjectable({
|
||||
id: "enlist-request-channel-listener-for-renderer",
|
||||
|
||||
instantiate: () => {
|
||||
// Requests from main to renderer are not implemented yet.
|
||||
return () => () => {};
|
||||
},
|
||||
|
||||
injectionToken: enlistRequestChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default enlistRequestChannelListenerInjectable;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/before-frame-starts-injection-token";
|
||||
import listeningOfChannelsInjectable from "../../../../common/utils/channel/listening-of-channels.injectable";
|
||||
|
||||
const startListeningOfChannelsInjectable = getInjectable({
|
||||
id: "start-listening-of-channels-renderer",
|
||||
|
||||
instantiate: (di) => {
|
||||
const listeningOfChannels = di.inject(listeningOfChannelsInjectable);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
await listeningOfChannels.start();
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: beforeFrameStartsInjectionToken,
|
||||
});
|
||||
|
||||
export default startListeningOfChannelsInjectable;
|
||||
File renamed without changes.
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { messageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token";
|
||||
import sendToMainInjectable from "./send-to-main.injectable";
|
||||
import type { MessageChannel } from "../../../common/utils/channel/message-channel-injection-token";
|
||||
|
||||
const messageToChannelInjectable = getInjectable({
|
||||
id: "message-to-channel",
|
||||
|
||||
instantiate: (di) => {
|
||||
const sendToMain = di.inject(sendToMainInjectable);
|
||||
|
||||
// TODO: Figure out way to improve typing in internals
|
||||
// Notice that this should be injected using "messageToChannelInjectionToken" which is typed correctly.
|
||||
return (channel: MessageChannel<any>, message?: unknown) => {
|
||||
sendToMain(channel.id, message);
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: messageToChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default messageToChannelInjectable;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { MessageToChannel } from "../../../common/utils/channel/message-to-channel-injection-token";
|
||||
import type { MessageChannel } from "../../../common/utils/channel/message-channel-injection-token";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import { messageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token";
|
||||
import ipcRendererInjectable from "./ipc-renderer.injectable";
|
||||
import type { IpcRenderer } from "electron";
|
||||
|
||||
describe("message to channel from renderer", () => {
|
||||
let messageToChannel: MessageToChannel;
|
||||
let sendMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
sendMock = jest.fn();
|
||||
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
send: sendMock,
|
||||
}) as unknown as IpcRenderer);
|
||||
|
||||
messageToChannel = di.inject(messageToChannelInjectionToken);
|
||||
});
|
||||
|
||||
it("given string as message, when messaging to channel, sends stringified message", () => {
|
||||
messageToChannel(someChannel, "some-message");
|
||||
|
||||
expect(sendMock).toHaveBeenCalledWith("some-channel-id", '"some-message"');
|
||||
});
|
||||
|
||||
it("given boolean as message, when messaging to channel, sends stringified message", () => {
|
||||
messageToChannel(someChannel, true);
|
||||
|
||||
expect(sendMock).toHaveBeenCalledWith("some-channel-id", "true");
|
||||
});
|
||||
|
||||
it("given number as message, when messaging to channel, sends stringified message", () => {
|
||||
messageToChannel(someChannel, 42);
|
||||
|
||||
expect(sendMock).toHaveBeenCalledWith("some-channel-id", "42");
|
||||
});
|
||||
|
||||
it("given object as message, when messaging to channel, sends stringified message", () => {
|
||||
messageToChannel(someChannel, { some: "object" });
|
||||
|
||||
expect(sendMock).toHaveBeenCalledWith(
|
||||
"some-channel-id",
|
||||
JSON.stringify({ some: "object" }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const someChannel: MessageChannel<any> = { id: "some-channel-id" };
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import ipcRendererInjectable from "./ipc-renderer.injectable";
|
||||
import { requestFromChannelInjectionToken } from "../../../common/utils/channel/request-from-channel-injection-token";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { tentativeStringifyJson } from "../../../common/utils/tentative-stringify-json";
|
||||
import { tentativeParseJson } from "../../../common/utils/tentative-parse-json";
|
||||
|
||||
const requestFromChannelInjectable = getInjectable({
|
||||
id: "request-from-channel",
|
||||
|
||||
instantiate: (di) => {
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
return async (channel, ...[request]) =>
|
||||
await pipeline(
|
||||
request,
|
||||
tentativeStringifyJson,
|
||||
(req) => ipcRenderer.invoke(channel.id, req),
|
||||
tentativeParseJson,
|
||||
);
|
||||
},
|
||||
|
||||
injectionToken: requestFromChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default requestFromChannelInjectable;
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { MessageChannel } from "../../../common/utils/channel/message-channel-injection-token";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import ipcRendererInjectable from "./ipc-renderer.injectable";
|
||||
import type { IpcRenderer } from "electron";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { RequestFromChannel } from "../../../common/utils/channel/request-from-channel-injection-token";
|
||||
import { requestFromChannelInjectionToken } from "../../../common/utils/channel/request-from-channel-injection-token";
|
||||
import requestFromChannelInjectable from "./request-from-channel.injectable";
|
||||
import { getPromiseStatus } from "../../../common/test-utils/get-promise-status";
|
||||
|
||||
describe("request from channel in renderer", () => {
|
||||
let requestFromChannel: RequestFromChannel;
|
||||
let invokeMock: AsyncFnMock<(channelId: string, request: any) => any>;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.unoverride(requestFromChannelInjectable);
|
||||
|
||||
invokeMock = asyncFn();
|
||||
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
invoke: invokeMock,
|
||||
}) as unknown as IpcRenderer);
|
||||
|
||||
requestFromChannel = di.inject(requestFromChannelInjectionToken);
|
||||
});
|
||||
|
||||
describe("when messaging to channel", () => {
|
||||
let actualPromise: Promise<any>;
|
||||
|
||||
beforeEach(() => {
|
||||
actualPromise = requestFromChannel(someChannel, "some-message");
|
||||
});
|
||||
|
||||
it("sends stringified message", () => {
|
||||
expect(invokeMock).toHaveBeenCalledWith("some-channel-id", '"some-message"');
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
it("when invoking resolves, resolves", async () => {
|
||||
await invokeMock.resolve("some-response");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toBe("some-response");
|
||||
});
|
||||
|
||||
it("when invoking resolves with stringified string, resolves with string", async () => {
|
||||
await invokeMock.resolve('"some-response"');
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toBe("some-response");
|
||||
});
|
||||
|
||||
it("when invoking resolves with stringified boolean, resolves with boolean", async () => {
|
||||
await invokeMock.resolve("true");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toBe(true);
|
||||
});
|
||||
|
||||
it("when invoking resolves with stringified number, resolves with number", async () => {
|
||||
await invokeMock.resolve("42");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toBe(42);
|
||||
});
|
||||
|
||||
it("when invoking resolves with stringified object, resolves with object", async () => {
|
||||
await invokeMock.resolve(JSON.stringify({ some: "object" }));
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({ some: "object" });
|
||||
});
|
||||
});
|
||||
|
||||
it("given string as message, when messaging to channel, sends stringified message", () => {
|
||||
requestFromChannel(someChannel, "some-message");
|
||||
|
||||
expect(invokeMock).toHaveBeenCalledWith("some-channel-id", '"some-message"');
|
||||
});
|
||||
|
||||
it("given boolean as message, when messaging to channel, sends stringified message", () => {
|
||||
requestFromChannel(someChannel, true);
|
||||
|
||||
expect(invokeMock).toHaveBeenCalledWith("some-channel-id", "true");
|
||||
});
|
||||
|
||||
it("given number as message, when messaging to channel, sends stringified message", () => {
|
||||
requestFromChannel(someChannel, 42);
|
||||
|
||||
expect(invokeMock).toHaveBeenCalledWith("some-channel-id", "42");
|
||||
});
|
||||
|
||||
it("given object as message, when messaging to channel, sends stringified message", () => {
|
||||
requestFromChannel(someChannel, { some: "object" });
|
||||
|
||||
expect(invokeMock).toHaveBeenCalledWith(
|
||||
"some-channel-id",
|
||||
JSON.stringify({ some: "object" }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const someChannel: MessageChannel<any> = { id: "some-channel-id" };
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { JsonValue } from "type-fest";
|
||||
import ipcRendererInjectable from "./ipc-renderer.injectable";
|
||||
import { tentativeStringifyJson } from "../../../common/utils/tentative-stringify-json";
|
||||
|
||||
const sendToMainInjectable = getInjectable({
|
||||
id: "send-to-main",
|
||||
|
||||
instantiate: (di) => {
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
// TODO: Figure out way to improve typing in internals
|
||||
return <T>(channelId: string, message: JsonValue extends T ? T : never ) => {
|
||||
const stringifiedMessage = tentativeStringifyJson(message);
|
||||
|
||||
ipcRenderer.send(channelId, ...(stringifiedMessage ? [stringifiedMessage] : []));
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default sendToMainInjectable;
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token";
|
||||
import syncBoxInitialValueChannelInjectable from "../../../common/utils/sync-box/sync-box-initial-value-channel.injectable";
|
||||
import syncBoxStateInjectable from "../../../common/utils/sync-box/sync-box-state.injectable";
|
||||
import { requestFromChannelInjectionToken } from "../../../common/utils/channel/request-from-channel-injection-token";
|
||||
|
||||
const provideInitialValuesForSyncBoxesInjectable = getInjectable({
|
||||
id: "provide-initial-values-for-sync-boxes",
|
||||
|
||||
instantiate: (di) => {
|
||||
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
|
||||
const syncBoxInitialValueChannel = di.inject(syncBoxInitialValueChannelInjectable);
|
||||
const setSyncBoxState = (id: string, state: any) => di.inject(syncBoxStateInjectable, id).set(state);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
const initialValues = await requestFromChannel(syncBoxInitialValueChannel);
|
||||
|
||||
initialValues.forEach(({ id, value }) => {
|
||||
setSyncBoxState(id, value);
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: beforeFrameStartsInjectionToken,
|
||||
});
|
||||
|
||||
export default provideInitialValuesForSyncBoxesInjectable;
|
||||
Reference in new issue
Block a user