mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Remove legacy IPC usage
- From extension installation state store Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
edafcc916c
commit
2164b4b010
@ -7,7 +7,7 @@ import type { MessageChannel } from "./message-channel-listener-injection-token"
|
|||||||
|
|
||||||
export interface SendMessageToChannel {
|
export interface SendMessageToChannel {
|
||||||
(channel: MessageChannel<void>): void;
|
(channel: MessageChannel<void>): void;
|
||||||
<Message>(channel: MessageChannel<Message>, message: Message): void;
|
<Channel extends MessageChannel<unknown>>(channel: Channel, message: Channel extends MessageChannel<infer Message> ? Message : never): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageChannelSender<Channel> = Channel extends MessageChannel<void | undefined>
|
export type MessageChannelSender<Channel> = Channel extends MessageChannel<void | undefined>
|
||||||
|
|||||||
@ -14,6 +14,7 @@ interface Iterator<T> extends Iterable<T> {
|
|||||||
flatMap<U>(fn: (val: T) => U[]): Iterator<U>;
|
flatMap<U>(fn: (val: T) => U[]): Iterator<U>;
|
||||||
concat(src2: IterableIterator<T>): Iterator<T>;
|
concat(src2: IterableIterator<T>): Iterator<T>;
|
||||||
join(sep?: string): string;
|
join(sep?: string): string;
|
||||||
|
count(): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function chain<T>(src: IterableIterator<T>): Iterator<T> {
|
export function chain<T>(src: IterableIterator<T>): Iterator<T> {
|
||||||
@ -26,6 +27,7 @@ export function chain<T>(src: IterableIterator<T>): Iterator<T> {
|
|||||||
join: (sep) => join(src, sep),
|
join: (sep) => join(src, sep),
|
||||||
collect: (fn) => fn(src),
|
collect: (fn) => fn(src),
|
||||||
concat: (src2) => chain(concat(src, src2)),
|
concat: (src2) => chain(concat(src, src2)),
|
||||||
|
count: () => count(src),
|
||||||
[Symbol.iterator]: () => src,
|
[Symbol.iterator]: () => src,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -246,3 +248,14 @@ export function* concat<T>(...sources: IterableIterator<T>[]): IterableIterator<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function count(src: Iterable<unknown>): number {
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
for (const x of src) {
|
||||||
|
void x;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,16 +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 loggerInjectable from "../../common/logger.injectable";
|
|
||||||
import { ExtensionInstallationStateStore } from "./extension-installation-state-store";
|
|
||||||
|
|
||||||
const extensionInstallationStateStoreInjectable = getInjectable({
|
|
||||||
id: "extension-installation-state-store",
|
|
||||||
instantiate: (di) => new ExtensionInstallationStateStore({
|
|
||||||
logger: di.inject(loggerInjectable),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default extensionInstallationStateStoreInjectable;
|
|
||||||
@ -1,247 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { action, computed, observable } from "mobx";
|
|
||||||
import { disposer } from "../../renderer/utils";
|
|
||||||
import type { ExtendableDisposer } from "../../renderer/utils";
|
|
||||||
import * as uuid from "uuid";
|
|
||||||
import { broadcastMessage } from "../../common/ipc";
|
|
||||||
import { ipcRenderer } from "electron";
|
|
||||||
import type { Logger } from "../../common/logger";
|
|
||||||
|
|
||||||
export enum ExtensionInstallationState {
|
|
||||||
INSTALLING = "installing",
|
|
||||||
UNINSTALLING = "uninstalling",
|
|
||||||
IDLE = "idle",
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
readonly logger: Logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Prefix = "[ExtensionInstallationStore]";
|
|
||||||
|
|
||||||
const installingFromMainChannel = "extension-installation-state-store:install";
|
|
||||||
const clearInstallingFromMainChannel = "extension-installation-state-store:clear-install";
|
|
||||||
|
|
||||||
export class ExtensionInstallationStateStore {
|
|
||||||
private readonly preInstallIds = observable.set<string>();
|
|
||||||
private readonly uninstallingExtensions = observable.set<string>();
|
|
||||||
private readonly installingExtensions = observable.set<string>();
|
|
||||||
|
|
||||||
constructor(private readonly dependencies: Dependencies) {}
|
|
||||||
|
|
||||||
bindIpcListeners = () => {
|
|
||||||
ipcRenderer
|
|
||||||
.on(installingFromMainChannel, (event, extId) => {
|
|
||||||
this.setInstalling(extId);
|
|
||||||
})
|
|
||||||
|
|
||||||
.on(clearInstallingFromMainChannel, (event, extId) => {
|
|
||||||
this.clearInstalling(extId);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strictly transitions an extension from not installing to installing
|
|
||||||
* @param extId the ID of the extension
|
|
||||||
* @throws if state is not IDLE
|
|
||||||
*/
|
|
||||||
@action setInstalling = (extId: string): void => {
|
|
||||||
this.dependencies.logger.debug(`${Prefix}: trying to set ${extId} as installing`);
|
|
||||||
|
|
||||||
const curState = this.getInstallationState(extId);
|
|
||||||
|
|
||||||
if (curState !== ExtensionInstallationState.IDLE) {
|
|
||||||
throw new Error(
|
|
||||||
`${Prefix}: cannot set ${extId} as installing. Is currently ${curState}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.installingExtensions.add(extId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcasts that an extension is being installed by the main process
|
|
||||||
* @param extId the ID of the extension
|
|
||||||
*/
|
|
||||||
setInstallingFromMain = (extId: string): void => {
|
|
||||||
broadcastMessage(installingFromMainChannel, extId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcasts that an extension is no longer being installed by the main process
|
|
||||||
* @param extId the ID of the extension
|
|
||||||
*/
|
|
||||||
clearInstallingFromMain = (extId: string): void => {
|
|
||||||
broadcastMessage(clearInstallingFromMainChannel, extId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks the start of a pre-install phase of an extension installation. The
|
|
||||||
* part of the installation before the tarball has been unpacked and the ID
|
|
||||||
* determined.
|
|
||||||
* @returns a disposer which should be called to mark the end of the install phase
|
|
||||||
*/
|
|
||||||
@action startPreInstall = (): ExtendableDisposer => {
|
|
||||||
const preInstallStepId = uuid.v4();
|
|
||||||
|
|
||||||
this.dependencies.logger.debug(
|
|
||||||
`${Prefix}: starting a new preinstall phase: ${preInstallStepId}`,
|
|
||||||
);
|
|
||||||
this.preInstallIds.add(preInstallStepId);
|
|
||||||
|
|
||||||
return disposer(() => {
|
|
||||||
this.preInstallIds.delete(preInstallStepId);
|
|
||||||
this.dependencies.logger.debug(`${Prefix}: ending a preinstall phase: ${preInstallStepId}`);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strictly transitions an extension from not uninstalling to uninstalling
|
|
||||||
* @param extId the ID of the extension
|
|
||||||
* @throws if state is not IDLE
|
|
||||||
*/
|
|
||||||
@action setUninstalling = (extId: string): void => {
|
|
||||||
this.dependencies.logger.debug(`${Prefix}: trying to set ${extId} as uninstalling`);
|
|
||||||
|
|
||||||
const curState = this.getInstallationState(extId);
|
|
||||||
|
|
||||||
if (curState !== ExtensionInstallationState.IDLE) {
|
|
||||||
throw new Error(
|
|
||||||
`${Prefix}: cannot set ${extId} as uninstalling. Is currently ${curState}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.uninstallingExtensions.add(extId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strictly clears the INSTALLING state of an extension
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
* @throws if state is not INSTALLING
|
|
||||||
*/
|
|
||||||
@action clearInstalling = (extId: string): void => {
|
|
||||||
this.dependencies.logger.debug(`${Prefix}: trying to clear ${extId} as installing`);
|
|
||||||
|
|
||||||
const curState = this.getInstallationState(extId);
|
|
||||||
|
|
||||||
switch (curState) {
|
|
||||||
case ExtensionInstallationState.INSTALLING:
|
|
||||||
return void this.installingExtensions.delete(extId);
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`${Prefix}: cannot clear INSTALLING state for ${extId}, it is currently ${curState}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strictly clears the UNINSTALLING state of an extension
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
* @throws if state is not UNINSTALLING
|
|
||||||
*/
|
|
||||||
@action clearUninstalling = (extId: string): void => {
|
|
||||||
this.dependencies.logger.debug(`${Prefix}: trying to clear ${extId} as uninstalling`);
|
|
||||||
|
|
||||||
const curState = this.getInstallationState(extId);
|
|
||||||
|
|
||||||
switch (curState) {
|
|
||||||
case ExtensionInstallationState.UNINSTALLING:
|
|
||||||
return void this.uninstallingExtensions.delete(extId);
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`${Prefix}: cannot clear UNINSTALLING state for ${extId}, it is currently ${curState}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current state of the extension. IDLE is default value.
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
*/
|
|
||||||
getInstallationState = (extId: string): ExtensionInstallationState => {
|
|
||||||
if (this.installingExtensions.has(extId)) {
|
|
||||||
return ExtensionInstallationState.INSTALLING;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.uninstallingExtensions.has(extId)) {
|
|
||||||
return ExtensionInstallationState.UNINSTALLING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExtensionInstallationState.IDLE;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the extension is currently INSTALLING
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
*/
|
|
||||||
isExtensionInstalling = (extId: string): boolean =>
|
|
||||||
this.getInstallationState(extId) === ExtensionInstallationState.INSTALLING;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the extension is currently UNINSTALLING
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
*/
|
|
||||||
isExtensionUninstalling = (extId: string): boolean =>
|
|
||||||
this.getInstallationState(extId) ===
|
|
||||||
ExtensionInstallationState.UNINSTALLING;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the extension is currently IDLE
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
*/
|
|
||||||
isExtensionIdle = (extId: string): boolean =>
|
|
||||||
this.getInstallationState(extId) === ExtensionInstallationState.IDLE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current number of extensions installing
|
|
||||||
*/
|
|
||||||
@computed get installing(): number {
|
|
||||||
return this.installingExtensions.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current number of extensions uninstalling
|
|
||||||
*/
|
|
||||||
get uninstalling(): number {
|
|
||||||
return this.uninstallingExtensions.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there is at least one extension currently installing
|
|
||||||
*/
|
|
||||||
get anyInstalling(): boolean {
|
|
||||||
return this.installing > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there is at least one extension currently uninstalling
|
|
||||||
*/
|
|
||||||
get anyUninstalling(): boolean {
|
|
||||||
return this.uninstalling > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current number of extensions preinstalling
|
|
||||||
*/
|
|
||||||
get preinstalling(): number {
|
|
||||||
return this.preInstallIds.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there is at least one extension currently downloading
|
|
||||||
*/
|
|
||||||
get anyPreinstalling(): boolean {
|
|
||||||
return this.preinstalling > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there is at least one installing or preinstalling step taking place
|
|
||||||
*/
|
|
||||||
get anyPreInstallingOrInstalling(): boolean {
|
|
||||||
return this.anyInstalling || this.anyPreinstalling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -8,8 +8,9 @@ import getDirnameOfPathInjectable from "../../../../common/path/get-dirname.inje
|
|||||||
import getRelativePathInjectable from "../../../../common/path/get-relative-path.injectable";
|
import getRelativePathInjectable from "../../../../common/path/get-relative-path.injectable";
|
||||||
import fileSystemSeparatorInjectable from "../../../../common/path/separator.injectable";
|
import fileSystemSeparatorInjectable from "../../../../common/path/separator.injectable";
|
||||||
import { manifestFilename } from "../../../../common/vars";
|
import { manifestFilename } from "../../../../common/vars";
|
||||||
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import installedExtensionsInjectable from "../../common/installed-extensions.injectable";
|
import installedExtensionsInjectable from "../../common/installed-extensions.injectable";
|
||||||
|
import setExtensionAsInstallingInjectable from "../../installation-states/main/set-as-installing.injectable";
|
||||||
|
import clearExtensionAsInstallingInjectable from "../../installation-states/renderer/clear-as-installing.injectable";
|
||||||
import installExtensionPackageInjectable from "../common/install-package.injectable";
|
import installExtensionPackageInjectable from "../common/install-package.injectable";
|
||||||
import localExtensionsDirectoryPathInjectable from "../common/local-extensions-directory-path.injectable";
|
import localExtensionsDirectoryPathInjectable from "../common/local-extensions-directory-path.injectable";
|
||||||
import extensionDiscoveryLoggerInjectable from "../common/logger.injectable";
|
import extensionDiscoveryLoggerInjectable from "../common/logger.injectable";
|
||||||
@ -22,12 +23,13 @@ const extensionFileAddedInjectable = getInjectable({
|
|||||||
const localExtensionsDirectoryPath = di.inject(localExtensionsDirectoryPathInjectable);
|
const localExtensionsDirectoryPath = di.inject(localExtensionsDirectoryPathInjectable);
|
||||||
const fileSystemSeparator = di.inject(fileSystemSeparatorInjectable);
|
const fileSystemSeparator = di.inject(fileSystemSeparatorInjectable);
|
||||||
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
||||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
|
||||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||||
const loadUserExtensionFromFolder = di.inject(loadUserExtensionFromFolderInjectable);
|
const loadUserExtensionFromFolder = di.inject(loadUserExtensionFromFolderInjectable);
|
||||||
const installExtensionPackage = di.inject(installExtensionPackageInjectable);
|
const installExtensionPackage = di.inject(installExtensionPackageInjectable);
|
||||||
const installedExtensions = di.inject(installedExtensionsInjectable);
|
const installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
const logger = di.inject(extensionDiscoveryLoggerInjectable);
|
const logger = di.inject(extensionDiscoveryLoggerInjectable);
|
||||||
|
const setExtensionAsInstalling = di.inject(setExtensionAsInstallingInjectable);
|
||||||
|
const clearExtensionAsInstalling = di.inject(clearExtensionAsInstallingInjectable);
|
||||||
|
|
||||||
return async (manifestPath: string): Promise<void> => {
|
return async (manifestPath: string): Promise<void> => {
|
||||||
// e.g. "foo/package.json"
|
// e.g. "foo/package.json"
|
||||||
@ -40,7 +42,7 @@ const extensionFileAddedInjectable = getInjectable({
|
|||||||
|
|
||||||
if (getBasenameOfPath(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
|
if (getBasenameOfPath(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
|
||||||
try {
|
try {
|
||||||
extensionInstallationStateStore.setInstallingFromMain(manifestPath);
|
setExtensionAsInstalling(manifestPath);
|
||||||
const absPath = getDirnameOfPath(manifestPath);
|
const absPath = getDirnameOfPath(manifestPath);
|
||||||
|
|
||||||
// this.loadExtensionFromPath updates this.packagesJson
|
// this.loadExtensionFromPath updates this.packagesJson
|
||||||
@ -56,7 +58,7 @@ const extensionFileAddedInjectable = getInjectable({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`failed to add extension: ${error}`, { error });
|
logger.error(`failed to add extension: ${error}`, { error });
|
||||||
} finally {
|
} finally {
|
||||||
extensionInstallationStateStore.clearInstallingFromMain(manifestPath);
|
clearExtensionAsInstalling(manifestPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { MessageChannel } from "../../../../common/utils/channel/message-channel-listener-injection-token";
|
||||||
|
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
|
|
||||||
|
export interface ExtensionInstallPhaseData {
|
||||||
|
id: LensExtensionId;
|
||||||
|
phase: "installing" | "clear-installing";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setExtensionInstallPhaseChannel: MessageChannel<ExtensionInstallPhaseData> = {
|
||||||
|
id: "set-extension-install-phase",
|
||||||
|
};
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
|
|
||||||
|
export type SetExtensionAsInstalling = (id: LensExtensionId) => void;
|
||||||
|
|
||||||
|
export const setExtensionAsInstallingInjectionToken = getInjectionToken<SetExtensionAsInstalling>({
|
||||||
|
id: "set-extension-as-installing-token",
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ClearExtensionAsInstalling = (id: LensExtensionId) => void;
|
||||||
|
|
||||||
|
export const clearExtensionAsInstallingInjectionToken = getInjectionToken<ClearExtensionAsInstalling>({
|
||||||
|
id: "clear-extension-as-installing-token",
|
||||||
|
});
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* 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 { sendMessageToChannelInjectionToken } from "../../../../common/utils/channel/message-to-channel-injection-token";
|
||||||
|
import { setExtensionInstallPhaseChannel } from "../common/channels";
|
||||||
|
import { clearExtensionAsInstallingInjectionToken } from "../common/tokens";
|
||||||
|
|
||||||
|
const clearExtensionAsInstallingInjectable = getInjectable({
|
||||||
|
id: "clear-extension-as-installing",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
|
||||||
|
|
||||||
|
return (id) => sendMessageToChannel(setExtensionInstallPhaseChannel, {
|
||||||
|
id,
|
||||||
|
phase: "clear-installing",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
injectionToken: clearExtensionAsInstallingInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clearExtensionAsInstallingInjectable;
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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 { sendMessageToChannelInjectionToken } from "../../../../common/utils/channel/message-to-channel-injection-token";
|
||||||
|
import { setExtensionInstallPhaseChannel } from "../common/channels";
|
||||||
|
import { setExtensionAsInstallingInjectionToken } from "../common/tokens";
|
||||||
|
|
||||||
|
const setExtensionAsInstallingInjectable = getInjectable({
|
||||||
|
id: "set-extension-as-installing",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
|
||||||
|
|
||||||
|
return (id) => sendMessageToChannel(setExtensionInstallPhaseChannel, {
|
||||||
|
id,
|
||||||
|
phase: "installing",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
injectionToken: setExtensionAsInstallingInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default setExtensionAsInstallingInjectable;
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 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 { clearExtensionAsInstallingInjectionToken } from "../common/tokens";
|
||||||
|
import extensionInstallationStatesInjectable from "./states.injectable";
|
||||||
|
|
||||||
|
const clearExtensionAsInstallingInjectable = getInjectable({
|
||||||
|
id: "clear-extension-as-installing",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const states = di.inject(extensionInstallationStatesInjectable);
|
||||||
|
|
||||||
|
return (id) => states.delete(id);
|
||||||
|
},
|
||||||
|
injectionToken: clearExtensionAsInstallingInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clearExtensionAsInstallingInjectable;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 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 type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
|
import type { InstallationState } from "./states.injectable";
|
||||||
|
import extensionInstallationStatesInjectable from "./states.injectable";
|
||||||
|
|
||||||
|
export type GetExtensionInstallationPhase = (id: LensExtensionId) => InstallationState;
|
||||||
|
|
||||||
|
const getExtensionInstallationPhaseInjectable = getInjectable({
|
||||||
|
id: "get-extension-installation-phase",
|
||||||
|
instantiate: (di): GetExtensionInstallationPhase => {
|
||||||
|
const states = di.inject(extensionInstallationStatesInjectable);
|
||||||
|
|
||||||
|
return (id) => states.get(id) ?? "idle";
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getExtensionInstallationPhaseInjectable;
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* 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 { computed } from "mobx";
|
||||||
|
import { iter } from "../../../../common/utils";
|
||||||
|
import extensionInstallationStatesInjectable from "./states.injectable";
|
||||||
|
|
||||||
|
const extensionsInstallingCountInjectable = getInjectable({
|
||||||
|
id: "extensions-installing-count",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const states = di.inject(extensionInstallationStatesInjectable);
|
||||||
|
|
||||||
|
return computed(() => (
|
||||||
|
iter.chain(states.entries())
|
||||||
|
.filter(([, state]) => state === "installing")
|
||||||
|
.count()
|
||||||
|
));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionsInstallingCountInjectable;
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 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 { computed } from "mobx";
|
||||||
|
import preinstallingPhasesInjectable from "./preinstalling.injectable";
|
||||||
|
|
||||||
|
const extensionsPreinstallingCountInjectable = getInjectable({
|
||||||
|
id: "extensions-preinstalling-count",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const preinstallingPhases = di.inject(preinstallingPhasesInjectable);
|
||||||
|
|
||||||
|
return computed(() => preinstallingPhases.size);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionsPreinstallingCountInjectable;
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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 { observable } from "mobx";
|
||||||
|
|
||||||
|
const preinstallingPhasesInjectable = getInjectable({
|
||||||
|
id: "preinstalling-phases",
|
||||||
|
instantiate: () => observable.set<string>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default preinstallingPhasesInjectable;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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 { setExtensionAsInstallingInjectionToken } from "../common/tokens";
|
||||||
|
import extensionInstallationStatesInjectable from "./states.injectable";
|
||||||
|
|
||||||
|
const setExtensionAsInstallingInjectable = getInjectable({
|
||||||
|
id: "set-extension-as-installing",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const states = di.inject(extensionInstallationStatesInjectable);
|
||||||
|
|
||||||
|
return (id) => states.set(id, "installing");
|
||||||
|
},
|
||||||
|
injectionToken: setExtensionAsInstallingInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default setExtensionAsInstallingInjectable;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 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 type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
|
import extensionInstallationStatesInjectable from "./states.injectable";
|
||||||
|
|
||||||
|
export type SetExtensionAsUninstalling = (id: LensExtensionId) => void;
|
||||||
|
|
||||||
|
const setExtensionAsUninstallingInjectable = getInjectable({
|
||||||
|
id: "set-extension-as-uninstalling",
|
||||||
|
instantiate: (di): SetExtensionAsUninstalling => {
|
||||||
|
const states = di.inject(extensionInstallationStatesInjectable);
|
||||||
|
|
||||||
|
return (id) => states.set(id, "uninstalling");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default setExtensionAsUninstallingInjectable;
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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 { action } from "mobx";
|
||||||
|
import type { Disposer } from "../../../../common/utils";
|
||||||
|
import preinstallingPhasesInjectable from "./preinstalling.injectable";
|
||||||
|
import * as uuid from "uuid";
|
||||||
|
|
||||||
|
export type StartPreInstallPhase = () => Disposer;
|
||||||
|
|
||||||
|
const startPreInstallPhaseInjectable = getInjectable({
|
||||||
|
id: "start-pre-install-phase",
|
||||||
|
instantiate: (di): StartPreInstallPhase => {
|
||||||
|
const preinstalling = di.inject(preinstallingPhasesInjectable);
|
||||||
|
|
||||||
|
return action(() => {
|
||||||
|
const preInstallStepId = uuid.v4();
|
||||||
|
|
||||||
|
preinstalling.add(preInstallStepId);
|
||||||
|
|
||||||
|
return () => preinstalling.delete(preInstallStepId);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default startPreInstallPhaseInjectable;
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 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 { observable } from "mobx";
|
||||||
|
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
|
|
||||||
|
export type InstallationState = ActiveInstallationState | "idle";
|
||||||
|
export type ActiveInstallationState = "installing" | "uninstalling";
|
||||||
|
|
||||||
|
const extensionInstallationStatesInjectable = getInjectable({
|
||||||
|
id: "extension-installation-states",
|
||||||
|
instantiate: () => observable.map<LensExtensionId, InstallationState>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionInstallationStatesInjectable;
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* 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 { computed } from "mobx";
|
||||||
|
import { iter } from "../../../../common/utils";
|
||||||
|
import extensionInstallationStatesInjectable from "./states.injectable";
|
||||||
|
|
||||||
|
const extensionsUninstallingCountInjectable = getInjectable({
|
||||||
|
id: "extensions-uninstalling-count",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const states = di.inject(extensionInstallationStatesInjectable);
|
||||||
|
|
||||||
|
return computed(() => (
|
||||||
|
iter.chain(states.entries())
|
||||||
|
.filter(([, state]) => state === "uninstalling")
|
||||||
|
.count()
|
||||||
|
));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionsUninstallingCountInjectable;
|
||||||
@ -10,7 +10,6 @@ import { render, unmountComponentAtNode } from "react-dom";
|
|||||||
import { DefaultProps } from "./mui-base-theme";
|
import { DefaultProps } from "./mui-base-theme";
|
||||||
import { DiContextProvider } from "@ogre-tools/injectable-react";
|
import { DiContextProvider } from "@ogre-tools/injectable-react";
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import initRootFrameInjectable from "./frames/root-frame/init-root-frame.injectable";
|
import initRootFrameInjectable from "./frames/root-frame/init-root-frame.injectable";
|
||||||
import initClusterFrameInjectable from "./frames/cluster-frame/init-cluster-frame.injectable";
|
import initClusterFrameInjectable from "./frames/cluster-frame/init-cluster-frame.injectable";
|
||||||
import { Router } from "react-router";
|
import { Router } from "react-router";
|
||||||
@ -27,8 +26,6 @@ export async function bootstrap(di: DiContainer) {
|
|||||||
|
|
||||||
assert(rootElem, "#app MUST exist");
|
assert(rootElem, "#app MUST exist");
|
||||||
|
|
||||||
di.inject(extensionInstallationStateStoreInjectable).bindIpcListeners();
|
|
||||||
|
|
||||||
let App;
|
let App;
|
||||||
let initializeApp;
|
let initializeApp;
|
||||||
|
|
||||||
|
|||||||
@ -17,8 +17,6 @@ import directoryForDownloadsInjectable from "../../../../common/app-paths/direct
|
|||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import type { InstallExtensionFromInput } from "../install-extension-from-input.injectable";
|
import type { InstallExtensionFromInput } from "../install-extension-from-input.injectable";
|
||||||
import installExtensionFromInputInjectable from "../install-extension-from-input.injectable";
|
import installExtensionFromInputInjectable from "../install-extension-from-input.injectable";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import type { IObservableValue, ObservableMap } from "mobx";
|
import type { IObservableValue, ObservableMap } from "mobx";
|
||||||
import { computed, observable, when } from "mobx";
|
import { computed, observable, when } from "mobx";
|
||||||
import type { RemovePath } from "../../../../common/fs/remove.injectable";
|
import type { RemovePath } from "../../../../common/fs/remove.injectable";
|
||||||
@ -31,16 +29,18 @@ import installedExtensionsInjectable from "../../../../features/extensions/commo
|
|||||||
import initialDiscoveryLoadCompletedInjectable from "../../../../features/extensions/discovery/common/initial-load-completed.injectable";
|
import initialDiscoveryLoadCompletedInjectable from "../../../../features/extensions/discovery/common/initial-load-completed.injectable";
|
||||||
import type { RemoveExtensionFiles } from "../../../../features/extensions/discovery/common/uninstall-extension.injectable";
|
import type { RemoveExtensionFiles } from "../../../../features/extensions/discovery/common/uninstall-extension.injectable";
|
||||||
import removeExtensionFilesInjectable from "../../../../features/extensions/discovery/common/uninstall-extension.injectable";
|
import removeExtensionFilesInjectable from "../../../../features/extensions/discovery/common/uninstall-extension.injectable";
|
||||||
|
import type { StartPreInstallPhase } from "../../../../features/extensions/installation-states/renderer/start-pre-install-phase.injectable";
|
||||||
|
import startPreInstallPhaseInjectable from "../../../../features/extensions/installation-states/renderer/start-pre-install-phase.injectable";
|
||||||
|
|
||||||
describe("Extensions", () => {
|
describe("Extensions", () => {
|
||||||
let installedExtensions: ObservableMap<LensExtensionId, InstalledExtension>;
|
let installedExtensions: ObservableMap<LensExtensionId, InstalledExtension>;
|
||||||
let installExtensionFromInput: jest.MockedFunction<InstallExtensionFromInput>;
|
let installExtensionFromInput: jest.MockedFunction<InstallExtensionFromInput>;
|
||||||
let extensionInstallationStateStore: ExtensionInstallationStateStore;
|
|
||||||
let render: DiRender;
|
let render: DiRender;
|
||||||
let deleteFileMock: jest.MockedFunction<RemovePath>;
|
let deleteFileMock: jest.MockedFunction<RemovePath>;
|
||||||
let downloadBinary: jest.MockedFunction<DownloadBinary>;
|
let downloadBinary: jest.MockedFunction<DownloadBinary>;
|
||||||
let isLoaded: IObservableValue<boolean>;
|
let isLoaded: IObservableValue<boolean>;
|
||||||
let removeExtensionFilesMock: jest.MockedFunction<RemoveExtensionFiles>;
|
let removeExtensionFilesMock: jest.MockedFunction<RemoveExtensionFiles>;
|
||||||
|
let startPreInstallPhase: StartPreInstallPhase;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
@ -69,7 +69,7 @@ describe("Extensions", () => {
|
|||||||
di.override(downloadBinaryInjectable, () => downloadBinary);
|
di.override(downloadBinaryInjectable, () => downloadBinary);
|
||||||
|
|
||||||
installedExtensions = di.inject(installedExtensionsInjectable);
|
installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
startPreInstallPhase = di.inject(startPreInstallPhaseInjectable);
|
||||||
|
|
||||||
installedExtensions.set("extensionId", {
|
installedExtensions.set("extensionId", {
|
||||||
id: "extensionId",
|
id: "extensionId",
|
||||||
@ -133,10 +133,10 @@ describe("Extensions", () => {
|
|||||||
installExtensionFromInput.mockImplementation(async (input) => {
|
installExtensionFromInput.mockImplementation(async (input) => {
|
||||||
expect(input).toBe("https://test.extensionurl/package.tgz");
|
expect(input).toBe("https://test.extensionurl/package.tgz");
|
||||||
|
|
||||||
const clear = extensionInstallationStateStore.startPreInstall();
|
const clearPreInstallPhase = startPreInstallPhase();
|
||||||
|
|
||||||
await when(() => resolveInstall.get());
|
await when(() => resolveInstall.get());
|
||||||
clear();
|
clearPreInstallPhase();
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.change(await screen.findByPlaceholderText("File path or URL", {
|
fireEvent.change(await screen.findByPlaceholderText("File path or URL", {
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import URLParse from "url-parse";
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
|
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
|
||||||
import getBaseRegistryUrlInjectable from "./get-base-registry-url/get-base-registry-url.injectable";
|
import getBaseRegistryUrlInjectable from "./get-base-registry-url/get-base-registry-url.injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import confirmInjectable from "../confirm-dialog/confirm.injectable";
|
import confirmInjectable from "../confirm-dialog/confirm.injectable";
|
||||||
import { reduce } from "lodash";
|
import { reduce } from "lodash";
|
||||||
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
||||||
@ -19,6 +18,7 @@ import downloadJsonInjectable from "../../../common/fetch/download-json/normal.i
|
|||||||
import type { PackageJson } from "type-fest";
|
import type { PackageJson } from "type-fest";
|
||||||
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
|
import startPreInstallPhaseInjectable from "../../../features/extensions/installation-states/renderer/start-pre-install-phase.injectable";
|
||||||
|
|
||||||
export interface ExtensionInfo {
|
export interface ExtensionInfo {
|
||||||
name: string;
|
name: string;
|
||||||
@ -46,17 +46,17 @@ const attemptInstallByInfoInjectable = getInjectable({
|
|||||||
instantiate: (di): AttemptInstallByInfo => {
|
instantiate: (di): AttemptInstallByInfo => {
|
||||||
const attemptInstall = di.inject(attemptInstallInjectable);
|
const attemptInstall = di.inject(attemptInstallInjectable);
|
||||||
const getBaseRegistryUrl = di.inject(getBaseRegistryUrlInjectable);
|
const getBaseRegistryUrl = di.inject(getBaseRegistryUrlInjectable);
|
||||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
|
||||||
const confirm = di.inject(confirmInjectable);
|
const confirm = di.inject(confirmInjectable);
|
||||||
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
||||||
const downloadJson = di.inject(downloadJsonInjectable);
|
const downloadJson = di.inject(downloadJsonInjectable);
|
||||||
const downloadBinary = di.inject(downloadBinaryInjectable);
|
const downloadBinary = di.inject(downloadBinaryInjectable);
|
||||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
|
const startPreInstallPhase = di.inject(startPreInstallPhaseInjectable);
|
||||||
|
|
||||||
return async (info) => {
|
return async (info) => {
|
||||||
const { name, version: versionOrTagName, requireConfirmation = false } = info;
|
const { name, version: versionOrTagName, requireConfirmation = false } = info;
|
||||||
const disposer = extensionInstallationStateStore.startPreInstall();
|
const clearPreInstallPhase = startPreInstallPhase();
|
||||||
const baseUrl = await getBaseRegistryUrl();
|
const baseUrl = await getBaseRegistryUrl();
|
||||||
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
|
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
|
||||||
let json: NpmRegistryPackageDescriptor;
|
let json: NpmRegistryPackageDescriptor;
|
||||||
@ -67,13 +67,13 @@ const attemptInstallByInfoInjectable = getInjectable({
|
|||||||
if (!result.callWasSuccessful) {
|
if (!result.callWasSuccessful) {
|
||||||
showErrorNotification(`Failed to get registry information for extension: ${result.error}`);
|
showErrorNotification(`Failed to get registry information for extension: ${result.error}`);
|
||||||
|
|
||||||
return disposer();
|
return clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isObject(result.response) || Array.isArray(result.response)) {
|
if (!isObject(result.response) || Array.isArray(result.response)) {
|
||||||
showErrorNotification("Failed to get registry information for extension");
|
showErrorNotification("Failed to get registry information for extension");
|
||||||
|
|
||||||
return disposer();
|
return clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.response.error || !isObject(result.response.versions)) {
|
if (result.response.error || !isObject(result.response.versions)) {
|
||||||
@ -81,7 +81,7 @@ const attemptInstallByInfoInjectable = getInjectable({
|
|||||||
|
|
||||||
showErrorNotification(`Failed to get registry information for extension${message}`);
|
showErrorNotification(`Failed to get registry information for extension${message}`);
|
||||||
|
|
||||||
return disposer();
|
return clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
json = result.response as unknown as NpmRegistryPackageDescriptor;
|
json = result.response as unknown as NpmRegistryPackageDescriptor;
|
||||||
@ -95,7 +95,7 @@ const attemptInstallByInfoInjectable = getInjectable({
|
|||||||
showErrorNotification(`Failed to get valid registry information for extension. ${error}`);
|
showErrorNotification(`Failed to get valid registry information for extension. ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return disposer();
|
return clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
let version = versionOrTagName;
|
let version = versionOrTagName;
|
||||||
@ -119,7 +119,7 @@ const attemptInstallByInfoInjectable = getInjectable({
|
|||||||
</p>
|
</p>
|
||||||
));
|
));
|
||||||
|
|
||||||
return disposer();
|
return clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
version = potentialVersion;
|
version = potentialVersion;
|
||||||
@ -137,7 +137,7 @@ const attemptInstallByInfoInjectable = getInjectable({
|
|||||||
</p>
|
</p>
|
||||||
));
|
));
|
||||||
|
|
||||||
return disposer();
|
return clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const versions = Object.keys(json.versions)
|
const versions = Object.keys(json.versions)
|
||||||
@ -154,7 +154,7 @@ const attemptInstallByInfoInjectable = getInjectable({
|
|||||||
logger.error("No versions supplied for extension", { name });
|
logger.error("No versions supplied for extension", { name });
|
||||||
showErrorNotification(`No versions found for ${name}`);
|
showErrorNotification(`No versions found for ${name}`);
|
||||||
|
|
||||||
return disposer();
|
return clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
const versionInfo = json.versions[version];
|
const versionInfo = json.versions[version];
|
||||||
@ -164,7 +164,7 @@ const attemptInstallByInfoInjectable = getInjectable({
|
|||||||
showErrorNotification("Configured registry has invalid data model. Please verify that it is like NPM's.");
|
showErrorNotification("Configured registry has invalid data model. Please verify that it is like NPM's.");
|
||||||
logger.warn(`[ATTEMPT-INSTALL-BY-INFO]: registry returned unexpected data, final version is ${version} but the versions object is missing .dist.tarball as a string`, versionInfo);
|
logger.warn(`[ATTEMPT-INSTALL-BY-INFO]: registry returned unexpected data, final version is ${version} but the versions object is missing .dist.tarball as a string`, versionInfo);
|
||||||
|
|
||||||
return disposer();
|
return clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requireConfirmation) {
|
if (requireConfirmation) {
|
||||||
@ -183,7 +183,7 @@ const attemptInstallByInfoInjectable = getInjectable({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!proceed) {
|
if (!proceed) {
|
||||||
return disposer();
|
return clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,10 +194,10 @@ const attemptInstallByInfoInjectable = getInjectable({
|
|||||||
if (!request.callWasSuccessful) {
|
if (!request.callWasSuccessful) {
|
||||||
showErrorNotification(`Failed to download extension: ${request.error}`);
|
showErrorNotification(`Failed to download extension: ${request.error}`);
|
||||||
|
|
||||||
return disposer();
|
return clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
return attemptInstall({ fileName, data: request.response }, disposer);
|
return attemptInstall({ fileName, data: request.response }, clearPreInstallPhase);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,17 +7,17 @@ import uninstallExtensionInjectable from "../uninstall-extension.injectable";
|
|||||||
import unpackExtensionInjectable from "./unpack-extension.injectable";
|
import unpackExtensionInjectable from "./unpack-extension.injectable";
|
||||||
import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable";
|
import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable";
|
||||||
import createTempFilesAndValidateInjectable from "./create-temp-files-and-validate.injectable";
|
import createTempFilesAndValidateInjectable from "./create-temp-files-and-validate.injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import type { Disposer } from "../../../../common/utils";
|
import type { Disposer } from "../../../../common/utils";
|
||||||
import { disposer } from "../../../../common/utils";
|
import { disposer } from "../../../../common/utils";
|
||||||
import { Button } from "../../button";
|
import { Button } from "../../button";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { remove as removeDir } from "fs-extra";
|
import { remove as removeDir } from "fs-extra";
|
||||||
import { shell } from "electron";
|
import { shell } from "electron";
|
||||||
import { ExtensionInstallationState } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
||||||
import showInfoNotificationInjectable from "../../notifications/show-info-notification.injectable";
|
import showInfoNotificationInjectable from "../../notifications/show-info-notification.injectable";
|
||||||
import getInstalledExtensionInjectable from "../../../../features/extensions/common/get-installed-extension.injectable";
|
import getInstalledExtensionInjectable from "../../../../features/extensions/common/get-installed-extension.injectable";
|
||||||
|
import startPreInstallPhaseInjectable from "../../../../features/extensions/installation-states/renderer/start-pre-install-phase.injectable";
|
||||||
|
import getExtensionInstallationPhaseInjectable from "../../../../features/extensions/installation-states/renderer/get-phase.injectable";
|
||||||
|
|
||||||
export interface InstallRequest {
|
export interface InstallRequest {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
@ -33,14 +33,15 @@ const attemptInstallInjectable = getInjectable({
|
|||||||
const unpackExtension = di.inject(unpackExtensionInjectable);
|
const unpackExtension = di.inject(unpackExtensionInjectable);
|
||||||
const createTempFilesAndValidate = di.inject(createTempFilesAndValidateInjectable);
|
const createTempFilesAndValidate = di.inject(createTempFilesAndValidateInjectable);
|
||||||
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
|
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
|
||||||
const installStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
|
||||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
const showInfoNotification = di.inject(showInfoNotificationInjectable);
|
const showInfoNotification = di.inject(showInfoNotificationInjectable);
|
||||||
const getInstalledExtension = di.inject(getInstalledExtensionInjectable);
|
const getInstalledExtension = di.inject(getInstalledExtensionInjectable);
|
||||||
|
const startPreInstallPhase = di.inject(startPreInstallPhaseInjectable);
|
||||||
|
const getExtensionInstallationPhase = di.inject(getExtensionInstallationPhaseInjectable);
|
||||||
|
|
||||||
return async (request, cleanup) => {
|
return async (request, cleanup) => {
|
||||||
const dispose = disposer(
|
const dispose = disposer(
|
||||||
installStateStore.startPreInstall(),
|
startPreInstallPhase(),
|
||||||
cleanup,
|
cleanup,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -51,9 +52,9 @@ const attemptInstallInjectable = getInjectable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { name, version, description } = validatedRequest.manifest;
|
const { name, version, description } = validatedRequest.manifest;
|
||||||
const curState = installStateStore.getInstallationState(validatedRequest.id);
|
const curState = getExtensionInstallationPhase(validatedRequest.id);
|
||||||
|
|
||||||
if (curState !== ExtensionInstallationState.IDLE) {
|
if (curState !== "idle") {
|
||||||
dispose();
|
dispose();
|
||||||
|
|
||||||
return void showErrorNotification(
|
return void showErrorNotification(
|
||||||
@ -115,7 +116,7 @@ const attemptInstallInjectable = getInjectable({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// clean up old data if still around
|
// clean up old data if still around
|
||||||
await removeDir(extensionFolder);
|
await removeDir(extensionFolder);
|
||||||
|
|
||||||
// install extension if not yet exists
|
// install extension if not yet exists
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable";
|
import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import type { Disposer } from "../../../../common/utils";
|
import type { Disposer } from "../../../../common/utils";
|
||||||
import { noop } from "../../../../common/utils";
|
import { noop } from "../../../../common/utils";
|
||||||
import { extensionDisplayName } from "../../../../extensions/lens-extension";
|
import { extensionDisplayName } from "../../../../extensions/lens-extension";
|
||||||
@ -20,6 +19,8 @@ import showInfoNotificationInjectable from "../../notifications/show-info-notifi
|
|||||||
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
||||||
import installedUserExtensionsInjectable from "../../../../features/extensions/common/user-extensions.injectable";
|
import installedUserExtensionsInjectable from "../../../../features/extensions/common/user-extensions.injectable";
|
||||||
import enableExtensionInjectable from "../enable-extension.injectable";
|
import enableExtensionInjectable from "../enable-extension.injectable";
|
||||||
|
import setExtensionAsInstallingInjectable from "../../../../features/extensions/installation-states/renderer/set-as-installing.injectable";
|
||||||
|
import clearExtensionAsInstallingInjectable from "../../../../features/extensions/installation-states/renderer/clear-as-installing.injectable";
|
||||||
|
|
||||||
export type UnpackExtension = (request: InstallRequestValidated, disposeDownloading?: Disposer) => Promise<void>;
|
export type UnpackExtension = (request: InstallRequestValidated, disposeDownloading?: Disposer) => Promise<void>;
|
||||||
|
|
||||||
@ -27,13 +28,14 @@ const unpackExtensionInjectable = getInjectable({
|
|||||||
id: "unpack-extension",
|
id: "unpack-extension",
|
||||||
instantiate: (di): UnpackExtension => {
|
instantiate: (di): UnpackExtension => {
|
||||||
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
|
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
|
||||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
|
||||||
const extractTar = di.inject(extractTarInjectable);
|
const extractTar = di.inject(extractTarInjectable);
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const showInfoNotification = di.inject(showInfoNotificationInjectable);
|
const showInfoNotification = di.inject(showInfoNotificationInjectable);
|
||||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
const installedUserExtensions = di.inject(installedUserExtensionsInjectable);
|
const installedUserExtensions = di.inject(installedUserExtensionsInjectable);
|
||||||
const enableExtension = di.inject(enableExtensionInjectable);
|
const enableExtension = di.inject(enableExtensionInjectable);
|
||||||
|
const setExtensionAsInstalling = di.inject(setExtensionAsInstallingInjectable);
|
||||||
|
const clearExtensionAsInstalling = di.inject(clearExtensionAsInstallingInjectable);
|
||||||
|
|
||||||
return async (request, disposeDownloading) => {
|
return async (request, disposeDownloading) => {
|
||||||
const {
|
const {
|
||||||
@ -43,7 +45,7 @@ const unpackExtensionInjectable = getInjectable({
|
|||||||
manifest: { name, version },
|
manifest: { name, version },
|
||||||
} = request;
|
} = request;
|
||||||
|
|
||||||
extensionInstallationStateStore.setInstalling(id);
|
setExtensionAsInstalling(id);
|
||||||
disposeDownloading?.();
|
disposeDownloading?.();
|
||||||
|
|
||||||
const displayName = extensionDisplayName(name, version);
|
const displayName = extensionDisplayName(name, version);
|
||||||
@ -103,8 +105,8 @@ const unpackExtensionInjectable = getInjectable({
|
|||||||
</p>
|
</p>
|
||||||
));
|
));
|
||||||
} finally {
|
} finally {
|
||||||
// Remove install state once finished
|
// Remove install state once finished
|
||||||
extensionInstallationStateStore.clearInstalling(id);
|
clearExtensionAsInstalling(id);
|
||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
fse.remove(unpackingTempFolder).catch(noop);
|
fse.remove(unpackingTempFolder).catch(noop);
|
||||||
|
|||||||
@ -35,9 +35,8 @@ import type { LensExtensionId } from "../../../extensions/lens-extension";
|
|||||||
import type { InstallOnDrop } from "./install-on-drop.injectable";
|
import type { InstallOnDrop } from "./install-on-drop.injectable";
|
||||||
import installOnDropInjectable from "./install-on-drop.injectable";
|
import installOnDropInjectable from "./install-on-drop.injectable";
|
||||||
import { supportedExtensionFormats } from "./supported-extension-formats";
|
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 Gutter from "../gutter/gutter";
|
||||||
|
import extensionsInstallingCountInjectable from "../../../features/extensions/installation-states/renderer/installing-count.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
userExtensions: IComputedValue<InstalledExtension[]>;
|
userExtensions: IComputedValue<InstalledExtension[]>;
|
||||||
@ -47,7 +46,7 @@ interface Dependencies {
|
|||||||
installExtensionFromInput: InstallExtensionFromInput;
|
installExtensionFromInput: InstallExtensionFromInput;
|
||||||
installFromSelectFileDialog: () => Promise<void>;
|
installFromSelectFileDialog: () => Promise<void>;
|
||||||
installOnDrop: InstallOnDrop;
|
installOnDrop: InstallOnDrop;
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
extensionsInstallingCount: IComputedValue<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -64,7 +63,7 @@ class NonInjectedExtensions extends React.Component<Dependencies> {
|
|||||||
reaction(() => this.props.userExtensions.get().length, (curSize, prevSize) => {
|
reaction(() => this.props.userExtensions.get().length, (curSize, prevSize) => {
|
||||||
if (curSize > prevSize) {
|
if (curSize > prevSize) {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
when(() => !this.props.extensionInstallationStateStore.anyInstalling, () => this.installPath = ""),
|
when(() => this.props.extensionsInstallingCount.get() === 0, () => this.installPath = ""),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -138,6 +137,6 @@ export const Extensions = withInjectables<Dependencies>(NonInjectedExtensions, {
|
|||||||
installExtensionFromInput: di.inject(installExtensionFromInputInjectable),
|
installExtensionFromInput: di.inject(installExtensionFromInputInjectable),
|
||||||
installOnDrop: di.inject(installOnDropInjectable),
|
installOnDrop: di.inject(installOnDropInjectable),
|
||||||
installFromSelectFileDialog: di.inject(installFromSelectFileDialogInjectable),
|
installFromSelectFileDialog: di.inject(installFromSelectFileDialogInjectable),
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
extensionsInstallingCount: di.inject(extensionsInstallingCountInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,19 +3,20 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { ExtendableDisposer } from "../../../common/utils";
|
import type { Disposer } from "../../../common/utils";
|
||||||
|
import { noop } from "../../../common/utils";
|
||||||
import { InputValidators } from "../input";
|
import { InputValidators } from "../input";
|
||||||
import { getMessageFromError } from "./get-message-from-error/get-message-from-error";
|
import { getMessageFromError } from "./get-message-from-error/get-message-from-error";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
|
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
|
||||||
import attemptInstallByInfoInjectable from "./attempt-install-by-info.injectable";
|
import attemptInstallByInfoInjectable from "./attempt-install-by-info.injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import readFileNotifyInjectable from "./read-file-notify/read-file-notify.injectable";
|
import readFileNotifyInjectable from "./read-file-notify/read-file-notify.injectable";
|
||||||
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
||||||
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
import downloadBinaryInjectable from "../../../common/fetch/download-binary.injectable";
|
import downloadBinaryInjectable from "../../../common/fetch/download-binary.injectable";
|
||||||
import { withTimeout } from "../../../common/fetch/timeout-controller";
|
import { withTimeout } from "../../../common/fetch/timeout-controller";
|
||||||
|
import startPreInstallPhaseInjectable from "../../../features/extensions/installation-states/renderer/start-pre-install-phase.injectable";
|
||||||
|
|
||||||
export type InstallExtensionFromInput = (input: string) => Promise<void>;
|
export type InstallExtensionFromInput = (input: string) => Promise<void>;
|
||||||
|
|
||||||
@ -25,33 +26,34 @@ const installExtensionFromInputInjectable = getInjectable({
|
|||||||
instantiate: (di): InstallExtensionFromInput => {
|
instantiate: (di): InstallExtensionFromInput => {
|
||||||
const attemptInstall = di.inject(attemptInstallInjectable);
|
const attemptInstall = di.inject(attemptInstallInjectable);
|
||||||
const attemptInstallByInfo = di.inject(attemptInstallByInfoInjectable);
|
const attemptInstallByInfo = di.inject(attemptInstallByInfoInjectable);
|
||||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
|
||||||
const readFileNotify = di.inject(readFileNotifyInjectable);
|
const readFileNotify = di.inject(readFileNotifyInjectable);
|
||||||
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
||||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const downloadBinary = di.inject(downloadBinaryInjectable);
|
const downloadBinary = di.inject(downloadBinaryInjectable);
|
||||||
|
const startPreInstallPhase = di.inject(startPreInstallPhaseInjectable);
|
||||||
|
|
||||||
return async (input) => {
|
return async (input) => {
|
||||||
let disposer: ExtendableDisposer | undefined = undefined;
|
let clearPreInstallPhase: Disposer = noop;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// fixme: improve error messages for non-tar-file URLs
|
// fixme: improve error messages for non-tar-file URLs
|
||||||
if (InputValidators.isUrl.validate(input)) {
|
if (InputValidators.isUrl.validate(input)) {
|
||||||
// install via url
|
// install via url
|
||||||
disposer = extensionInstallationStateStore.startPreInstall();
|
clearPreInstallPhase = startPreInstallPhase();
|
||||||
const { signal } = withTimeout(10 * 60 * 1000);
|
const { signal } = withTimeout(10 * 60 * 1000);
|
||||||
const result = await downloadBinary(input, { signal });
|
const result = await downloadBinary(input, { signal });
|
||||||
|
|
||||||
if (!result.callWasSuccessful) {
|
if (!result.callWasSuccessful) {
|
||||||
showErrorNotification(`Failed to download extension: ${result.error}`);
|
showErrorNotification(`Failed to download extension: ${result.error}`);
|
||||||
|
clearPreInstallPhase();
|
||||||
|
|
||||||
return disposer();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileName = getBasenameOfPath(input);
|
const fileName = getBasenameOfPath(input);
|
||||||
|
|
||||||
return await attemptInstall({ fileName, data: result.response }, disposer);
|
return await attemptInstall({ fileName, data: result.response }, clearPreInstallPhase);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -88,7 +90,7 @@ const installExtensionFromInputInjectable = getInjectable({
|
|||||||
</p>
|
</p>
|
||||||
));
|
));
|
||||||
} finally {
|
} finally {
|
||||||
disposer?.();
|
clearPreInstallPhase();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,14 +8,15 @@ import React from "react";
|
|||||||
import { prevDefault } from "../../utils";
|
import { prevDefault } from "../../utils";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { Input, InputValidators } from "../input";
|
import { Input, InputValidators } from "../input";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { TooltipPosition } from "../tooltip";
|
import { TooltipPosition } from "../tooltip";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import { unionInputValidatorsAsync } from "../input/input_validators";
|
import { unionInputValidatorsAsync } from "../input/input_validators";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import extensionsInstallingCountInjectable from "../../../features/extensions/installation-states/renderer/installing-count.injectable";
|
||||||
|
import extensionsPreinstallingCountInjectable from "../../../features/extensions/installation-states/renderer/preinstalling-count.injectable";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
export interface InstallProps {
|
export interface InstallProps {
|
||||||
installPath: string;
|
installPath: string;
|
||||||
@ -26,7 +27,8 @@ export interface InstallProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
extensionsInstallingCount: IComputedValue<number>;
|
||||||
|
extensionsPreinstallingCount: IComputedValue<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const installInputValidator = unionInputValidatorsAsync(
|
const installInputValidator = unionInputValidatorsAsync(
|
||||||
@ -38,71 +40,67 @@ const installInputValidator = unionInputValidatorsAsync(
|
|||||||
InputValidators.isPath,
|
InputValidators.isPath,
|
||||||
);
|
);
|
||||||
|
|
||||||
const NonInjectedInstall: React.FC<Dependencies & InstallProps> = ({
|
const NonInjectedInstall = observer(({
|
||||||
installPath,
|
installPath,
|
||||||
supportedFormats,
|
supportedFormats,
|
||||||
onChange,
|
onChange,
|
||||||
installFromInput,
|
installFromInput,
|
||||||
installFromSelectFileDialog,
|
installFromSelectFileDialog,
|
||||||
extensionInstallationStateStore,
|
extensionsInstallingCount,
|
||||||
}) => (
|
extensionsPreinstallingCount,
|
||||||
<section>
|
}: Dependencies & InstallProps) => {
|
||||||
<SubTitle
|
const anyPreInstallingOrInstalling = extensionsInstallingCount.get() > 0 || extensionsPreinstallingCount.get() > 0;
|
||||||
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>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Install = withInjectables<Dependencies, InstallProps>(
|
return (
|
||||||
observer(NonInjectedInstall),
|
<section>
|
||||||
{
|
<SubTitle
|
||||||
getProps: (di, props) => ({
|
title={`Name or file path or URL to an extension package (${supportedFormats.join(", ")})`}
|
||||||
extensionInstallationStateStore: di.inject(
|
/>
|
||||||
extensionInstallationStateStoreInjectable,
|
<div className={styles.inputs}>
|
||||||
),
|
<div>
|
||||||
|
<Input
|
||||||
|
theme="round-black"
|
||||||
|
disabled={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={anyPreInstallingOrInstalling}
|
||||||
|
waiting={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>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
...props,
|
export const Install = withInjectables<Dependencies, InstallProps>(NonInjectedInstall, {
|
||||||
}),
|
getProps: (di, props) => ({
|
||||||
},
|
...props,
|
||||||
);
|
extensionsInstallingCount: di.inject(extensionsInstallingCountInjectable),
|
||||||
|
extensionsPreinstallingCount: di.inject(extensionsPreinstallingCountInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|||||||
@ -5,9 +5,7 @@
|
|||||||
|
|
||||||
import styles from "./installed-extensions.module.scss";
|
import styles from "./installed-extensions.module.scss";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import type {
|
import type { InstalledExtension } from "../../../extensions/extension-discovery/extension-discovery";
|
||||||
InstalledExtension,
|
|
||||||
} from "../../../extensions/extension-discovery/extension-discovery";
|
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { List } from "../list/list";
|
import { List } from "../list/list";
|
||||||
import { MenuActions, MenuItem } from "../menu";
|
import { MenuActions, MenuItem } from "../menu";
|
||||||
@ -16,14 +14,12 @@ import { cssNames, toJS } from "../../utils";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { Row } from "react-table";
|
import type { Row } from "react-table";
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
|
|
||||||
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
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 { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import initialDiscoveryLoadCompletedInjectable from "../../../features/extensions/discovery/common/initial-load-completed.injectable";
|
import initialDiscoveryLoadCompletedInjectable from "../../../features/extensions/discovery/common/initial-load-completed.injectable";
|
||||||
|
import type { GetExtensionInstallationPhase } from "../../../features/extensions/installation-states/renderer/get-phase.injectable";
|
||||||
|
import getExtensionInstallationPhaseInjectable from "../../../features/extensions/installation-states/renderer/get-phase.injectable";
|
||||||
|
import extensionsUninstallingCountInjectable from "../../../features/extensions/installation-states/renderer/uninstalling-count.injectable";
|
||||||
|
|
||||||
export interface InstalledExtensionsProps {
|
export interface InstalledExtensionsProps {
|
||||||
extensions: InstalledExtension[];
|
extensions: InstalledExtension[];
|
||||||
@ -34,7 +30,8 @@ export interface InstalledExtensionsProps {
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
initialDiscoveryLoadCompleted: IComputedValue<boolean>;
|
initialDiscoveryLoadCompleted: IComputedValue<boolean>;
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
getExtensionInstallationPhase: GetExtensionInstallationPhase;
|
||||||
|
extensionsUninstallingCount: IComputedValue<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatus(extension: InstalledExtension) {
|
function getStatus(extension: InstalledExtension) {
|
||||||
@ -47,11 +44,12 @@ function getStatus(extension: InstalledExtension) {
|
|||||||
|
|
||||||
const NonInjectedInstalledExtensions = observer(({
|
const NonInjectedInstalledExtensions = observer(({
|
||||||
initialDiscoveryLoadCompleted,
|
initialDiscoveryLoadCompleted,
|
||||||
extensionInstallationStateStore,
|
getExtensionInstallationPhase,
|
||||||
extensions,
|
extensions,
|
||||||
uninstall,
|
uninstall,
|
||||||
enable,
|
enable,
|
||||||
disable,
|
disable,
|
||||||
|
extensionsUninstallingCount,
|
||||||
}: Dependencies & InstalledExtensionsProps) => {
|
}: Dependencies & InstalledExtensionsProps) => {
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@ -91,7 +89,7 @@ const NonInjectedInstalledExtensions = observer(({
|
|||||||
() => extensions.map(extension => {
|
() => extensions.map(extension => {
|
||||||
const { id, isEnabled, isCompatible, manifest } = extension;
|
const { id, isEnabled, isCompatible, manifest } = extension;
|
||||||
const { name, description, version } = manifest;
|
const { name, description, version } = manifest;
|
||||||
const isUninstalling = extensionInstallationStateStore.isExtensionUninstalling(id);
|
const isUninstalling = getExtensionInstallationPhase(id) === "uninstalling";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extension: (
|
extension: (
|
||||||
@ -145,7 +143,7 @@ const NonInjectedInstalledExtensions = observer(({
|
|||||||
</MenuActions>
|
</MenuActions>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}), [toJS(extensions), extensionInstallationStateStore.anyUninstalling],
|
}), [toJS(extensions), extensionsUninstallingCount.get() > 0],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!initialDiscoveryLoadCompleted.get()) {
|
if (!initialDiscoveryLoadCompleted.get()) {
|
||||||
@ -184,7 +182,8 @@ const NonInjectedInstalledExtensions = observer(({
|
|||||||
export const InstalledExtensions = withInjectables<Dependencies, InstalledExtensionsProps>(NonInjectedInstalledExtensions, {
|
export const InstalledExtensions = withInjectables<Dependencies, InstalledExtensionsProps>(NonInjectedInstalledExtensions, {
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
...props,
|
...props,
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
|
||||||
initialDiscoveryLoadCompleted: di.inject(initialDiscoveryLoadCompletedInjectable).value,
|
initialDiscoveryLoadCompleted: di.inject(initialDiscoveryLoadCompletedInjectable).value,
|
||||||
|
getExtensionInstallationPhase: di.inject(getExtensionInstallationPhaseInjectable),
|
||||||
|
extensionsUninstallingCount: di.inject(extensionsUninstallingCountInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
import { extensionDisplayName } from "../../../extensions/lens-extension";
|
import { extensionDisplayName } from "../../../extensions/lens-extension";
|
||||||
@ -15,18 +14,21 @@ import showErrorNotificationInjectable from "../notifications/show-error-notific
|
|||||||
import installedUserExtensionsInjectable from "../../../features/extensions/common/user-extensions.injectable";
|
import installedUserExtensionsInjectable from "../../../features/extensions/common/user-extensions.injectable";
|
||||||
import getInstalledExtensionInjectable from "../../../features/extensions/common/get-installed-extension.injectable";
|
import getInstalledExtensionInjectable from "../../../features/extensions/common/get-installed-extension.injectable";
|
||||||
import removeExtensionFilesInjectable from "../../../features/extensions/discovery/common/uninstall-extension.injectable";
|
import removeExtensionFilesInjectable from "../../../features/extensions/discovery/common/uninstall-extension.injectable";
|
||||||
|
import clearExtensionAsInstallingInjectable from "../../../features/extensions/installation-states/renderer/clear-as-installing.injectable";
|
||||||
|
import setExtensionAsUninstallingInjectable from "../../../features/extensions/installation-states/renderer/set-as-uninstalling.injectable";
|
||||||
|
|
||||||
const uninstallExtensionInjectable = getInjectable({
|
const uninstallExtensionInjectable = getInjectable({
|
||||||
id: "uninstall-extension",
|
id: "uninstall-extension",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
|
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
|
||||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
const installedUserExtensions = di.inject(installedUserExtensionsInjectable);
|
const installedUserExtensions = di.inject(installedUserExtensionsInjectable);
|
||||||
const getInstalledExtension = di.inject(getInstalledExtensionInjectable);
|
const getInstalledExtension = di.inject(getInstalledExtensionInjectable);
|
||||||
const removeExtensionFiles = di.inject(removeExtensionFilesInjectable);
|
const removeExtensionFiles = di.inject(removeExtensionFilesInjectable);
|
||||||
|
const clearExtensionAsInstalling = di.inject(clearExtensionAsInstallingInjectable);
|
||||||
|
const setExtensionAsUninstalling = di.inject(setExtensionAsUninstallingInjectable);
|
||||||
|
|
||||||
return async (extensionId: LensExtensionId): Promise<boolean> => {
|
return async (extensionId: LensExtensionId): Promise<boolean> => {
|
||||||
const ext = getInstalledExtension(extensionId);
|
const ext = getInstalledExtension(extensionId);
|
||||||
@ -42,8 +44,8 @@ const uninstallExtensionInjectable = getInjectable({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
logger.debug(`[EXTENSIONS]: trying to uninstall ${extensionId}`);
|
logger.debug(`[EXTENSIONS]: trying to uninstall ${extensionId}`);
|
||||||
extensionInstallationStateStore.setUninstalling(extensionId);
|
|
||||||
|
|
||||||
|
setExtensionAsUninstalling(extensionId);
|
||||||
await removeExtensionFiles(extensionId);
|
await removeExtensionFiles(extensionId);
|
||||||
|
|
||||||
// wait for the ExtensionLoader to actually uninstall the extension
|
// wait for the ExtensionLoader to actually uninstall the extension
|
||||||
@ -76,8 +78,7 @@ const uninstallExtensionInjectable = getInjectable({
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
// Remove uninstall state on uninstall failure
|
clearExtensionAsInstalling(extensionId);
|
||||||
extensionInstallationStateStore.clearUninstalling(extensionId);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user