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:
parent
51231ae2df
commit
993c3746b1
@ -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) {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
|||||||
@ -19,3 +19,7 @@
|
|||||||
@apply opacity-10;
|
@apply opacity-10;
|
||||||
--size: 180px;
|
--size: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.frozenRow {
|
||||||
|
@apply opacity-30 pointer-events-none;
|
||||||
|
}
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user