1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Making extension components observable

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
Alex Andreev 2021-05-17 15:06:26 +03:00
parent 51231ae2df
commit 993c3746b1
5 changed files with 44 additions and 101 deletions

View File

@ -61,7 +61,7 @@ export class ExtensionLoader extends Singleton {
whenLoaded = when(() => this.isLoaded);
@computed get userExtensions(): Map<LensExtensionId, InstalledExtension> {
const extensions = this.extensions.toJS();
const extensions = this.toJSON();
extensions.forEach((ext, extId) => {
if (ext.isBundled) {
@ -86,6 +86,10 @@ export class ExtensionLoader extends Singleton {
return extensions;
}
@computed get userExtensionList() {
return Array.from(this.userExtensions.values());
}
getExtensionByName(name: string): LensExtension | null {
for (const [, val] of this.instances) {
if (val.name === name) {

View File

@ -24,7 +24,7 @@ import "./extensions.scss";
import { remote, shell } from "electron";
import fse from "fs-extra";
import _ from "lodash";
import { computed, observable, reaction, when } from "mobx";
import { observable, reaction, when } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import os from "os";
import path from "path";
@ -33,7 +33,6 @@ import { SemVer } from "semver";
import URLParse from "url-parse";
import {
autobind,
Disposer,
disposer,
downloadFile,
@ -55,11 +54,9 @@ import {
import logger from "../../../main/logger";
import { Button } from "../button";
import { ConfirmDialog } from "../confirm-dialog";
import { Icon } from "../icon";
import { DropFileInput, InputValidators } from "../input";
import { PageLayout } from "../layout/page-layout";
import { Notifications } from "../notifications";
import { Spinner } from "../spinner/spinner";
import { ExtensionInstallationState, ExtensionInstallationStateStore } from "./extension-install.store";
import { Install } from "./install";
import { InstalledExtensions } from "./installed-extensions";
@ -106,6 +103,22 @@ interface InstallRequestValidated {
tempFile: string; // temp system path to packed extension for unpacking
}
function setExtensionEnabled(id: LensExtensionId, isEnabled: boolean): void {
const extension = ExtensionLoader.getInstance().getExtension(id);
if (extension) {
extension.isEnabled = isEnabled;
}
}
function enableExtension(id: LensExtensionId) {
setExtensionEnabled(id, true);
}
function disableExtension(id: LensExtensionId) {
setExtensionEnabled(id, false);
}
async function uninstallExtension(extensionId: LensExtensionId): Promise<boolean> {
const loader = ExtensionLoader.getInstance();
const { manifest } = loader.getExtension(extensionId);
@ -482,19 +495,8 @@ async function installFromSelectFileDialog() {
@observer
export class Extensions extends React.Component {
@observable search = "";
@observable installPath = "";
@computed get searchedForExtensions() {
const searchText = this.search.toLowerCase();
return Array.from(ExtensionLoader.getInstance().userExtensions.values())
.filter(({ manifest: { name, description }}) => (
name.toLowerCase().includes(searchText)
|| description?.toLowerCase().includes(searchText)
));
}
componentDidMount() {
// TODO: change this after upgrading to mobx6 as that versions' reactions have this functionality
let prevSize = ExtensionLoader.getInstance().userExtensions.size;
@ -513,83 +515,8 @@ export class Extensions extends React.Component {
]);
}
renderNoExtensionsHelpText() {
if (this.search) {
return <p>No search results found</p>;
}
return (
<p>
There are no installed extensions.
See list of <a href="https://github.com/lensapp/lens-extensions/blob/main/README.md" target="_blank" rel="noreferrer">available extensions</a>.
</p>
);
}
renderNoExtensions() {
return (
<div className="no-extensions flex box gaps justify-center">
<Icon material="info" />
<div>
{this.renderNoExtensionsHelpText()}
</div>
</div>
);
}
@autobind()
renderExtension(extension: InstalledExtension) {
const { id, isEnabled, manifest } = extension;
const { name, description, version } = manifest;
const isUninstalling = ExtensionInstallationStateStore.isExtensionUninstalling(id);
return (
<div key={id} className="extension flex gaps align-center">
<div className="box grow">
<h5>{name}</h5>
<h6>{version}</h6>
<p>{description}</p>
</div>
<div className="actions">
{
isEnabled
? <Button accent disabled={isUninstalling} onClick={() => extension.isEnabled = false}>Disable</Button>
: <Button plain active disabled={isUninstalling} onClick={() => extension.isEnabled = true}>Enable</Button>
}
<Button
plain
active
disabled={isUninstalling}
waiting={isUninstalling}
onClick={() => confirmUninstallExtension(extension)}
>
Uninstall
</Button>
</div>
</div>
);
}
renderExtensions() {
if (!ExtensionDiscovery.getInstance().isLoaded) {
return <div className="spinner-wrapper"><Spinner /></div>;
}
const { searchedForExtensions } = this;
if (!searchedForExtensions.length) {
return this.renderNoExtensions();
}
return (
<>
{...searchedForExtensions.map(this.renderExtension)}
</>
);
}
render() {
const extensions = Array.from(ExtensionLoader.getInstance().userExtensions.values());
const extensions = ExtensionLoader.getInstance().userExtensionList;
return (
<DropFileInput onDropFiles={installOnDrop}>
@ -611,6 +538,8 @@ export class Extensions extends React.Component {
<InstalledExtensions
extensions={extensions}
enable={enableExtension}
disable={disableExtension}
uninstall={confirmUninstallExtension}
/>
</section>

View File

@ -7,6 +7,7 @@ import { Input, InputValidator, InputValidators } from "../input";
import { SubTitle } from "../layout/sub-title";
import { TooltipPosition } from "../tooltip";
import { ExtensionInstallationStateStore } from "./extension-install.store";
import { observer } from "mobx-react";
interface Props {
installPath: string;
@ -29,7 +30,7 @@ const installInputValidator: InputValidator = {
),
};
export function Install(props: Props) {
export const Install = observer((props: Props) => {
const { installPath, supportedFormats, onChange, installFromInput, installFromSelectFileDialog } = props;
return (
@ -63,7 +64,7 @@ export function Install(props: Props) {
primary
label="Install"
className="w-80 h-full"
// disabled={ExtensionInstallationStateStore.anyPreInstallingOrInstalling || !installInputValidator.validate(installPath)}
disabled={ExtensionInstallationStateStore.anyPreInstallingOrInstalling}
waiting={ExtensionInstallationStateStore.anyPreInstallingOrInstalling}
onClick={installFromInput}
/>
@ -74,4 +75,5 @@ export function Install(props: Props) {
</small>
</section>
);
}
});

View File

@ -18,4 +18,8 @@
.noItemsIcon {
@apply opacity-10;
--size: 180px;
}
.frozenRow {
@apply opacity-30 pointer-events-none;
}

View File

@ -7,9 +7,13 @@ import { MenuActions, MenuItem } from "../menu";
import { Spinner } from "../spinner";
import { ExtensionInstallationStateStore } from "./extension-install.store";
import { cssNames } from "../../utils";
import { observer } from "mobx-react";
import type { LensExtensionId } from "../../../extensions/lens-extension";
interface Props {
extensions: InstalledExtension[];
enable: (id: LensExtensionId) => void;
disable: (id: LensExtensionId) => void;
uninstall: (extension: InstalledExtension) => void;
}
@ -17,7 +21,7 @@ function getStatus(isEnabled: boolean) {
return isEnabled ? "Enabled" : "Disabled";
}
export function InstalledExtensions({ extensions, uninstall }: Props) {
export const InstalledExtensions = observer(({ extensions, uninstall, enable, disable }: Props) => {
if (!ExtensionDiscovery.getInstance().isLoaded) {
return <div><Spinner center /></div>;
}
@ -43,7 +47,7 @@ export function InstalledExtensions({ extensions, uninstall }: Props) {
const columns = useMemo(
() => [
{
Header: "Extension",
Header: "Name",
accessor: "extension",
width: 200
},
@ -74,7 +78,7 @@ export function InstalledExtensions({ extensions, uninstall }: Props) {
return {
extension: (
<div className="flex items-start">
<div className={cssNames("flex items-start", { [styles.frozenRow]: isUninstalling })}>
<div>
<div className={styles.extensionName}>{name}</div>
<div className={styles.extensionDescription}>{description}</div>
@ -92,7 +96,7 @@ export function InstalledExtensions({ extensions, uninstall }: Props) {
{isEnabled ? (
<MenuItem
disabled={isUninstalling}
onClick={() => extension.isEnabled = false}
onClick={() => disable(id)}
>
<Icon material="unpublished"/>
<span className="title">Disable</span>
@ -100,7 +104,7 @@ export function InstalledExtensions({ extensions, uninstall }: Props) {
) : (
<MenuItem
disabled={isUninstalling}
onClick={() => extension.isEnabled = true}
onClick={() => enable(id)}
>
<Icon material="check_circle"/>
<span className="title">Enable</span>
@ -131,4 +135,4 @@ export function InstalledExtensions({ extensions, uninstall }: Props) {
/>
</section>
);
}
});