mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix some edge cases
- Clear the loading state in more places - Add loading animation when an extension is discovered by main Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
79aec6bdb7
commit
6df8cacf36
@ -1,13 +1,14 @@
|
|||||||
import { watch } from "chokidar";
|
import { watch } from "chokidar";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import fs from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import { observable, reaction, toJS, when } from "mobx";
|
import { observable, reaction, toJS, when } from "mobx";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
|
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
|
||||||
import { getBundledExtensions } from "../common/utils/app-version";
|
import { getBundledExtensions } from "../common/utils/app-version";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
|
import { ExtensionInstallationStateStore } from "../renderer/components/+extensions/extension-install.store";
|
||||||
import { extensionInstaller, PackageJson } from "./extension-installer";
|
import { extensionInstaller, PackageJson } from "./extension-installer";
|
||||||
import { extensionLoader } from "./extension-loader";
|
import { extensionLoader } from "./extension-loader";
|
||||||
import { extensionsStore } from "./extensions-store";
|
import { extensionsStore } from "./extensions-store";
|
||||||
@ -40,7 +41,7 @@ interface ExtensionDiscoveryChannelMessage {
|
|||||||
* Returns true if the lstat is for a directory-like file (e.g. isDirectory or symbolic link)
|
* Returns true if the lstat is for a directory-like file (e.g. isDirectory or symbolic link)
|
||||||
* @param lstat the stats to compare
|
* @param lstat the stats to compare
|
||||||
*/
|
*/
|
||||||
const isDirectoryLike = (lstat: fs.Stats) => lstat.isDirectory() || lstat.isSymbolicLink();
|
const isDirectoryLike = (lstat: fse.Stats) => lstat.isDirectory() || lstat.isSymbolicLink();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discovers installed bundled and local extensions from the filesystem.
|
* Discovers installed bundled and local extensions from the filesystem.
|
||||||
@ -137,7 +138,7 @@ export class ExtensionDiscovery {
|
|||||||
depth: 1,
|
depth: 1,
|
||||||
ignoreInitial: true,
|
ignoreInitial: true,
|
||||||
// Try to wait until the file has been completely copied.
|
// Try to wait until the file has been completely copied.
|
||||||
// The OS might emit an event for added file even it's not completely written to the filesysten.
|
// The OS might emit an event for added file even it's not completely written to the filesystem.
|
||||||
awaitWriteFinish: {
|
awaitWriteFinish: {
|
||||||
// Wait 300ms until the file size doesn't change to consider the file written.
|
// Wait 300ms until the file size doesn't change to consider the file written.
|
||||||
// For a small file like package.json this should be plenty of time.
|
// For a small file like package.json this should be plenty of time.
|
||||||
@ -146,8 +147,10 @@ export class ExtensionDiscovery {
|
|||||||
})
|
})
|
||||||
// Extension add is detected by watching "<extensionDir>/package.json" add
|
// Extension add is detected by watching "<extensionDir>/package.json" add
|
||||||
.on("add", this.handleWatchFileAdd)
|
.on("add", this.handleWatchFileAdd)
|
||||||
// Extension remove is detected by watching <extensionDir>" unlink
|
// Extension remove is detected by watching "<extensionDir>" unlink
|
||||||
.on("unlinkDir", this.handleWatchUnlinkDir);
|
.on("unlinkDir", this.handleWatchUnlinkDir)
|
||||||
|
// Extension remove is detected by watching "<extensionSymLink>" unlink
|
||||||
|
.on("unlink", this.handleWatchUnlinkDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWatchFileAdd = async (manifestPath: string) => {
|
handleWatchFileAdd = async (manifestPath: string) => {
|
||||||
@ -161,6 +164,7 @@ export class ExtensionDiscovery {
|
|||||||
|
|
||||||
if (path.basename(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
|
if (path.basename(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
|
||||||
try {
|
try {
|
||||||
|
ExtensionInstallationStateStore.setInstallingFromMain(manifestPath);
|
||||||
const absPath = path.dirname(manifestPath);
|
const absPath = path.dirname(manifestPath);
|
||||||
|
|
||||||
// this.loadExtensionFromPath updates this.packagesJson
|
// this.loadExtensionFromPath updates this.packagesJson
|
||||||
@ -178,7 +182,9 @@ export class ExtensionDiscovery {
|
|||||||
this.events.emit("add", extension);
|
this.events.emit("add", extension);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
logger.error(`${logModule}: failed to add extension: ${error}`, { error });
|
||||||
|
} finally {
|
||||||
|
ExtensionInstallationStateStore.clearInstallingFromMain(manifestPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -191,8 +197,9 @@ export class ExtensionDiscovery {
|
|||||||
// Check that the removed path is directly under this.localFolderPath
|
// Check that the removed path is directly under this.localFolderPath
|
||||||
// Note that the watcher can create unlink events for subdirectories of the extension
|
// Note that the watcher can create unlink events for subdirectories of the extension
|
||||||
const extensionFolderName = path.basename(filePath);
|
const extensionFolderName = path.basename(filePath);
|
||||||
|
const expectedPath = path.relative(this.localFolderPath, filePath);
|
||||||
|
|
||||||
if (path.relative(this.localFolderPath, filePath) === extensionFolderName) {
|
if (expectedPath === extensionFolderName) {
|
||||||
const extension = Array.from(this.extensions.values()).find((extension) => extension.absolutePath === filePath);
|
const extension = Array.from(this.extensions.values()).find((extension) => extension.absolutePath === filePath);
|
||||||
|
|
||||||
if (extension) {
|
if (extension) {
|
||||||
@ -221,7 +228,7 @@ export class ExtensionDiscovery {
|
|||||||
* @param name e.g. "@mirantis/lens-extension-cc"
|
* @param name e.g. "@mirantis/lens-extension-cc"
|
||||||
*/
|
*/
|
||||||
removeSymlinkByPackageName(name: string) {
|
removeSymlinkByPackageName(name: string) {
|
||||||
return fs.remove(this.getInstalledPath(name));
|
return fse.remove(this.getInstalledPath(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -247,7 +254,7 @@ export class ExtensionDiscovery {
|
|||||||
await this.removeSymlinkByPackageName(manifest.name);
|
await this.removeSymlinkByPackageName(manifest.name);
|
||||||
|
|
||||||
// fs.remove does nothing if the path doesn't exist anymore
|
// fs.remove does nothing if the path doesn't exist anymore
|
||||||
await fs.remove(absolutePath);
|
await fse.remove(absolutePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(): Promise<Map<LensExtensionId, InstalledExtension>> {
|
async load(): Promise<Map<LensExtensionId, InstalledExtension>> {
|
||||||
@ -261,12 +268,11 @@ export class ExtensionDiscovery {
|
|||||||
logger.info(`${logModule} loading extensions from ${extensionInstaller.extensionPackagesRoot}`);
|
logger.info(`${logModule} loading extensions from ${extensionInstaller.extensionPackagesRoot}`);
|
||||||
|
|
||||||
// fs.remove won't throw if path is missing
|
// fs.remove won't throw if path is missing
|
||||||
await fs.remove(path.join(extensionInstaller.extensionPackagesRoot, "package-lock.json"));
|
await fse.remove(path.join(extensionInstaller.extensionPackagesRoot, "package-lock.json"));
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Verify write access to static/extensions, which is needed for symlinking
|
// Verify write access to static/extensions, which is needed for symlinking
|
||||||
await fs.access(this.inTreeFolderPath, fs.constants.W_OK);
|
await fse.access(this.inTreeFolderPath, fse.constants.W_OK);
|
||||||
|
|
||||||
// Set bundled folder path to static/extensions
|
// Set bundled folder path to static/extensions
|
||||||
this.bundledFolderPath = this.inTreeFolderPath;
|
this.bundledFolderPath = this.inTreeFolderPath;
|
||||||
@ -275,26 +281,26 @@ export class ExtensionDiscovery {
|
|||||||
// The error can happen if there is read-only rights to static/extensions, which would fail symlinking.
|
// The error can happen if there is read-only rights to static/extensions, which would fail symlinking.
|
||||||
|
|
||||||
// Remove e.g. /Users/<username>/Library/Application Support/LensDev/extensions
|
// Remove e.g. /Users/<username>/Library/Application Support/LensDev/extensions
|
||||||
await fs.remove(this.inTreeTargetPath);
|
await fse.remove(this.inTreeTargetPath);
|
||||||
|
|
||||||
// Create folder e.g. /Users/<username>/Library/Application Support/LensDev/extensions
|
// Create folder e.g. /Users/<username>/Library/Application Support/LensDev/extensions
|
||||||
await fs.ensureDir(this.inTreeTargetPath);
|
await fse.ensureDir(this.inTreeTargetPath);
|
||||||
|
|
||||||
// Copy static/extensions to e.g. /Users/<username>/Library/Application Support/LensDev/extensions
|
// Copy static/extensions to e.g. /Users/<username>/Library/Application Support/LensDev/extensions
|
||||||
await fs.copy(this.inTreeFolderPath, this.inTreeTargetPath);
|
await fse.copy(this.inTreeFolderPath, this.inTreeTargetPath);
|
||||||
|
|
||||||
// Set bundled folder path to e.g. /Users/<username>/Library/Application Support/LensDev/extensions
|
// Set bundled folder path to e.g. /Users/<username>/Library/Application Support/LensDev/extensions
|
||||||
this.bundledFolderPath = this.inTreeTargetPath;
|
this.bundledFolderPath = this.inTreeTargetPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.ensureDir(this.nodeModulesPath);
|
await fse.ensureDir(this.nodeModulesPath);
|
||||||
await fs.ensureDir(this.localFolderPath);
|
await fse.ensureDir(this.localFolderPath);
|
||||||
|
|
||||||
const extensions = await this.ensureExtensions();
|
try {
|
||||||
|
return await this.ensureExtensions();
|
||||||
this.isLoaded = true;
|
} finally {
|
||||||
|
this.isLoaded = true;
|
||||||
return extensions;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -317,30 +323,22 @@ export class ExtensionDiscovery {
|
|||||||
* Returns InstalledExtension from path to package.json file.
|
* Returns InstalledExtension from path to package.json file.
|
||||||
* Also updates this.packagesJson.
|
* Also updates this.packagesJson.
|
||||||
*/
|
*/
|
||||||
protected async getByManifest(manifestPath: string, { isBundled = false }: {
|
protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise<InstalledExtension | null> {
|
||||||
isBundled?: boolean;
|
|
||||||
} = {}): Promise<InstalledExtension | null> {
|
|
||||||
let manifestJson: LensExtensionManifest;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// check manifest file for existence
|
const manifest = await fse.readJson(manifestPath);
|
||||||
fs.accessSync(manifestPath, fs.constants.F_OK);
|
const installedManifestPath = this.getInstalledManifestPath(manifest.name);
|
||||||
|
|
||||||
manifestJson = __non_webpack_require__(manifestPath);
|
|
||||||
const installedManifestPath = this.getInstalledManifestPath(manifestJson.name);
|
|
||||||
|
|
||||||
const isEnabled = isBundled || extensionsStore.isEnabled(installedManifestPath);
|
const isEnabled = isBundled || extensionsStore.isEnabled(installedManifestPath);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: installedManifestPath,
|
id: installedManifestPath,
|
||||||
absolutePath: path.dirname(manifestPath),
|
absolutePath: path.dirname(manifestPath),
|
||||||
manifestPath: installedManifestPath,
|
manifestPath: installedManifestPath,
|
||||||
manifest: manifestJson,
|
manifest,
|
||||||
isBundled,
|
isBundled,
|
||||||
isEnabled
|
isEnabled
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${error}`, { manifestJson });
|
logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${error}`);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -354,7 +352,7 @@ export class ExtensionDiscovery {
|
|||||||
const userExtensions = await this.loadFromFolder(this.localFolderPath);
|
const userExtensions = await this.loadFromFolder(this.localFolderPath);
|
||||||
|
|
||||||
for (const extension of userExtensions) {
|
for (const extension of userExtensions) {
|
||||||
if (await fs.pathExists(extension.manifestPath) === false) {
|
if (await fse.pathExists(extension.manifestPath) === false) {
|
||||||
await this.installPackage(extension.absolutePath);
|
await this.installPackage(extension.absolutePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -386,7 +384,7 @@ export class ExtensionDiscovery {
|
|||||||
const extensions: InstalledExtension[] = [];
|
const extensions: InstalledExtension[] = [];
|
||||||
const folderPath = this.bundledFolderPath;
|
const folderPath = this.bundledFolderPath;
|
||||||
const bundledExtensions = getBundledExtensions();
|
const bundledExtensions = getBundledExtensions();
|
||||||
const paths = await fs.readdir(folderPath);
|
const paths = await fse.readdir(folderPath);
|
||||||
|
|
||||||
for (const fileName of paths) {
|
for (const fileName of paths) {
|
||||||
if (!bundledExtensions.includes(fileName)) {
|
if (!bundledExtensions.includes(fileName)) {
|
||||||
@ -408,7 +406,7 @@ export class ExtensionDiscovery {
|
|||||||
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
|
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
|
||||||
const bundledExtensions = getBundledExtensions();
|
const bundledExtensions = getBundledExtensions();
|
||||||
const extensions: InstalledExtension[] = [];
|
const extensions: InstalledExtension[] = [];
|
||||||
const paths = await fs.readdir(folderPath);
|
const paths = await fse.readdir(folderPath);
|
||||||
|
|
||||||
for (const fileName of paths) {
|
for (const fileName of paths) {
|
||||||
// do not allow to override bundled extensions
|
// do not allow to override bundled extensions
|
||||||
@ -418,11 +416,11 @@ export class ExtensionDiscovery {
|
|||||||
|
|
||||||
const absPath = path.resolve(folderPath, fileName);
|
const absPath = path.resolve(folderPath, fileName);
|
||||||
|
|
||||||
if (!fs.existsSync(absPath)) {
|
if (!fse.existsSync(absPath)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lstat = await fs.lstat(absPath);
|
const lstat = await fse.lstat(absPath);
|
||||||
|
|
||||||
// skip non-directories
|
// skip non-directories
|
||||||
if (!isDirectoryLike(lstat)) {
|
if (!isDirectoryLike(lstat)) {
|
||||||
|
|||||||
@ -12,8 +12,6 @@ import type { LensExtension, LensExtensionConstructor, LensExtensionId } from ".
|
|||||||
import type { LensMainExtension } from "./lens-main-extension";
|
import type { LensMainExtension } from "./lens-main-extension";
|
||||||
import type { LensRendererExtension } from "./lens-renderer-extension";
|
import type { LensRendererExtension } from "./lens-renderer-extension";
|
||||||
import * as registries from "./registries";
|
import * as registries from "./registries";
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
|
|
||||||
export function extensionPackagesRoot() {
|
export function extensionPackagesRoot() {
|
||||||
return path.join((app || remote.app).getPath("userData"));
|
return path.join((app || remote.app).getPath("userData"));
|
||||||
@ -290,28 +288,20 @@ export class ExtensionLoader {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected requireExtension(extension: InstalledExtension): LensExtensionConstructor {
|
protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null {
|
||||||
let extEntrypoint = "";
|
const entryPointName = ipcRenderer ? "renderer" : "main";
|
||||||
|
const extensionEntryPointRelativePath = extension.manifest[entryPointName];
|
||||||
|
|
||||||
|
if (!extensionEntryPointRelativePath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extensionEntryPointAbsolutePath = path.resolve(path.join(path.dirname(extension.manifestPath), extensionEntryPointRelativePath));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (ipcRenderer && extension.manifest.renderer) {
|
return __non_webpack_require__(extensionEntryPointAbsolutePath).default;
|
||||||
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.renderer));
|
} catch (error) {
|
||||||
} else if (!ipcRenderer && extension.manifest.main) {
|
logger.error(`${logModule}: can't load extension main at ${extensionEntryPointAbsolutePath}: ${error}`, { extension, error });
|
||||||
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extEntrypoint !== "") {
|
|
||||||
if (!fs.existsSync(extEntrypoint)) {
|
|
||||||
console.log(`${logModule}: entrypoint ${extEntrypoint} not found, skipping ...`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return __non_webpack_require__(extEntrypoint).default;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`${logModule}: can't load extension main at ${extEntrypoint}: ${err}`, { extension });
|
|
||||||
console.trace(err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { filesystemProvisionerStore } from "../main/extension-filesystem";
|
|||||||
import { App } from "./components/app";
|
import { App } from "./components/app";
|
||||||
import { LensApp } from "./lens-app";
|
import { LensApp } from "./lens-app";
|
||||||
import { themeStore } from "./theme.store";
|
import { themeStore } from "./theme.store";
|
||||||
|
import { ExtensionInstallationStateStore } from "./components/+extensions/extension-install.store";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this is a development buid, wait a second to attach
|
* If this is a development buid, wait a second to attach
|
||||||
@ -50,6 +51,7 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
await attachChromeDebugger();
|
await attachChromeDebugger();
|
||||||
rootElem.classList.toggle("is-mac", isMac);
|
rootElem.classList.toggle("is-mac", isMac);
|
||||||
|
|
||||||
|
ExtensionInstallationStateStore.bindIpcListeners();
|
||||||
extensionLoader.init();
|
extensionLoader.init();
|
||||||
extensionDiscovery.init();
|
extensionDiscovery.init();
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { action, computed, observable } from "mobx";
|
|||||||
import logger from "../../../main/logger";
|
import logger from "../../../main/logger";
|
||||||
import { disposer, Disposer } from "../../utils";
|
import { disposer, Disposer } from "../../utils";
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
|
import { broadcastMessage } from "../../../common/ipc";
|
||||||
|
import { ipcRenderer } from "electron";
|
||||||
|
|
||||||
export enum ExtensionInstallationState {
|
export enum ExtensionInstallationState {
|
||||||
INSTALLING = "installing",
|
INSTALLING = "installing",
|
||||||
@ -15,6 +17,19 @@ const uninstallingExtensions = observable.set<string>();
|
|||||||
const preInstallIds = observable.set<string>();
|
const preInstallIds = observable.set<string>();
|
||||||
|
|
||||||
export class ExtensionInstallationStateStore {
|
export class ExtensionInstallationStateStore {
|
||||||
|
private static InstallingFromMainChannel = "extension-installation-state-store:install";
|
||||||
|
private static ClearInstallingFromMainChannel = "extension-installation-state-store:clear-install";
|
||||||
|
|
||||||
|
static bindIpcListeners() {
|
||||||
|
ipcRenderer
|
||||||
|
.on(ExtensionInstallationStateStore.InstallingFromMainChannel, (event, extId) => {
|
||||||
|
ExtensionInstallationStateStore.setInstalling(extId);
|
||||||
|
})
|
||||||
|
.on(ExtensionInstallationStateStore.ClearInstallingFromMainChannel, (event, extId) => {
|
||||||
|
ExtensionInstallationStateStore.clearInstalling(extId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@action static reset() {
|
@action static reset() {
|
||||||
logger.warn(`${Prefix}: resetting, may throw errors`);
|
logger.warn(`${Prefix}: resetting, may throw errors`);
|
||||||
installingExtensions.clear();
|
installingExtensions.clear();
|
||||||
@ -39,6 +54,22 @@ export class ExtensionInstallationStateStore {
|
|||||||
installingExtensions.add(extId);
|
installingExtensions.add(extId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts that an extension is being installed by the main process
|
||||||
|
* @param extId the ID of the extension
|
||||||
|
*/
|
||||||
|
static setInstallingFromMain(extId: string): void {
|
||||||
|
broadcastMessage(ExtensionInstallationStateStore.InstallingFromMainChannel, extId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts that an extension is no longer being installed by the main process
|
||||||
|
* @param extId the ID of the extension
|
||||||
|
*/
|
||||||
|
static clearInstallingFromMain(extId: string): void {
|
||||||
|
broadcastMessage(ExtensionInstallationStateStore.ClearInstallingFromMainChannel, extId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the start of a pre-install phase of an extension installation. The
|
* 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
|
* part of the installation before the tarball has been unpacked and the ID
|
||||||
@ -165,7 +196,7 @@ export class ExtensionInstallationStateStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current number of extensions preinstallig
|
* The current number of extensions preinstalling
|
||||||
*/
|
*/
|
||||||
@computed static get preinstalling(): number {
|
@computed static get preinstalling(): number {
|
||||||
return preInstallIds.size;
|
return preInstallIds.size;
|
||||||
|
|||||||
@ -59,7 +59,7 @@ async function uninstallExtension(extensionId: LensExtensionId, manifest: LensEx
|
|||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notifications.error(
|
Notifications.error(
|
||||||
<p>Uninstalling extension <b>{displayName}</b> has failed: <em>{error?.message ?? ""}</em></p>
|
<p>Uninstalling extension <b>{displayName}</b> has failed: <em>{error?.message ?? error?.toString() ?? ""}</em></p>
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -174,7 +174,7 @@ async function createTempFilesAndValidate(request: InstallRequestPreloaded, { sh
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unpackExtension(request: InstallRequestValidated, disposeDownloading: Disposer) {
|
async function unpackExtension(request: InstallRequestValidated, disposeDownloading?: Disposer) {
|
||||||
const { id, fileName, tempFile, manifest: { name, version } } = request;
|
const { id, fileName, tempFile, manifest: { name, version } } = request;
|
||||||
|
|
||||||
ExtensionInstallationStateStore.setInstalling(id);
|
ExtensionInstallationStateStore.setInstalling(id);
|
||||||
@ -238,13 +238,13 @@ async function requestInstall(request: InstallRequest, d?: Disposer): Promise<vo
|
|||||||
const loadedRequest = await preloadExtension(request);
|
const loadedRequest = await preloadExtension(request);
|
||||||
|
|
||||||
if (!loadedRequest) {
|
if (!loadedRequest) {
|
||||||
return;
|
return dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedRequest = await createTempFilesAndValidate(loadedRequest);
|
const validatedRequest = await createTempFilesAndValidate(loadedRequest);
|
||||||
|
|
||||||
if (!validatedRequest) {
|
if (!validatedRequest) {
|
||||||
return;
|
return dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, version, description } = validatedRequest.manifest;
|
const { name, version, description } = validatedRequest.manifest;
|
||||||
@ -282,10 +282,18 @@ async function requestInstall(request: InstallRequest, d?: Disposer): Promise<vo
|
|||||||
<Button autoFocus label="Install" onClick={async () => {
|
<Button autoFocus label="Install" onClick={async () => {
|
||||||
removeNotification();
|
removeNotification();
|
||||||
|
|
||||||
await uninstallExtension(validatedRequest.id, validatedRequest.manifest)
|
if (await uninstallExtension(validatedRequest.id, validatedRequest.manifest)) {
|
||||||
&& await unpackExtension(validatedRequest, dispose);
|
await unpackExtension(validatedRequest, dispose);
|
||||||
|
} else {
|
||||||
|
disposer();
|
||||||
|
}
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>,
|
||||||
|
{
|
||||||
|
onClose() {
|
||||||
|
disposer();
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -456,10 +464,6 @@ export class Extensions extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{...searchedForExtensions.map(this.renderExtension)}
|
{...searchedForExtensions.map(this.renderExtension)}
|
||||||
{
|
|
||||||
ExtensionInstallationStateStore.anyPreInstallingOrInstalling
|
|
||||||
&& <div className="spinner-wrapper"><Spinner /></div>
|
|
||||||
}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -505,6 +509,7 @@ export class Extensions extends React.Component {
|
|||||||
primary
|
primary
|
||||||
label="Install"
|
label="Install"
|
||||||
disabled={ExtensionInstallationStateStore.anyPreInstallingOrInstalling || !Extensions.installPathValidator.validate(installPath)}
|
disabled={ExtensionInstallationStateStore.anyPreInstallingOrInstalling || !Extensions.installPathValidator.validate(installPath)}
|
||||||
|
waiting={ExtensionInstallationStateStore.anyPreInstallingOrInstalling}
|
||||||
onClick={() => installFromUrlOrPath(this.installPath)}
|
onClick={() => installFromUrlOrPath(this.installPath)}
|
||||||
/>
|
/>
|
||||||
<small className="hint">
|
<small className="hint">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user