1
0
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:
Sebastian Malton 2021-07-14 11:49:30 -04:00
parent 207f0dfd62
commit 3d06f8a765
10 changed files with 154 additions and 50 deletions

View File

@ -20,12 +20,11 @@
*/
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 { ClusterStore } from "../cluster-store";
import { requestMain } from "../ipc";
import { CatalogCategory, CatalogCategorySpec } from "../catalog";
import { addClusterURL, preferencesURL } from "../routes";
import { app } from "electron";
import type { CatalogEntitySpec } from "../catalog/catalog-entity";
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 kind = "CatalogCategory";
public metadata = {
@ -167,25 +166,8 @@ export class KubernetesClusterCategory extends CatalogCategory {
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);

View File

@ -22,6 +22,7 @@
import EventEmitter from "events";
import type TypedEmitter from "typed-emitter";
import { observable, makeObservable } from "mobx";
import type React from "react";
type ExtractEntityMetadataType<Entity> = Entity extends CatalogEntity<infer Metadata> ? Metadata : never;
type ExtractEntityStatusType<Entity> = Entity extends CatalogEntity<any, infer Status> ? Status : never;
@ -108,7 +109,7 @@ export interface CatalogEntityContextMenu {
/**
* Menu icon
*/
icon?: string;
icon?: string | React.ComponentType;
/**
* OnClick handler
*/
@ -122,7 +123,7 @@ export interface CatalogEntityContextMenu {
}
export interface CatalogEntityAddMenu extends CatalogEntityContextMenu {
icon: string;
icon: string | React.ComponentType;
}
export interface CatalogEntitySettingsMenu {

View File

@ -21,6 +21,12 @@
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> {
static new<K, V>(entries?: readonly (readonly [K, V])[] | null): ExtendedMap<K, V> {
return new ExtendedMap<K, V>(entries);

View File

@ -74,6 +74,7 @@ export async function bootstrap(App: AppComponent) {
rootElem.classList.toggle("is-mac", isMac);
initializers.initRegistries();
initializers.initCatalogCategoryRegistryEntries();
initializers.initAppPreferenceKindRegistry();
initializers.initAppPreferenceRegistry();
initializers.initCommandRegistry();

View File

@ -97,7 +97,7 @@ export class CatalogAddButton extends React.Component<CatalogAddButtonProps> {
{ this.menuItems.map((menuItem, index) => {
return <SpeedDialAction
key={index}
icon={<Icon material={menuItem.icon} />}
icon={typeof menuItem.icon === "string" ? <Icon material={menuItem.icon} /> : <menuItem.icon />}
tooltipTitle={menuItem.title}
onClick={() => menuItem.onClick()}
TooltipClasses={{

View File

@ -31,11 +31,24 @@ import { ConfirmDialog } from "../confirm-dialog";
import { HotbarStore } from "../../../common/hotbar-store";
import { Icon } from "../icon";
import type { CatalogEntityItem } from "./catalog-entity.store";
import { Tooltip } from "@material-ui/core";
export interface CatalogEntityDrawerMenuProps<T extends CatalogEntity> extends MenuActionsProps {
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
export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Component<CatalogEntityDrawerMenuProps<T>> {
@observable private contextMenu: CatalogEntityContextMenuContext;
@ -86,14 +99,9 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
continue;
}
const key = menuItem.icon.includes("<svg") ? "svg" : "material";
items.push(
<MenuItem key={menuItem.title} onClick={() => this.onMenuItemClick(menuItem)}>
<Icon
tooltip={menuItem.title}
{...{ [key]: menuItem.icon }}
/>
{resolveIcon(menuItem)}
</MenuItem>
);
}
@ -109,7 +117,7 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
render() {
const { className, item: entity, ...menuProps } = this.props;
if (!this.contextMenu || !entity.enabled) {
return null;
}

View File

@ -28,7 +28,7 @@ import fse from "fs-extra";
import { KubeconfigSyncEntry, KubeconfigSyncValue, UserStore } from "../../../common/user-store";
import { Spinner } from "../spinner";
import logger from "../../../main/logger";
import { iter } from "../../utils";
import { iter, multiSet } from "../../utils";
import { isWindows } from "../../../common/vars";
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
export class KubeconfigSyncs extends React.Component {
syncs = observable.map<string, Value>();
@ -105,13 +109,7 @@ export class KubeconfigSyncs extends React.Component {
}
@action
onPick = async (filePaths: string[]) => {
const newEntries = await Promise.all(filePaths.map(filePath => getMapEntry({ filePath })));
for (const [filePath, info] of newEntries) {
this.syncs.set(filePath, info);
}
};
onPick = async (filePaths: string[]) => multiSet(this.syncs, await getAllEntries(filePaths));
renderEntryIcon(entry: Entry) {
switch (entry.info.type) {

View File

@ -25,12 +25,10 @@ import React from "react";
import { cssNames } from "../../utils";
import { Button } from "../button";
export interface PathPickerProps {
className?: string;
export interface PathPickOpts {
label: string;
disabled?: boolean;
onPick?: (paths: string[]) => void;
onCancel?: () => void;
onPick?: (paths: string[]) => any;
onCancel?: () => any;
defaultPath?: string;
buttonLabel?: string;
filters?: FileFilter[];
@ -38,10 +36,15 @@ export interface PathPickerProps {
securityScopedBookmarks?: boolean;
}
export interface PathPickerProps extends PathPickOpts {
className?: string;
disabled?: boolean;
}
@observer
export class PathPicker extends React.Component<PathPickerProps> {
async onClick() {
const { onPick, onCancel, label, className, disabled, ...dialogOptions } = this.props;
static async pick(opts: PathPickOpts) {
const { onPick, onCancel, label, ...dialogOptions } = opts;
const { dialog, BrowserWindow } = remote;
const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), {
message: label,
@ -49,12 +52,18 @@ export class PathPicker extends React.Component<PathPickerProps> {
});
if (canceled) {
onCancel?.();
await onCancel?.();
} else {
onPick?.(filePaths);
await onPick?.(filePaths);
}
}
async onClick() {
const { className, disabled, ...pickOpts } = this.props;
return PathPicker.pick(pickOpts);
}
render() {
const { className, label, disabled } = this.props;

View 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" }));
},
},
);
}
});
}

View File

@ -21,6 +21,7 @@
export * from "./app-preferences-kind-registry";
export * from "./app-preferences-registry";
export * from "./catalog-category-registry";
export * from "./catalog-entity-detail-registry";
export * from "./catalog";
export * from "./command-registry";