# This is a combination of 2 commits.

# This is the 1st commit message:

Upgrade to electron 12 and Node 14

Signed-off-by: Sebastian Malton <sebastian@malton.name>

# This is the commit message #2:

Fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton committed 2021-08-23 15:54:28 -04:00
1 parent 4219f3c907
commit ac2ce1ba57
47 files changed
+989 -1049

No files matched your search

+2 -3
View File
@@ -37,7 +37,6 @@ import { Console } from "console";
import { SemVer } from "semver";
import electron from "electron";
import { stdout, stderr } from "process";
import { beforeEachWrapped } from "../../../integration/helpers/utils";
import { ThemeStore } from "../../renderer/theme.store";
import type { ClusterStoreModel } from "../cluster-store";
@@ -45,7 +44,7 @@ console = new Console(stdout, stderr);
describe("user store tests", () => {
describe("for an empty config", () => {
beforeEachWrapped(() => {
beforeEach(() => {
mockFs({ tmp: { "config.json": "{}", "kube_config": "{}" } });
(UserStore.createInstance() as any).refreshNewContexts = jest.fn(() => Promise.resolve());
@@ -94,7 +93,7 @@ describe("user store tests", () => {
});
describe("migrations", () => {
beforeEachWrapped(() => {
beforeEach(() => {
mockFs({
"tmp": {
"config.json": JSON.stringify({
+3 -2
View File
@@ -22,7 +22,7 @@
import path from "path";
import Config from "conf";
import type { Options as ConfOptions } from "conf/dist/source/types";
import { app, ipcMain, ipcRenderer, remote } from "electron";
import { ipcMain, ipcRenderer } from "electron";
import { IReactionOptions, makeObservable, reaction, runInAction } from "mobx";
import { getAppVersion, Singleton, toJS, Disposer } from "./utils";
import logger from "../main/logger";
@@ -30,6 +30,7 @@ import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc";
import isEqual from "lodash/isEqual";
import { isTestEnv } from "./vars";
import { kebabCase } from "lodash";
import { getPath } from "./utils/getPath";
export interface BaseStoreParams<T> extends ConfOptions<T> {
syncOptions?: IReactionOptions;
@@ -88,7 +89,7 @@ export abstract class BaseStore<T> extends Singleton {
}
protected cwd() {
return (app || remote.app).getPath("userData");
return getPath("userData");
}
protected async saveToFile(model: T) {
+2
View File
@@ -19,6 +19,8 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export const dialogShowOpenDialogHandler = "dialog:show-open-dialog";
export * from "./ipc";
export * from "./invalid-kubeconfig";
export * from "./update-available.ipc";
+4 -1
View File
@@ -23,12 +23,14 @@
// https://www.electronjs.org/docs/api/ipc-main
// https://www.electronjs.org/docs/api/ipc-renderer
import { ipcMain, ipcRenderer, remote, webContents } from "electron";
import { ipcMain, ipcRenderer, webContents } from "electron";
import { toJS } from "../utils/toJS";
import logger from "../../main/logger";
import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames";
import type { Disposer } from "../utils";
const remote = ipcMain ? null : require("@electron/remote");
const subFramesChannel = "ipc:get-sub-frames";
export async function requestMain(channel: string, ...args: any[]) {
@@ -46,6 +48,7 @@ function getSubFrames(): ClusterFrameInfo[] {
}
export function broadcastMessage(channel: string, ...args: any[]) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const views = (webContents || remote?.webContents)?.getAllWebContents();
if (!views) return;
+7 -6
View File
@@ -19,7 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { app, remote } from "electron";
import { app } from "electron";
import semver from "semver";
import { action, computed, observable, reaction, makeObservable } from "mobx";
import { BaseStore } from "../base-store";
@@ -32,7 +32,8 @@ import { fileNameMigration } from "../../migrations/user-store";
import { ObservableToggleSet, toJS } from "../../renderer/utils";
import { DESCRIPTORS, KubeconfigSyncValue, UserPreferencesModel, EditorConfiguration } from "./preferences-helpers";
import logger from "../../main/logger";
import type {monaco} from "react-monaco-editor";
import type { monaco } from "react-monaco-editor";
import { getPath } from "../utils/getPath";
export interface UserStoreModel {
lastSeenAppVersion: string;
@@ -69,7 +70,7 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
@observable shell?: string;
@observable downloadBinariesPath?: string;
@observable kubectlBinariesPath?: string;
/**
* Download kubectl binaries matching cluster version
*/
@@ -86,7 +87,7 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
* Monaco editor configs
*/
@observable editorConfiguration:EditorConfiguration = {tabSize: null, miniMap: null, lineNumbers: null};
/**
* The set of file/folder paths to be synced
*/
@@ -115,7 +116,7 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
});
}, {
fireImmediately: true,
});
});
}
// Returns monaco editor options for selected editor type (the place, where a particular instance of the editor is mounted)
@@ -244,5 +245,5 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
* @returns string
*/
export function getDefaultKubectlDownloadPath(): string {
return path.join((app || remote.app).getPath("userData"), "binaries");
return path.join(getPath("userData"), "binaries");
}
+38
View File
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { app, ipcMain } from "electron";
const remote = ipcMain ? null : require("@electron/remote");
/**
* calls getPath either on app or on the remote's app
*
* @deprecated Use a different method for accessing the getPath function
*/
export function getPath(name: Parameters<typeof app["getPath"]>[0]): string {
if (app) {
return app.getPath(name);
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
return remote.app.getPath(name);
}
+1
View File
@@ -40,6 +40,7 @@ export * from "./downloadFile";
export * from "./formatDuration";
export * from "./escapeRegExp";
export * from "./extended-map";
export * from "./getPath";
export * from "./getRandId";
export * from "./hash-set";
export * from "./local-kubeconfig";
+3 -3
View File
@@ -19,14 +19,14 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { app, ipcRenderer, remote } from "electron";
import { ipcRenderer } from "electron";
import { EventEmitter } from "events";
import { isEqual } from "lodash";
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
import path from "path";
import { ClusterStore } from "../common/cluster-store";
import { broadcastMessage, ipcMainOn, ipcRendererOn, requestMain, ipcMainHandle } from "../common/ipc";
import { Disposer, getHostedClusterId, Singleton, toJS } from "../common/utils";
import { Disposer, getHostedClusterId, Singleton, toJS, getPath } from "../common/utils";
import logger from "../main/logger";
import type { InstalledExtension } from "./extension-discovery";
import { ExtensionsStore } from "./extensions-store";
@@ -36,7 +36,7 @@ import type { LensRendererExtension } from "./lens-renderer-extension";
import * as registries from "./registries";
export function extensionPackagesRoot() {
return path.join((app || remote.app).getPath("userData"));
return path.join(getPath("userData"));
}
const logModule = "[EXTENSIONS-LOADER]";
+2 -1
View File
@@ -33,7 +33,8 @@ import { VersionDetector } from "./cluster-detectors/version-detector";
import { DetectorRegistry } from "./cluster-detectors/detector-registry";
import plimit from "p-limit";
import { toJS } from "../common/utils";
import { initialNodeShellImage, ClusterState, ClusterMetadataKey, ClusterRefreshOptions, ClusterStatus, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../common/cluster-types";
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../common/cluster-types";
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../common/cluster-types";
/**
* Cluster
+2 -2
View File
@@ -21,13 +21,13 @@
import { randomBytes } from "crypto";
import { SHA256 } from "crypto-js";
import { app, remote } from "electron";
import fse from "fs-extra";
import { action, makeObservable, observable } from "mobx";
import path from "path";
import { BaseStore } from "../common/base-store";
import type { LensExtensionId } from "../extensions/lens-extension";
import { toJS } from "../common/utils";
import { getPath } from "../common/utils/getPath";
interface FSProvisionModel {
extensions: Record<string, string>; // extension names to paths
@@ -55,7 +55,7 @@ export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
if (!this.registeredExtensions.has(extensionName)) {
const salt = randomBytes(32).toString("hex");
const hashedName = SHA256(`${extensionName}/${salt}`).toString();
const dirPath = path.resolve((app || remote.app).getPath("userData"), "extension_data", hashedName);
const dirPath = path.resolve(getPath("userData"), "extension_data", hashedName);
this.registeredExtensions.set(extensionName, dirPath);
}
+6
View File
@@ -22,6 +22,7 @@
// Main process
import "../common/system-ca";
import { initialize as initializeRemote } from "@electron/remote/main";
import * as Mobx from "mobx";
import * as LensExtensionsCommonApi from "../extensions/common-api";
import * as LensExtensionsMainApi from "../extensions/main-api";
@@ -88,6 +89,7 @@ if (process.env.LENS_DISABLE_GPU) {
app.disableHardwareAcceleration();
}
initializeRemote();
configurePackages();
mangleProxyEnv();
initializers.initIpcMainHandlers();
@@ -271,6 +273,10 @@ app.on("activate", (event, hasVisibleWindows) => {
*/
let blockQuit = true;
if (process.argv.includes("--integration-testing")) {
blockQuit = false;
}
autoUpdater.on("before-quit-for-update", () => blockQuit = false);
app.on("will-quit", (event) => {
+9 -2
View File
@@ -19,20 +19,21 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { IpcMainInvokeEvent } from "electron";
import { BrowserWindow, dialog, IpcMainInvokeEvent } from "electron";
import { KubernetesCluster } from "../../common/catalog-entities";
import { clusterFrameMap } from "../../common/cluster-frames";
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterDeleteHandler } from "../../common/cluster-ipc";
import { ClusterStore } from "../../common/cluster-store";
import type { ClusterId } from "../../common/cluster-types";
import { appEventBus } from "../../common/event-bus";
import { ipcMainHandle } from "../../common/ipc";
import { dialogShowOpenDialogHandler, ipcMainHandle } from "../../common/ipc";
import { catalogEntityRegistry } from "../catalog";
import { ClusterManager } from "../cluster-manager";
import { bundledKubectlPath } from "../kubectl";
import logger from "../logger";
import { promiseExecFile } from "../promise-exec";
import { ResourceApplier } from "../resource-applier";
import { WindowManager } from "../window-manager";
export function initIpcMainHandlers() {
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
@@ -138,4 +139,10 @@ export function initIpcMainHandlers() {
throw `${clusterId} is not a valid cluster id`;
}
});
ipcMainHandle(dialogShowOpenDialogHandler, async (event, dialogOpts: Electron.OpenDialogOptions) => {
await WindowManager.getInstance().ensureMainWindow();
return dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), dialogOpts);
});
}
+2 -2
View File
@@ -19,7 +19,6 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { app, remote } from "electron";
import path from "path";
import fs from "fs";
import { promiseExec } from "./promise-exec";
@@ -32,6 +31,7 @@ import { customRequest } from "../common/request";
import { getBundledKubectlVersion } from "../common/utils/app-version";
import { isDevelopment, isWindows, isTestEnv } from "../common/vars";
import { SemVer } from "semver";
import { getPath } from "../common/utils/getPath";
const bundledVersion = getBundledKubectlVersion();
const kubectlMap: Map<string, string> = new Map([
@@ -84,7 +84,7 @@ export class Kubectl {
protected dirname: string;
static get kubectlDir() {
return path.join((app || remote.app).getPath("userData"), "binaries", "kubectl");
return path.join(getPath("userData"), "binaries", "kubectl");
}
public static readonly bundledKubectlVersion: string = bundledVersion;
+9 -3
View File
@@ -70,6 +70,7 @@ export function buildMenu(windowManager: WindowManager) {
const macAppMenu: MenuItemConstructorOptions = {
label: app.getName(),
id: "Application",
submenu: [
{
label: `About ${productName}`,
@@ -80,10 +81,11 @@ export function buildMenu(windowManager: WindowManager) {
{ type: "separator" },
{
label: "Preferences",
id: "Preferences",
accelerator: "CmdOrCtrl+,",
click() {
navigate(preferencesURL());
}
},
},
{
label: "Extensions",
@@ -106,10 +108,11 @@ export function buildMenu(windowManager: WindowManager) {
exitApp();
}
}
]
],
};
const fileMenu: MenuItemConstructorOptions = {
label: "File",
id: "File",
submenu: [
{
label: "Add Cluster",
@@ -122,6 +125,7 @@ export function buildMenu(windowManager: WindowManager) {
{ type: "separator" },
{
label: "Preferences",
id: "Preferences",
accelerator: "Ctrl+,",
click() {
navigate(preferencesURL());
@@ -150,7 +154,7 @@ export function buildMenu(windowManager: WindowManager) {
}
}
])
]
],
};
const editMenu: MenuItemConstructorOptions = {
label: "Edit",
@@ -168,6 +172,7 @@ export function buildMenu(windowManager: WindowManager) {
};
const viewMenu: MenuItemConstructorOptions = {
label: "View",
id: "View",
submenu: [
{
label: "Catalog",
@@ -216,6 +221,7 @@ export function buildMenu(windowManager: WindowManager) {
};
const helpMenu: MenuItemConstructorOptions = {
role: "help",
id: "help",
submenu: [
{
label: "Welcome",
+6 -2
View File
@@ -89,7 +89,8 @@ export class WindowManager extends Singleton {
nodeIntegration: true,
nodeIntegrationInSubFrames: true,
enableRemoteModule: true,
webviewTag: true
webviewTag: true,
contextIsolation: false,
},
});
this.windowState.manage(this.mainWindow);
@@ -257,7 +258,10 @@ export class WindowManager extends Singleton {
resizable: false,
show: false,
webPreferences: {
nodeIntegration: true
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false,
nodeIntegrationInSubFrames: true,
}
});
await this.splashWindow.loadURL("static://splash.html");
@@ -20,11 +20,11 @@
*/
import fse from "fs-extra";
import { app, remote } from "electron";
import path from "path";
import { getPath } from "../../common/utils/getPath";
export function fileNameMigration() {
const userDataPath = (app || remote.app).getPath("userData");
const userDataPath = getPath("userData");
const configJsonPath = path.join(userDataPath, "config.json");
const lensUserStoreJsonPath = path.join(userDataPath, "lens-user-store.json");
@@ -86,6 +86,7 @@ export class HelmCharts extends Component<Props> {
[columnId.name]: chart => chart.getName(),
[columnId.repo]: chart => chart.getRepository(),
}}
renderHeaderTitle="Helm Charts"
searchFilters={[
chart => chart.getName(),
chart => chart.getVersion(),
@@ -96,7 +97,7 @@ export class HelmCharts extends Component<Props> {
searchProps: {
...searchProps,
placeholder: "Search Helm Charts...",
},
}
})}
renderTableHeader={[
{ className: "icon", showWithColumn: columnId.name },
@@ -138,7 +138,7 @@ export class HelmReleases extends Component<Props> {
},
...headerPlaceholders,
})}
renderHeaderTitle="Releases"
renderHeaderTitle="Helm Releases"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
@@ -21,7 +21,7 @@
import "./extensions.scss";
import { remote, shell } from "electron";
import { shell } from "electron";
import fse from "fs-extra";
import _ from "lodash";
import { makeObservable, observable, reaction, when } from "mobx";
@@ -46,6 +46,8 @@ import { InstalledExtensions } from "./installed-extensions";
import { Notice } from "./notice";
import { SettingLayout } from "../layout/setting-layout";
import { docsUrl } from "../../../common/vars";
import { dialog } from "../../remote-helpers";
import { getPath } from "../../../common/utils/getPath";
function getMessageFromError(error: any): string {
if (!error || typeof error !== "object") {
@@ -466,9 +468,8 @@ async function installFromInput(input: string) {
const supportedFormats = ["tar", "tgz"];
async function installFromSelectFileDialog() {
const { dialog, BrowserWindow, app } = remote;
const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), {
defaultPath: app.getPath("downloads"),
const { canceled, filePaths } = await dialog.showOpenDialog({
defaultPath: getPath("downloads"),
properties: ["openFile", "multiSelections"],
message: `Select extensions to install (formats: ${supportedFormats.join(", ")}), `,
buttonLabel: "Use configuration",
@@ -22,7 +22,7 @@
import "./add-helm-repo-dialog.scss";
import React from "react";
import { remote, FileFilter } from "electron";
import type { FileFilter } from "electron";
import { observable, makeObservable } from "mobx";
import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog";
@@ -35,6 +35,7 @@ import { SubTitle } from "../layout/sub-title";
import { Icon } from "../icon";
import { Notifications } from "../notifications";
import { HelmRepo, HelmRepoManager } from "../../../main/helm/helm-repo-manager";
import { dialog } from "../../remote-helpers";
interface Props extends Partial<DialogProps> {
onAddRepo: Function
@@ -88,8 +89,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
}
async selectFileDialog(type: FileType, fileFilter: FileFilter) {
const { dialog, BrowserWindow } = remote;
const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), {
const { canceled, filePaths } = await dialog.showOpenDialog({
defaultPath: this.getFilePath(type),
properties: ["openFile", "showHiddenFiles"],
message: `Select file`,
@@ -50,6 +50,7 @@ export async function initView(clusterId: ClusterId) {
const parentElem = document.getElementById("lens-views");
const iframe = document.createElement("iframe");
iframe.id = `cluster-frame-${cluster.id}`;
iframe.name = cluster.contextName;
iframe.setAttribute("src", getClusterFrameUrl(clusterId));
iframe.addEventListener("load", () => {
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export * from "./path-picker";
@@ -27,7 +27,7 @@ import { UserStore } from "../../common/user-store";
import { getAllEntries } from "../components/+preferences/kubeconfig-syncs";
import { runInAction } from "mobx";
import { isWindows } from "../../common/vars";
import { PathPicker } from "../components/path-picker/path-picker";
import { PathPicker } from "../components/path-picker";
import { Notifications } from "../components/notifications";
import { Link } from "react-router-dom";
+1 -1
View File
@@ -117,7 +117,7 @@ function HotbarTooManyItemsHandler(): void {
Notifications.error(`Cannot have more than ${defaultHotbarCells} items pinned to a hotbar`);
}
export function registerIpcHandlers() {
export function registerIpcListeners() {
onCorrect({
source: ipcRenderer,
channel: UpdateAvailableChannel,
+88
View File
@@ -0,0 +1,88 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { ipcRenderer } from "electron";
import { navigate } from "../navigation";
/**
* The definition of a keyboard shortcut
*/
interface Shortcut {
code?: string;
key?: string;
metaKey?: boolean;
altKey?: boolean;
shiftKey?: boolean;
ctrlKey?: boolean;
action: () => void;
}
const shortcuts: Shortcut[] = [
{
key: "p",
metaKey: true,
shiftKey: true,
action: () => ipcRenderer.emit("command-palette:open"),
},
{
code: "Comma",
metaKey: true,
action: () => navigate("/preferences"),
},
];
function shortcutMatches(shortcut: Shortcut, event: KeyboardEvent): boolean {
if (typeof shortcut.metaKey === "boolean" && shortcut.metaKey !== event.metaKey) {
return false;
}
if (typeof shortcut.altKey === "boolean" && shortcut.altKey !== event.altKey) {
return false;
}
if (typeof shortcut.shiftKey === "boolean" && shortcut.shiftKey !== event.shiftKey) {
return false;
}
if (typeof shortcut.ctrlKey === "boolean" && shortcut.ctrlKey !== event.ctrlKey) {
return false;
}
if (typeof shortcut.code === "string" && shortcut.code !== event.code) {
return false;
}
if (typeof shortcut.key === "string" && shortcut.key !== event.key) {
return false;
}
return true;
}
export function registerKeyboardShortcuts() {
window.addEventListener("keydown", event => {
for (const shortcut of shortcuts) {
if (shortcutMatches(shortcut, event)) {
shortcut.action();
}
}
});
}
+4 -2
View File
@@ -32,10 +32,11 @@ import { ExtensionLoader } from "../extensions/extension-loader";
import { broadcastMessage } from "../common/ipc";
import { CommandContainer } from "./components/command-palette/command-container";
import { bindProtocolAddRouteHandlers, LensProtocolRouterRenderer } from "./protocol-handler";
import { registerIpcHandlers } from "./ipc";
import { registerIpcListeners } from "./ipc";
import { ipcRenderer } from "electron";
import { IpcRendererNavigationEvents } from "./navigation/events";
import { catalogEntityRegistry } from "./api/catalog-entity-registry";
import { registerKeyboardShortcuts } from "./keyboard-shortcuts";
@observer
export class LensApp extends React.Component {
@@ -48,7 +49,8 @@ export class LensApp extends React.Component {
window.addEventListener("offline", () => broadcastMessage("network:offline"));
window.addEventListener("online", () => broadcastMessage("network:online"));
registerIpcHandlers();
registerKeyboardShortcuts();
registerIpcListeners();
}
componentDidMount() {
+26
View File
@@ -0,0 +1,26 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { dialogShowOpenDialogHandler, requestMain } from "../../common/ipc";
export async function showOpenDialog(options: Electron.OpenDialogOptions): Promise<Electron.OpenDialogReturnValue> {
return requestMain(dialogShowOpenDialogHandler, options);
}
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import * as dialog from "./dialog";
export { dialog };
+29 -31
View File
@@ -22,13 +22,12 @@
// Keeps window.localStorage state in external JSON-files.
// Because app creates random port between restarts => storage session wiped out each time.
import path from "path";
import { app, remote } from "electron";
import { comparer, observable, reaction, toJS, when } from "mobx";
import fse from "fs-extra";
import { StorageHelper } from "./storageHelper";
import { ClusterStore } from "../../common/cluster-store";
import logger from "../../main/logger";
import { getHostedClusterId } from "../../common/utils";
import { getHostedClusterId, getPath, noop } from "../../common/utils";
const storage = observable({
initialized: false,
@@ -47,15 +46,11 @@ export function createStorage<T>(key: string, defaultValue: T) {
export function createAppStorage<T>(key: string, defaultValue: T, clusterId?: string | undefined) {
const { logPrefix } = StorageHelper;
const folder = path.resolve((app || remote.app).getPath("userData"), "lens-local-storage");
const folder = path.resolve(getPath("userData"), "lens-local-storage");
const fileName = `${clusterId ?? "app"}.json`;
const filePath = path.resolve(folder, fileName);
if (!storage.initialized) {
init(); // called once per cluster-view
}
function init() {
if (!storage.initialized && !process.argv.includes("--integration-tests")) {
storage.initialized = true;
// read previously saved state (if any)
@@ -68,32 +63,35 @@ export function createAppStorage<T>(key: string, defaultValue: T, clusterId?: st
});
// bind auto-saving data changes to %storage-file.json
reaction(() => toJS(storage.data), saveFile, {
delay: 250, // lazy, avoid excessive writes to fs
equals: comparer.structural, // save only when something really changed
});
reaction(
() => toJS(storage.data),
async (state: Record<string, any> = {}) => {
logger.info(`${logPrefix} saving ${filePath}`);
try {
await fse.ensureDir(folder, { mode: 0o755 });
await fse.writeJson(filePath, state, { spaces: 2 });
} catch (error) {
logger.error(`${logPrefix} saving failed: ${error}`, {
json: state, jsonFilePath: filePath
});
}
},
{
delay: 250, // lazy, avoid excessive writes to fs
equals: comparer.structural, // save only when something really changed
},
);
// remove json-file when cluster deleted
if (clusterId !== undefined) {
when(() => ClusterStore.getInstance(false)?.removedClusters.has(clusterId)).then(removeFile);
}
async function saveFile(state: Record<string, any> = {}) {
logger.info(`${logPrefix} saving ${filePath}`);
try {
await fse.ensureDir(folder, { mode: 0o755 });
await fse.writeJson(filePath, state, { spaces: 2 });
} catch (error) {
logger.error(`${logPrefix} saving failed: ${error}`, {
json: state, jsonFilePath: filePath
});
}
}
function removeFile() {
logger.debug(`${logPrefix} removing ${filePath}`);
fse.unlink(filePath).catch(Function);
when(
() => ClusterStore.getInstance(false)?.removedClusters.has(clusterId),
() => {
logger.debug(`${logPrefix} removing ${filePath}`);
fse.unlink(filePath).catch(noop);
}
);
}
}