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

View File

@ -24,7 +24,7 @@ import "./extensions.scss";
import { remote, shell } from "electron"; import { remote, shell } from "electron";
import fse from "fs-extra"; import fse from "fs-extra";
import _ from "lodash"; import _ from "lodash";
import { computed, observable, reaction, when } from "mobx"; import { observable, reaction, when } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import os from "os"; import os from "os";
import path from "path"; import path from "path";
@ -33,7 +33,6 @@ import { SemVer } from "semver";
import URLParse from "url-parse"; import URLParse from "url-parse";
import { import {
autobind,
Disposer, Disposer,
disposer, disposer,
downloadFile, downloadFile,
@ -55,11 +54,9 @@ import {
import logger from "../../../main/logger"; import logger from "../../../main/logger";
import { Button } from "../button"; import { Button } from "../button";
import { ConfirmDialog } from "../confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog";
import { Icon } from "../icon";
import { DropFileInput, InputValidators } from "../input"; import { DropFileInput, InputValidators } from "../input";
import { PageLayout } from "../layout/page-layout"; import { PageLayout } from "../layout/page-layout";
import { Notifications } from "../notifications"; import { Notifications } from "../notifications";
import { Spinner } from "../spinner/spinner";
import { ExtensionInstallationState, ExtensionInstallationStateStore } from "./extension-install.store"; import { ExtensionInstallationState, ExtensionInstallationStateStore } from "./extension-install.store";
import { Install } from "./install"; import { Install } from "./install";
import { InstalledExtensions } from "./installed-extensions"; import { InstalledExtensions } from "./installed-extensions";
@ -106,6 +103,22 @@ interface InstallRequestValidated {
tempFile: string; // temp system path to packed extension for unpacking 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> { async function uninstallExtension(extensionId: LensExtensionId): Promise<boolean> {
const loader = ExtensionLoader.getInstance(); const loader = ExtensionLoader.getInstance();
const { manifest } = loader.getExtension(extensionId); const { manifest } = loader.getExtension(extensionId);
@ -482,19 +495,8 @@ async function installFromSelectFileDialog() {
@observer @observer
export class Extensions extends React.Component { export class Extensions extends React.Component {
@observable search = "";
@observable installPath = ""; @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() { componentDidMount() {
// TODO: change this after upgrading to mobx6 as that versions' reactions have this functionality // TODO: change this after upgrading to mobx6 as that versions' reactions have this functionality
let prevSize = ExtensionLoader.getInstance().userExtensions.size; 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() { render() {
const extensions = Array.from(ExtensionLoader.getInstance().userExtensions.values()); const extensions = ExtensionLoader.getInstance().userExtensionList;
return ( return (
<DropFileInput onDropFiles={installOnDrop}> <DropFileInput onDropFiles={installOnDrop}>
@ -611,6 +538,8 @@ export class Extensions extends React.Component {
<InstalledExtensions <InstalledExtensions
extensions={extensions} extensions={extensions}
enable={enableExtension}
disable={disableExtension}
uninstall={confirmUninstallExtension} uninstall={confirmUninstallExtension}
/> />
</section> </section>

View File

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

View File

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