mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Open file picker dialog first before switching to preferences page
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
207f0dfd62
commit
3d06f8a765
@ -20,12 +20,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
||||||
import { CatalogEntity, CatalogEntityActionContext, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
import { CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
||||||
import { clusterActivateHandler, clusterDeleteHandler, clusterDisconnectHandler } from "../cluster-ipc";
|
import { clusterActivateHandler, clusterDeleteHandler, clusterDisconnectHandler } from "../cluster-ipc";
|
||||||
import { ClusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store";
|
||||||
import { requestMain } from "../ipc";
|
import { requestMain } from "../ipc";
|
||||||
import { CatalogCategory, CatalogCategorySpec } from "../catalog";
|
import { CatalogCategory, CatalogCategorySpec } from "../catalog";
|
||||||
import { addClusterURL, preferencesURL } from "../routes";
|
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import type { CatalogEntitySpec } from "../catalog/catalog-entity";
|
import type { CatalogEntitySpec } from "../catalog/catalog-entity";
|
||||||
import { HotbarStore } from "../hotbar-store";
|
import { HotbarStore } from "../hotbar-store";
|
||||||
@ -148,7 +147,7 @@ export class KubernetesCluster extends CatalogEntity<KubernetesClusterMetadata,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KubernetesClusterCategory extends CatalogCategory {
|
class KubernetesClusterCategory extends CatalogCategory {
|
||||||
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
|
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||||
public readonly kind = "CatalogCategory";
|
public readonly kind = "CatalogCategory";
|
||||||
public metadata = {
|
public metadata = {
|
||||||
@ -167,25 +166,8 @@ export class KubernetesClusterCategory extends CatalogCategory {
|
|||||||
kind: "KubernetesCluster"
|
kind: "KubernetesCluster"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.on("catalogAddMenu", (ctx: CatalogEntityAddMenuContext) => {
|
|
||||||
ctx.menuItems.push(
|
|
||||||
{
|
|
||||||
icon: "text_snippet",
|
|
||||||
title: "Add from kubeconfig",
|
|
||||||
onClick: () => ctx.navigate(addClusterURL()),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "settings",
|
|
||||||
title: "Sync kubeconfig file(s)",
|
|
||||||
onClick: () => ctx.navigate(preferencesURL({ fragment: "kube-sync" })),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catalogCategoryRegistry.add(new KubernetesClusterCategory());
|
export const kubernetesClusterCategory = new KubernetesClusterCategory();
|
||||||
|
|
||||||
|
catalogCategoryRegistry.add(kubernetesClusterCategory);
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import type TypedEmitter from "typed-emitter";
|
import type TypedEmitter from "typed-emitter";
|
||||||
import { observable, makeObservable } from "mobx";
|
import { observable, makeObservable } from "mobx";
|
||||||
|
import type React from "react";
|
||||||
|
|
||||||
type ExtractEntityMetadataType<Entity> = Entity extends CatalogEntity<infer Metadata> ? Metadata : never;
|
type ExtractEntityMetadataType<Entity> = Entity extends CatalogEntity<infer Metadata> ? Metadata : never;
|
||||||
type ExtractEntityStatusType<Entity> = Entity extends CatalogEntity<any, infer Status> ? Status : never;
|
type ExtractEntityStatusType<Entity> = Entity extends CatalogEntity<any, infer Status> ? Status : never;
|
||||||
@ -108,7 +109,7 @@ export interface CatalogEntityContextMenu {
|
|||||||
/**
|
/**
|
||||||
* Menu icon
|
* Menu icon
|
||||||
*/
|
*/
|
||||||
icon?: string;
|
icon?: string | React.ComponentType;
|
||||||
/**
|
/**
|
||||||
* OnClick handler
|
* OnClick handler
|
||||||
*/
|
*/
|
||||||
@ -122,7 +123,7 @@ export interface CatalogEntityContextMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CatalogEntityAddMenu extends CatalogEntityContextMenu {
|
export interface CatalogEntityAddMenu extends CatalogEntityContextMenu {
|
||||||
icon: string;
|
icon: string | React.ComponentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CatalogEntitySettingsMenu {
|
export interface CatalogEntitySettingsMenu {
|
||||||
|
|||||||
@ -21,6 +21,12 @@
|
|||||||
|
|
||||||
import { action, ObservableMap } from "mobx";
|
import { action, ObservableMap } from "mobx";
|
||||||
|
|
||||||
|
export function multiSet<T, V>(map: Map<T, V>, newEntries: [T, V][]): void {
|
||||||
|
for (const [key, val] of newEntries) {
|
||||||
|
map.set(key, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ExtendedMap<K, V> extends Map<K, V> {
|
export class ExtendedMap<K, V> extends Map<K, V> {
|
||||||
static new<K, V>(entries?: readonly (readonly [K, V])[] | null): ExtendedMap<K, V> {
|
static new<K, V>(entries?: readonly (readonly [K, V])[] | null): ExtendedMap<K, V> {
|
||||||
return new ExtendedMap<K, V>(entries);
|
return new ExtendedMap<K, V>(entries);
|
||||||
|
|||||||
@ -74,6 +74,7 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
rootElem.classList.toggle("is-mac", isMac);
|
rootElem.classList.toggle("is-mac", isMac);
|
||||||
|
|
||||||
initializers.initRegistries();
|
initializers.initRegistries();
|
||||||
|
initializers.initCatalogCategoryRegistryEntries();
|
||||||
initializers.initAppPreferenceKindRegistry();
|
initializers.initAppPreferenceKindRegistry();
|
||||||
initializers.initAppPreferenceRegistry();
|
initializers.initAppPreferenceRegistry();
|
||||||
initializers.initCommandRegistry();
|
initializers.initCommandRegistry();
|
||||||
|
|||||||
@ -97,7 +97,7 @@ export class CatalogAddButton extends React.Component<CatalogAddButtonProps> {
|
|||||||
{ this.menuItems.map((menuItem, index) => {
|
{ this.menuItems.map((menuItem, index) => {
|
||||||
return <SpeedDialAction
|
return <SpeedDialAction
|
||||||
key={index}
|
key={index}
|
||||||
icon={<Icon material={menuItem.icon} />}
|
icon={typeof menuItem.icon === "string" ? <Icon material={menuItem.icon} /> : <menuItem.icon />}
|
||||||
tooltipTitle={menuItem.title}
|
tooltipTitle={menuItem.title}
|
||||||
onClick={() => menuItem.onClick()}
|
onClick={() => menuItem.onClick()}
|
||||||
TooltipClasses={{
|
TooltipClasses={{
|
||||||
|
|||||||
@ -31,11 +31,24 @@ import { ConfirmDialog } from "../confirm-dialog";
|
|||||||
import { HotbarStore } from "../../../common/hotbar-store";
|
import { HotbarStore } from "../../../common/hotbar-store";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import type { CatalogEntityItem } from "./catalog-entity.store";
|
import type { CatalogEntityItem } from "./catalog-entity.store";
|
||||||
|
import { Tooltip } from "@material-ui/core";
|
||||||
|
|
||||||
export interface CatalogEntityDrawerMenuProps<T extends CatalogEntity> extends MenuActionsProps {
|
export interface CatalogEntityDrawerMenuProps<T extends CatalogEntity> extends MenuActionsProps {
|
||||||
item: CatalogEntityItem<T> | null | undefined;
|
item: CatalogEntityItem<T> | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveIcon(item: CatalogEntityContextMenu) {
|
||||||
|
if (typeof item.icon === "string") {
|
||||||
|
if (item.icon.includes("<svg")) {
|
||||||
|
return <Icon title={item.title} svg={item.icon} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Icon title={item.title} material={item.icon} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Tooltip title={item.title}><item.icon /></Tooltip>;
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Component<CatalogEntityDrawerMenuProps<T>> {
|
export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Component<CatalogEntityDrawerMenuProps<T>> {
|
||||||
@observable private contextMenu: CatalogEntityContextMenuContext;
|
@observable private contextMenu: CatalogEntityContextMenuContext;
|
||||||
@ -86,14 +99,9 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = menuItem.icon.includes("<svg") ? "svg" : "material";
|
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
<MenuItem key={menuItem.title} onClick={() => this.onMenuItemClick(menuItem)}>
|
<MenuItem key={menuItem.title} onClick={() => this.onMenuItemClick(menuItem)}>
|
||||||
<Icon
|
{resolveIcon(menuItem)}
|
||||||
tooltip={menuItem.title}
|
|
||||||
{...{ [key]: menuItem.icon }}
|
|
||||||
/>
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -109,7 +117,7 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, item: entity, ...menuProps } = this.props;
|
const { className, item: entity, ...menuProps } = this.props;
|
||||||
|
|
||||||
if (!this.contextMenu || !entity.enabled) {
|
if (!this.contextMenu || !entity.enabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import fse from "fs-extra";
|
|||||||
import { KubeconfigSyncEntry, KubeconfigSyncValue, UserStore } from "../../../common/user-store";
|
import { KubeconfigSyncEntry, KubeconfigSyncValue, UserStore } from "../../../common/user-store";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import logger from "../../../main/logger";
|
import logger from "../../../main/logger";
|
||||||
import { iter } from "../../utils";
|
import { iter, multiSet } from "../../utils";
|
||||||
import { isWindows } from "../../../common/vars";
|
import { isWindows } from "../../../common/vars";
|
||||||
import { PathPicker } from "../path-picker";
|
import { PathPicker } from "../path-picker";
|
||||||
|
|
||||||
@ -68,6 +68,10 @@ async function getMapEntry({ filePath, ...data}: KubeconfigSyncEntry): Promise<[
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAllEntries(filePaths: string[]): Promise<[string, Value][]> {
|
||||||
|
return Promise.all(filePaths.map(filePath => getMapEntry({ filePath })));
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class KubeconfigSyncs extends React.Component {
|
export class KubeconfigSyncs extends React.Component {
|
||||||
syncs = observable.map<string, Value>();
|
syncs = observable.map<string, Value>();
|
||||||
@ -105,13 +109,7 @@ export class KubeconfigSyncs extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
onPick = async (filePaths: string[]) => {
|
onPick = async (filePaths: string[]) => multiSet(this.syncs, await getAllEntries(filePaths));
|
||||||
const newEntries = await Promise.all(filePaths.map(filePath => getMapEntry({ filePath })));
|
|
||||||
|
|
||||||
for (const [filePath, info] of newEntries) {
|
|
||||||
this.syncs.set(filePath, info);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
renderEntryIcon(entry: Entry) {
|
renderEntryIcon(entry: Entry) {
|
||||||
switch (entry.info.type) {
|
switch (entry.info.type) {
|
||||||
|
|||||||
@ -25,12 +25,10 @@ import React from "react";
|
|||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
|
|
||||||
export interface PathPickerProps {
|
export interface PathPickOpts {
|
||||||
className?: string;
|
|
||||||
label: string;
|
label: string;
|
||||||
disabled?: boolean;
|
onPick?: (paths: string[]) => any;
|
||||||
onPick?: (paths: string[]) => void;
|
onCancel?: () => any;
|
||||||
onCancel?: () => void;
|
|
||||||
defaultPath?: string;
|
defaultPath?: string;
|
||||||
buttonLabel?: string;
|
buttonLabel?: string;
|
||||||
filters?: FileFilter[];
|
filters?: FileFilter[];
|
||||||
@ -38,10 +36,15 @@ export interface PathPickerProps {
|
|||||||
securityScopedBookmarks?: boolean;
|
securityScopedBookmarks?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PathPickerProps extends PathPickOpts {
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class PathPicker extends React.Component<PathPickerProps> {
|
export class PathPicker extends React.Component<PathPickerProps> {
|
||||||
async onClick() {
|
static async pick(opts: PathPickOpts) {
|
||||||
const { onPick, onCancel, label, className, disabled, ...dialogOptions } = this.props;
|
const { onPick, onCancel, label, ...dialogOptions } = opts;
|
||||||
const { dialog, BrowserWindow } = remote;
|
const { dialog, BrowserWindow } = remote;
|
||||||
const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), {
|
const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), {
|
||||||
message: label,
|
message: label,
|
||||||
@ -49,12 +52,18 @@ export class PathPicker extends React.Component<PathPickerProps> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
onCancel?.();
|
await onCancel?.();
|
||||||
} else {
|
} else {
|
||||||
onPick?.(filePaths);
|
await onPick?.(filePaths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onClick() {
|
||||||
|
const { className, disabled, ...pickOpts } = this.props;
|
||||||
|
|
||||||
|
return PathPicker.pick(pickOpts);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, label, disabled } = this.props;
|
const { className, label, disabled } = this.props;
|
||||||
|
|
||||||
|
|||||||
98
src/renderer/initializers/catalog-category-registry.tsx
Normal file
98
src/renderer/initializers/catalog-category-registry.tsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* 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 React from "react";
|
||||||
|
import { CreateNewFolderOutlined, NoteAdd } from "@material-ui/icons";
|
||||||
|
import { kubernetesClusterCategory } from "../../common/catalog-entities";
|
||||||
|
import { addClusterURL, preferencesURL } from "../../common/routes";
|
||||||
|
import { PathPicker } from "../components/path-picker";
|
||||||
|
import { multiSet } from "../utils";
|
||||||
|
import { UserStore } from "../../common/user-store";
|
||||||
|
import { getAllEntries } from "../components/+preferences/kubeconfig-syncs";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
|
import { isWindows } from "../../common/vars";
|
||||||
|
|
||||||
|
async function addSyncEntries(filePaths: string[]) {
|
||||||
|
const entries = await getAllEntries(filePaths);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
multiSet(UserStore.getInstance().syncKubeconfigEntries, entries);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initCatalogCategoryRegistryEntries() {
|
||||||
|
kubernetesClusterCategory.on("catalogAddMenu", ctx => {
|
||||||
|
ctx.menuItems.push(
|
||||||
|
{
|
||||||
|
icon: "text_snippet",
|
||||||
|
title: "Add from kubeconfig",
|
||||||
|
onClick: () => ctx.navigate(addClusterURL()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isWindows) {
|
||||||
|
ctx.menuItems.push(
|
||||||
|
{
|
||||||
|
icon: () => <CreateNewFolderOutlined fontSize="large" />,
|
||||||
|
title: "Sync kubeconfig folders(s)",
|
||||||
|
onClick: async () => {
|
||||||
|
await PathPicker.pick({
|
||||||
|
label: "Sync folders(s)",
|
||||||
|
buttonLabel: "Sync",
|
||||||
|
properties: ["showHiddenFiles", "multiSelections", "openDirectory"],
|
||||||
|
onPick: addSyncEntries,
|
||||||
|
});
|
||||||
|
ctx.navigate(preferencesURL({ fragment: "kube-sync" }));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: () => <NoteAdd fontSize="large" />,
|
||||||
|
title: "Sync kubeconfig file(s)",
|
||||||
|
onClick: async () => {
|
||||||
|
await PathPicker.pick({
|
||||||
|
label: "Sync file(s)",
|
||||||
|
buttonLabel: "Sync",
|
||||||
|
properties: ["showHiddenFiles", "multiSelections", "openFile"],
|
||||||
|
onPick: addSyncEntries,
|
||||||
|
});
|
||||||
|
ctx.navigate(preferencesURL({ fragment: "kube-sync" }));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.menuItems.push(
|
||||||
|
{
|
||||||
|
icon: "settings",
|
||||||
|
title: "Sync kubeconfig(s)",
|
||||||
|
onClick: async () => {
|
||||||
|
await PathPicker.pick({
|
||||||
|
label: "Sync file(s)",
|
||||||
|
buttonLabel: "Sync",
|
||||||
|
properties: ["showHiddenFiles", "multiSelections", "openFile"],
|
||||||
|
onPick: addSyncEntries,
|
||||||
|
});
|
||||||
|
ctx.navigate(preferencesURL({ fragment: "kube-sync" }));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
export * from "./app-preferences-kind-registry";
|
export * from "./app-preferences-kind-registry";
|
||||||
export * from "./app-preferences-registry";
|
export * from "./app-preferences-registry";
|
||||||
|
export * from "./catalog-category-registry";
|
||||||
export * from "./catalog-entity-detail-registry";
|
export * from "./catalog-entity-detail-registry";
|
||||||
export * from "./catalog";
|
export * from "./catalog";
|
||||||
export * from "./command-registry";
|
export * from "./command-registry";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user