Introduce clearer boundry between extensions (#7164)

- Bundled extensions are always enabled, and are always compatible
- Have bundled extensions be loaded asyncronously to support
  typescript dynamic import (which is typed) as opposed to require

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2023-04-05 10:21:38 -04:00
1 parent 351f9d492f
commit 058494bc73
29 files changed
+440 -550

No files matched your search

@@ -86,7 +86,7 @@ const attemptInstall = ({
}
const extensionFolder = getExtensionDestFolder(name);
const installedExtension = extensionLoader.getExtension(validatedRequest.id);
const installedExtension = extensionLoader.getExtensionById(validatedRequest.id);
if (installedExtension) {
const { version: oldVersion } = installedExtension.manifest;
@@ -73,7 +73,7 @@ const unpackExtensionInjectable = getInjectable({
await fse.move(unpackedRootFolder, extensionFolder, { overwrite: true });
// wait for the loader has actually install it
await when(() => extensionLoader.userExtensions.has(id));
await when(() => extensionLoader.userExtensions.get().has(id));
// Enable installed extensions by default.
extensionLoader.setIsEnabled(id, true);
@@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensExtensionId } from "@k8slens/legacy-extensions";
import { getInjectable } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
export type DisableExtension = (extId: LensExtensionId) => void;
const disableExtensionInjectable = getInjectable({
id: "disable-extension",
instantiate: (di): DisableExtension => {
const extensionLoader = di.inject(extensionLoaderInjectable);
return (extId) => {
const ext = extensionLoader.getExtensionById(extId);
if (ext && !ext.isBundled) {
ext.isEnabled = false;
}
};
},
});
export default disableExtensionInjectable;
@@ -1,18 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
import { disableExtension } from "./disable-extension";
const disableExtensionInjectable = getInjectable({
id: "disable-extension",
instantiate: (di) =>
disableExtension({
extensionLoader: di.inject(extensionLoaderInjectable),
}),
});
export default disableExtensionInjectable;
@@ -1,20 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensExtensionId } from "@k8slens/legacy-extensions";
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
interface Dependencies {
extensionLoader: ExtensionLoader;
}
export const disableExtension =
({ extensionLoader }: Dependencies) =>
(id: LensExtensionId) => {
const extension = extensionLoader.getExtension(id);
if (extension) {
extension.isEnabled = false;
}
};
@@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensExtensionId } from "@k8slens/legacy-extensions";
import { getInjectable } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
export type EnableExtension = (extId: LensExtensionId) => void;
const enableExtensionInjectable = getInjectable({
id: "enable-extension",
instantiate: (di): EnableExtension => {
const extensionLoader = di.inject(extensionLoaderInjectable);
return (extId) => {
const ext = extensionLoader.getExtensionById(extId);
if (ext && !ext.isBundled) {
ext.isEnabled = true;
}
};
},
});
export default enableExtensionInjectable;
@@ -1,18 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
import { enableExtension } from "./enable-extension";
const enableExtensionInjectable = getInjectable({
id: "enable-extension",
instantiate: (di) =>
enableExtension({
extensionLoader: di.inject(extensionLoaderInjectable),
}),
});
export default enableExtensionInjectable;
@@ -1,20 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensExtensionId } from "@k8slens/legacy-extensions";
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
interface Dependencies {
extensionLoader: ExtensionLoader;
}
export const enableExtension =
({ extensionLoader }: Dependencies) =>
(id: LensExtensionId) => {
const extension = extensionLoader.getExtension(id);
if (extension) {
extension.isEnabled = true;
}
};
@@ -4,138 +4,66 @@
*/
import styles from "./extensions.module.scss";
import type { IComputedValue } from "mobx";
import {
makeObservable,
observable,
reaction,
when,
} from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import React from "react";
import { DropFileInput } from "../input";
import { Install } from "./install";
import { ExtensionInstall } from "./install";
import { InstalledExtensions } from "./installed-extensions";
import { Notice } from "./notice";
import { SettingLayout } from "../layout/setting-layout";
import { docsUrl } from "../../../common/vars";
import { withInjectables } from "@ogre-tools/injectable-react";
import userExtensionsInjectable from "./user-extensions/user-extensions.injectable";
import enableExtensionInjectable from "./enable-extension/enable-extension.injectable";
import disableExtensionInjectable from "./disable-extension/disable-extension.injectable";
import type { ConfirmUninstallExtension } from "./confirm-uninstall-extension.injectable";
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension.injectable";
import type { InstallExtensionFromInput } from "./install-extension-from-input.injectable";
import installExtensionFromInputInjectable from "./install-extension-from-input.injectable";
import installFromSelectFileDialogInjectable from "./install-from-select-file-dialog.injectable";
import type { InstallOnDrop } from "./install-on-drop.injectable";
import installOnDropInjectable from "./install-on-drop.injectable";
import { supportedExtensionFormats } from "./supported-extension-formats";
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
import Gutter from "../gutter/gutter";
import type { InstalledExtension, LensExtensionId } from "@k8slens/legacy-extensions";
const ExtensionsNotice = () => (
<Notice className={styles.notice}>
<p>
{"Add new features via Lens Extensions. Check out the "}
<a
href={`${docsUrl}/extensions/lens-extensions`}
target="_blank"
rel="noreferrer"
>
docs
</a>
{" and list of "}
<a
href="https://github.com/lensapp/lens-extensions/blob/main/README.md"
target="_blank"
rel="noreferrer"
>
available extensions
</a>
.
</p>
</Notice>
);
interface Dependencies {
userExtensions: IComputedValue<InstalledExtension[]>;
enableExtension: (id: LensExtensionId) => void;
disableExtension: (id: LensExtensionId) => void;
confirmUninstallExtension: ConfirmUninstallExtension;
installExtensionFromInput: InstallExtensionFromInput;
installFromSelectFileDialog: () => Promise<void>;
installOnDrop: InstallOnDrop;
extensionInstallationStateStore: ExtensionInstallationStateStore;
}
@observer
class NonInjectedExtensions extends React.Component<Dependencies> {
@observable installPath = "";
constructor(props: Dependencies) {
super(props);
makeObservable(this);
}
componentDidMount() {
disposeOnUnmount(this, [
reaction(() => this.props.userExtensions.get().length, (curSize, prevSize) => {
if (curSize > prevSize) {
disposeOnUnmount(this, [
when(() => !this.props.extensionInstallationStateStore.anyInstalling, () => this.installPath = ""),
]);
}
}),
]);
}
render() {
const userExtensions = this.props.userExtensions.get();
return (
<DropFileInput onDropFiles={this.props.installOnDrop}>
<SettingLayout
className="Extensions"
contentGaps={false}
data-testid="extensions-page"
>
<section>
<h1>Extensions</h1>
<Notice className={styles.notice}>
<p>
{"Add new features via Lens Extensions. Check out the "}
<a
href={`${docsUrl}/extensions/`}
target="_blank"
rel="noreferrer"
>
docs
</a>
{" and list of "}
<a
href="https://github.com/lensapp/lens-extensions/blob/main/README.md"
target="_blank"
rel="noreferrer"
>
available extensions
</a>
.
</p>
</Notice>
<Install
supportedFormats={supportedExtensionFormats}
onChange={value => (this.installPath = value)}
installFromInput={() => this.props.installExtensionFromInput(this.installPath)}
installFromSelectFileDialog={this.props.installFromSelectFileDialog}
installPath={this.installPath}
/>
<Gutter size="md" />
<InstalledExtensions
extensions={userExtensions}
enable={this.props.enableExtension}
disable={this.props.disableExtension}
uninstall={this.props.confirmUninstallExtension}
/>
</section>
</SettingLayout>
</DropFileInput>
);
}
}
const NonInjectedExtensions = ({ installOnDrop }: Dependencies) => (
<DropFileInput onDropFiles={installOnDrop}>
<SettingLayout
className="Extensions"
contentGaps={false}
data-testid="extensions-page"
>
<section>
<h1>Extensions</h1>
<ExtensionsNotice />
<ExtensionInstall />
<Gutter size="md" />
<InstalledExtensions />
</section>
</SettingLayout>
</DropFileInput>
);
export const Extensions = withInjectables<Dependencies>(NonInjectedExtensions, {
getProps: (di) => ({
userExtensions: di.inject(userExtensionsInjectable),
enableExtension: di.inject(enableExtensionInjectable),
disableExtension: di.inject(disableExtensionInjectable),
confirmUninstallExtension: di.inject(confirmUninstallExtensionInjectable),
installExtensionFromInput: di.inject(installExtensionFromInputInjectable),
installOnDrop: di.inject(installOnDropInjectable),
installFromSelectFileDialog: di.inject(installFromSelectFileDialogInjectable),
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
}),
});
@@ -4,7 +4,7 @@
*/
import styles from "./install.module.scss";
import React from "react";
import React, { useEffect, useRef, useState } from "react";
import { prevDefault } from "@k8slens/utilities";
import { Button } from "../button";
import { Icon } from "../icon";
@@ -16,17 +16,16 @@ import type { ExtensionInstallationStateStore } from "../../../extensions/extens
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import { unionInputValidatorsAsync } from "../input/input_validators";
export interface InstallProps {
installPath: string;
supportedFormats: string[];
onChange: (path: string) => void;
installFromInput: () => void;
installFromSelectFileDialog: () => void;
}
import { supportedExtensionFormats } from "./supported-extension-formats";
import type { InstallExtensionFromInput } from "./install-extension-from-input.injectable";
import type { InstallFromSelectFileDialog } from "./install-from-select-file-dialog.injectable";
import installExtensionFromInputInjectable from "./install-extension-from-input.injectable";
import installFromSelectFileDialogInjectable from "./install-from-select-file-dialog.injectable";
interface Dependencies {
extensionInstallationStateStore: ExtensionInstallationStateStore;
installState: ExtensionInstallationStateStore;
installExtensionFromInput: InstallExtensionFromInput;
installFromSelectFileDialog: InstallFromSelectFileDialog;
}
const installInputValidator = unionInputValidatorsAsync(
@@ -38,71 +37,75 @@ const installInputValidator = unionInputValidatorsAsync(
InputValidators.isPath,
);
const NonInjectedInstall: React.FC<Dependencies & InstallProps> = ({
installPath,
supportedFormats,
onChange,
installFromInput,
const installTitle = `Name or file path or URL to an extension package (${supportedExtensionFormats.join(", ")})`;
const NonInjectedInstall = observer(({
installExtensionFromInput,
installFromSelectFileDialog,
extensionInstallationStateStore,
}) => (
<section>
<SubTitle
title={`Name or file path or URL to an extension package (${supportedFormats.join(
", ",
)})`}
/>
<div className={styles.inputs}>
<div>
<Input
theme="round-black"
disabled={extensionInstallationStateStore.anyPreInstallingOrInstalling}
placeholder={"Name or file path or URL"}
showErrorsAsTooltip={{ preferredPositions: TooltipPosition.BOTTOM }}
validators={installPath ? installInputValidator : undefined}
value={installPath}
onChange={onChange}
onSubmit={installFromInput}
iconRight={(
<Icon
className={styles.icon}
smallest
material="folder_open"
onClick={prevDefault(installFromSelectFileDialog)}
tooltip="Browse"
/>
)}
/>
</div>
<div>
<Button
className={styles.button}
primary
label="Install"
disabled={
extensionInstallationStateStore.anyPreInstallingOrInstalling
}
waiting={extensionInstallationStateStore.anyPreInstallingOrInstalling}
onClick={installFromInput}
/>
</div>
</div>
<small className={styles.proTip}>
<b>Pro-Tip</b>
: you can drag and drop a tarball file to this area
</small>
</section>
);
installState,
}: Dependencies) => {
const [installPath, setInstallPath] = useState("");
const prevAnyInstalling = useRef(installState.anyInstalling);
export const Install = withInjectables<Dependencies, InstallProps>(
observer(NonInjectedInstall),
{
getProps: (di, props) => ({
extensionInstallationStateStore: di.inject(
extensionInstallationStateStoreInjectable,
),
useEffect(() => {
const currentlyInstalling = installState.anyInstalling;
const previouslyInstalling = prevAnyInstalling.current;
...props,
}),
},
);
if (!currentlyInstalling && previouslyInstalling) {
prevAnyInstalling.current = false;
setInstallPath("");
}
}, [installState.anyInstalling]);
return (
<section>
<SubTitle title={installTitle} />
<div className={styles.inputs}>
<div>
<Input
theme="round-black"
disabled={installState.anyPreInstallingOrInstalling}
placeholder="Name or file path or URL"
showErrorsAsTooltip={{ preferredPositions: TooltipPosition.BOTTOM }}
validators={installPath ? installInputValidator : undefined}
value={installPath}
onChange={setInstallPath}
onSubmit={() => installExtensionFromInput(installPath)}
iconRight={(
<Icon
className={styles.icon}
smallest
material="folder_open"
onClick={prevDefault(installFromSelectFileDialog)}
tooltip="Browse"
/>
)}
/>
</div>
<div>
<Button
className={styles.button}
primary
label="Install"
disabled={installState.anyPreInstallingOrInstalling}
waiting={installState.anyPreInstallingOrInstalling}
onClick={() => installExtensionFromInput(installPath)}
/>
</div>
</div>
<small className={styles.proTip}>
<b>Pro-Tip</b>
: you can drag and drop a tarball file to this area
</small>
</section>
);
});
export const ExtensionInstall = withInjectables<Dependencies>(NonInjectedInstall, {
getProps: (di, props) => ({
...props,
installState: di.inject(extensionInstallationStateStoreInjectable),
installExtensionFromInput: di.inject(installExtensionFromInputInjectable),
installFromSelectFileDialog: di.inject(installFromSelectFileDialogInjectable),
}),
});
@@ -4,8 +4,7 @@
*/
import styles from "./installed-extensions.module.scss";
import React, { useMemo } from "react";
import type { ExtensionDiscovery } from "../../../extensions/extension-discovery/extension-discovery";
import React from "react";
import { Icon } from "../icon";
import { List } from "../list/list";
import { MenuActions, MenuItem } from "../menu";
@@ -17,18 +16,27 @@ import extensionDiscoveryInjectable from "../../../extensions/extension-discover
import { withInjectables } from "@ogre-tools/injectable-react";
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
import type { InstalledExtension, LensExtensionId } from "@k8slens/legacy-extensions";
import type { InstalledExtension } from "@k8slens/legacy-extensions";
import type { IComputedValue } from "mobx";
import type { ConfirmUninstallExtension } from "./confirm-uninstall-extension.injectable";
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension.injectable";
import type { DisableExtension } from "./disable-extension.injectable";
import disableExtensionInjectable from "./disable-extension.injectable";
import type { EnableExtension } from "./enable-extension.injectable";
import enableExtensionInjectable from "./enable-extension.injectable";
import userExtensionsInjectable from "./user-extensions/user-extensions.injectable";
import type { ExtensionDiscovery } from "../../../extensions/extension-discovery/extension-discovery";
export interface InstalledExtensionsProps {
extensions: InstalledExtension[];
enable: (id: LensExtensionId) => void;
disable: (id: LensExtensionId) => void;
uninstall: (extension: InstalledExtension) => void;
}
interface Dependencies {
extensionDiscovery: ExtensionDiscovery;
extensionInstallationStateStore: ExtensionInstallationStateStore;
userExtensions: IComputedValue<InstalledExtension[]>;
enableExtension: EnableExtension;
disableExtension: DisableExtension;
confirmUninstallExtension: ConfirmUninstallExtension;
}
function getStatus(extension: InstalledExtension) {
@@ -39,106 +47,20 @@ function getStatus(extension: InstalledExtension) {
return extension.isEnabled ? "Enabled" : "Disabled";
}
const NonInjectedInstalledExtensions = observer(({ extensionDiscovery, extensionInstallationStateStore, extensions, uninstall, enable, disable }: Dependencies & InstalledExtensionsProps) => {
const columns = useMemo(
() => [
{
Header: "Name",
accessor: "extension",
width: 200,
sortType: (rowA: Row, rowB: Row) => { // Custom sorting for extension name
const nameA = extensions[rowA.index].manifest.name;
const nameB = extensions[rowB.index].manifest.name;
if (nameA > nameB) return -1;
if (nameB > nameA) return 1;
return 0;
},
},
{
Header: "Version",
accessor: "version",
},
{
Header: "Status",
accessor: "status",
},
{
Header: "",
accessor: "actions",
disableSortBy: true,
width: 20,
className: "actions",
},
], [],
);
const data = useMemo(
() => extensions.map(extension => {
const { id, isEnabled, isCompatible, manifest } = extension;
const { name, description, version } = manifest;
const isUninstalling = extensionInstallationStateStore.isExtensionUninstalling(id);
return {
extension: (
<div className={"flex items-start"}>
<div>
<div className={styles.extensionName}>{name}</div>
<div className={styles.extensionDescription}>{description}</div>
</div>
</div>
),
version,
status: (
<div className={cssNames({ [styles.enabled]: isEnabled, [styles.invalid]: !isCompatible })}>
{getStatus(extension)}
</div>
),
actions: (
<MenuActions
id={`menu-actions-for-installed-extensions-for-${id}`}
usePortal
toolbar={false}>
{isCompatible && (
<>
{isEnabled ? (
<MenuItem
disabled={isUninstalling}
onClick={() => disable(id)}
>
<Icon material="unpublished" />
<span className="title" aria-disabled={isUninstalling}>Disable</span>
</MenuItem>
) : (
<MenuItem
disabled={isUninstalling}
onClick={() => enable(id)}
>
<Icon material="check_circle" />
<span className="title" aria-disabled={isUninstalling}>Enable</span>
</MenuItem>
)}
</>
)}
<MenuItem
disabled={isUninstalling}
onClick={() => uninstall(extension)}
>
<Icon material="delete" />
<span className="title" aria-disabled={isUninstalling}>Uninstall</span>
</MenuItem>
</MenuActions>
),
};
}), [extensions, extensionInstallationStateStore.anyUninstalling],
);
const NonInjectedInstalledExtensions = observer(({
extensionDiscovery,
extensionInstallationStateStore,
userExtensions,
confirmUninstallExtension,
enableExtension,
disableExtension,
}: Dependencies & InstalledExtensionsProps) => {
if (!extensionDiscovery.isLoaded) {
return <div><Spinner center /></div>;
}
const extensions = userExtensions.get();
if (extensions.length == 0) {
return (
<div className="flex column h-full items-center justify-center">
@@ -151,13 +73,96 @@ const NonInjectedInstalledExtensions = observer(({ extensionDiscovery, extension
);
}
const toggleExtensionWith = (enabled: boolean) => (
enabled
? disableExtension
: enableExtension
);
return (
<section data-testid="extensions-table">
<List
title={<h2 className={styles.title}>Installed extensions</h2>}
columns={columns}
data={data}
items={extensions}
columns={[
{
Header: "Name",
accessor: "extension",
width: 200,
sortType: (rowA: Row, rowB: Row) => { // Custom sorting for extension name
const nameA = extensions[rowA.index].manifest.name;
const nameB = extensions[rowB.index].manifest.name;
if (nameA > nameB) return -1;
if (nameB > nameA) return 1;
return 0;
},
},
{
Header: "Version",
accessor: "version",
},
{
Header: "Status",
accessor: "status",
},
{
Header: "",
accessor: "actions",
disableSortBy: true,
width: 20,
},
]}
data={extensions.map(extension => {
const { id, isEnabled, isCompatible, manifest } = extension;
const { name, description, version } = manifest;
const isUninstalling = extensionInstallationStateStore.isExtensionUninstalling(id);
const toggleExtension = toggleExtensionWith(isEnabled);
return {
extension: (
<div className={"flex items-start"}>
<div>
<div className={styles.extensionName}>{name}</div>
<div className={styles.extensionDescription}>{description}</div>
</div>
</div>
),
version,
status: (
<div className={cssNames({ [styles.enabled]: isEnabled, [styles.invalid]: !isCompatible })}>
{getStatus(extension)}
</div>
),
actions: (
<MenuActions
id={`menu-actions-for-installed-extensions-for-${id}`}
usePortal
toolbar={false}>
{isCompatible && (
<MenuItem
disabled={isUninstalling}
onClick={() => toggleExtension(id)}
>
<Icon material={isEnabled ? "unpublished" : "check_circle"} />
<span className="title" aria-disabled={isUninstalling}>
{isEnabled ? "Disable" : "Enabled"}
</span>
</MenuItem>
)}
<MenuItem
disabled={isUninstalling}
onClick={() => confirmUninstallExtension(extension)}
>
<Icon material="delete" />
<span className="title" aria-disabled={isUninstalling}>Uninstall</span>
</MenuItem>
</MenuActions>
),
};
})}
items={userExtensions.get()}
filters={[
(extension) => extension.manifest.name,
(extension) => getStatus(extension),
@@ -170,8 +175,12 @@ const NonInjectedInstalledExtensions = observer(({ extensionDiscovery, extension
export const InstalledExtensions = withInjectables<Dependencies, InstalledExtensionsProps>(NonInjectedInstalledExtensions, {
getProps: (di, props) => ({
...props,
extensionDiscovery: di.inject(extensionDiscoveryInjectable),
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
...props,
userExtensions: di.inject(userExtensionsInjectable),
enableExtension: di.inject(enableExtensionInjectable),
disableExtension: di.inject(disableExtensionInjectable),
confirmUninstallExtension: di.inject(confirmUninstallExtensionInjectable),
}),
});
@@ -27,7 +27,7 @@ const uninstallExtensionInjectable = getInjectable({
const showErrorNotification = di.inject(showErrorNotificationInjectable);
return async (extensionId: LensExtensionId): Promise<boolean> => {
const ext = extensionLoader.getExtension(extensionId);
const ext = extensionLoader.getExtensionById(extensionId);
if (!ext) {
logger.debug(`[EXTENSIONS]: cannot uninstall ${extensionId}, was not installed`);
@@ -45,7 +45,7 @@ const uninstallExtensionInjectable = getInjectable({
await extensionDiscovery.uninstallExtension(extensionId);
// wait for the ExtensionLoader to actually uninstall the extension
await when(() => !extensionLoader.userExtensions.has(extensionId));
await when(() => !extensionLoader.userExtensions.get().has(extensionId));
showSuccessNotification(
<p>
@@ -12,7 +12,7 @@ const userExtensionsInjectable = getInjectable({
instantiate: (di) => {
const extensionLoader = di.inject(extensionLoaderInjectable);
return computed(() => [...extensionLoader.userExtensions.values()]);
return computed(() => [...extensionLoader.userExtensions.get().values()]);
},
});
@@ -30,7 +30,7 @@ const createExtensionInstanceInjectable = getInjectable({
};
return (ExtensionClass, extension) => {
const instance = new ExtensionClass(extension) as LensRendererExtension;
const instance = new ExtensionClass(extension as any) as LensRendererExtension;
(instance as Writable<LensRendererExtension>)[lensExtensionDependencies] = deps;