mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Restyling extensions page with tailwindcss (#2796)
* Setting up tailwind and css modules env Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Using tailwind with scss files also Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Introducing react-table Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Spread extensions to smaller components Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Add table sorting Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fixing inputs line-height Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fine-tuning page view Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Align table rows Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Adding extension notice Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fine-tuning overall styling Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Adding a extensions placeholder Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Updating MaterialIcons font Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Aligning not found state Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Making extension components observable Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fixing search input cross icon Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fix drag-n-drop indication Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fixing extension name sorting Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fix linter Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fixing tests Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Ignoring ts files to tailwind purge Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Cleaning up Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Renaming Table -> ReactTable Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fixing integration tests Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Moving tailwind imports into app.scss Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Moving userExtensionList() out from extension-loader Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Transform extension list to array Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Expand install input placeholder a bit Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
1 parent
ef21eba724
commit
0899ace037
35 files changed
+1677
-360
No files matched your search
@@ -20,33 +20,47 @@
|
||||
*/
|
||||
|
||||
import "./extensions.scss";
|
||||
|
||||
import { remote, shell } from "electron";
|
||||
import fse from "fs-extra";
|
||||
import { computed, observable, reaction, when } from "mobx";
|
||||
import _ from "lodash";
|
||||
import { observable, reaction, when } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import React from "react";
|
||||
import { autobind, disposer, Disposer, downloadFile, downloadJson, ExtendableDisposer, extractTar, listTarEntries, noop, readFileFromTar } from "../../../common/utils";
|
||||
import { docsUrl } from "../../../common/vars";
|
||||
import { SemVer } from "semver";
|
||||
import URLParse from "url-parse";
|
||||
|
||||
import {
|
||||
Disposer,
|
||||
disposer,
|
||||
downloadFile,
|
||||
downloadJson,
|
||||
ExtendableDisposer,
|
||||
extractTar,
|
||||
listTarEntries,
|
||||
noop,
|
||||
readFileFromTar,
|
||||
} from "../../../common/utils";
|
||||
import { ExtensionDiscovery, InstalledExtension, manifestFilename } from "../../../extensions/extension-discovery";
|
||||
import { ExtensionLoader } from "../../../extensions/extension-loader";
|
||||
import { extensionDisplayName, LensExtensionId, LensExtensionManifest, sanitizeExtensionName } from "../../../extensions/lens-extension";
|
||||
import {
|
||||
extensionDisplayName,
|
||||
LensExtensionId,
|
||||
LensExtensionManifest,
|
||||
sanitizeExtensionName,
|
||||
} from "../../../extensions/lens-extension";
|
||||
import logger from "../../../main/logger";
|
||||
import { prevDefault } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { Icon } from "../icon";
|
||||
import { DropFileInput, Input, InputValidator, InputValidators, SearchInput } from "../input";
|
||||
import { DropFileInput, InputValidators } from "../input";
|
||||
import { PageLayout } from "../layout/page-layout";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Spinner } from "../spinner/spinner";
|
||||
import { TooltipPosition } from "../tooltip";
|
||||
import { ExtensionInstallationState, ExtensionInstallationStateStore } from "./extension-install.store";
|
||||
import URLParse from "url-parse";
|
||||
import { SemVer } from "semver";
|
||||
import _ from "lodash";
|
||||
import { Install } from "./install";
|
||||
import { InstalledExtensions } from "./installed-extensions";
|
||||
import { Notice } from "./notice";
|
||||
|
||||
function getMessageFromError(error: any): string {
|
||||
if (!error || typeof error !== "object") {
|
||||
@@ -89,6 +103,22 @@ interface InstallRequestValidated {
|
||||
tempFile: string; // temp system path to packed extension for unpacking
|
||||
}
|
||||
|
||||
function setExtensionEnabled(id: LensExtensionId, isEnabled: boolean): void {
|
||||
const extension = ExtensionLoader.getInstance().getExtension(id);
|
||||
|
||||
if (extension) {
|
||||
extension.isEnabled = isEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
function enableExtension(id: LensExtensionId) {
|
||||
setExtensionEnabled(id, true);
|
||||
}
|
||||
|
||||
function disableExtension(id: LensExtensionId) {
|
||||
setExtensionEnabled(id, false);
|
||||
}
|
||||
|
||||
async function uninstallExtension(extensionId: LensExtensionId): Promise<boolean> {
|
||||
const loader = ExtensionLoader.getInstance();
|
||||
const { manifest } = loader.getExtension(extensionId);
|
||||
@@ -465,32 +495,8 @@ async function installFromSelectFileDialog() {
|
||||
|
||||
@observer
|
||||
export class Extensions extends React.Component {
|
||||
private static installInputValidators = [
|
||||
InputValidators.isUrl,
|
||||
InputValidators.isPath,
|
||||
InputValidators.isExtensionNameInstall,
|
||||
];
|
||||
|
||||
private static installInputValidator: InputValidator = {
|
||||
message: "Invalid URL, absolute path, or extension name",
|
||||
validate: (value: string) => (
|
||||
Extensions.installInputValidators.some(({ validate }) => validate(value))
|
||||
),
|
||||
};
|
||||
|
||||
@observable search = "";
|
||||
@observable installPath = "";
|
||||
|
||||
@computed get searchedForExtensions() {
|
||||
const searchText = this.search.toLowerCase();
|
||||
|
||||
return Array.from(ExtensionLoader.getInstance().userExtensions.values())
|
||||
.filter(({ manifest: { name, description }}) => (
|
||||
name.toLowerCase().includes(searchText)
|
||||
|| description?.toLowerCase().includes(searchText)
|
||||
));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// TODO: change this after upgrading to mobx6 as that versions' reactions have this functionality
|
||||
let prevSize = ExtensionLoader.getInstance().userExtensions.size;
|
||||
@@ -509,138 +515,34 @@ export class Extensions extends React.Component {
|
||||
]);
|
||||
}
|
||||
|
||||
renderNoExtensionsHelpText() {
|
||||
if (this.search) {
|
||||
return <p>No search results found</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<p>
|
||||
There are no installed extensions.
|
||||
See list of <a href="https://github.com/lensapp/lens-extensions/blob/main/README.md" target="_blank" rel="noreferrer">available extensions</a>.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
renderNoExtensions() {
|
||||
return (
|
||||
<div className="no-extensions flex box gaps justify-center">
|
||||
<Icon material="info" />
|
||||
<div>
|
||||
{this.renderNoExtensionsHelpText()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@autobind()
|
||||
renderExtension(extension: InstalledExtension) {
|
||||
const { id, isEnabled, manifest } = extension;
|
||||
const { name, description, version } = manifest;
|
||||
const isUninstalling = ExtensionInstallationStateStore.isExtensionUninstalling(id);
|
||||
|
||||
return (
|
||||
<div key={id} className="extension flex gaps align-center">
|
||||
<div className="box grow">
|
||||
<h5>{name}</h5>
|
||||
<h6>{version}</h6>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
<div className="actions">
|
||||
{
|
||||
isEnabled
|
||||
? <Button accent disabled={isUninstalling} onClick={() => extension.isEnabled = false}>Disable</Button>
|
||||
: <Button plain active disabled={isUninstalling} onClick={() => extension.isEnabled = true}>Enable</Button>
|
||||
}
|
||||
<Button
|
||||
plain
|
||||
active
|
||||
disabled={isUninstalling}
|
||||
waiting={isUninstalling}
|
||||
onClick={() => confirmUninstallExtension(extension)}
|
||||
>
|
||||
Uninstall
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderExtensions() {
|
||||
if (!ExtensionDiscovery.getInstance().isLoaded) {
|
||||
return <div className="spinner-wrapper"><Spinner /></div>;
|
||||
}
|
||||
|
||||
const { searchedForExtensions } = this;
|
||||
|
||||
if (!searchedForExtensions.length) {
|
||||
return this.renderNoExtensions();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{...searchedForExtensions.map(this.renderExtension)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { installPath } = this;
|
||||
const extensions = Array.from(ExtensionLoader.getInstance().userExtensions.values());
|
||||
|
||||
return (
|
||||
<DropFileInput onDropFiles={installOnDrop}>
|
||||
<PageLayout showOnTop className="Extensions" contentGaps={false}>
|
||||
<h2>Lens Extensions</h2>
|
||||
<div>
|
||||
Add new features and functionality via Lens Extensions.
|
||||
Check out documentation to <a href={`${docsUrl}/latest/extensions/usage/`} target="_blank" rel="noreferrer">learn more</a> or see the list of <a href="https://github.com/lensapp/lens-extensions/blob/main/README.md" target="_blank" rel="noreferrer">available extensions</a>.
|
||||
</div>
|
||||
<section>
|
||||
<h1>Extensions</h1>
|
||||
|
||||
<div className="install-extension flex column gaps">
|
||||
<SubTitle title="Install Extension:"/>
|
||||
<div className="extension-input flex box gaps align-center">
|
||||
<Input
|
||||
className="box grow"
|
||||
theme="round-black"
|
||||
disabled={ExtensionInstallationStateStore.anyPreInstallingOrInstalling}
|
||||
placeholder={`Name or file path or URL to an extension package (${supportedFormats.join(", ")})`}
|
||||
showErrorsAsTooltip={{ preferredPositions: TooltipPosition.BOTTOM }}
|
||||
validators={installPath ? Extensions.installInputValidator : undefined}
|
||||
value={installPath}
|
||||
onChange={value => this.installPath = value}
|
||||
onSubmit={() => installFromInput(this.installPath)}
|
||||
iconLeft="link"
|
||||
iconRight={
|
||||
<Icon
|
||||
interactive
|
||||
material="folder"
|
||||
onClick={prevDefault(installFromSelectFileDialog)}
|
||||
tooltip="Browse"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
primary
|
||||
label="Install"
|
||||
disabled={ExtensionInstallationStateStore.anyPreInstallingOrInstalling || !Extensions.installInputValidator.validate(installPath)}
|
||||
waiting={ExtensionInstallationStateStore.anyPreInstallingOrInstalling}
|
||||
onClick={() => installFromInput(this.installPath)}
|
||||
/>
|
||||
<small className="hint">
|
||||
<b>Pro-Tip</b>: you can also drag-n-drop tarball-file to this area
|
||||
</small>
|
||||
</div>
|
||||
<Notice/>
|
||||
|
||||
<h2>Installed Extensions</h2>
|
||||
<div className="installed-extensions flex column gaps">
|
||||
<SearchInput
|
||||
placeholder="Search installed extensions by name or description"
|
||||
value={this.search}
|
||||
onChange={(value) => this.search = value}
|
||||
<Install
|
||||
supportedFormats={supportedFormats}
|
||||
onChange={(value) => this.installPath = value}
|
||||
installFromInput={() => installFromInput(this.installPath)}
|
||||
installFromSelectFileDialog={installFromSelectFileDialog}
|
||||
installPath={this.installPath}
|
||||
/>
|
||||
{this.renderExtensions()}
|
||||
</div>
|
||||
|
||||
{extensions.length > 0 && <hr/>}
|
||||
|
||||
<InstalledExtensions
|
||||
extensions={extensions}
|
||||
enable={enableExtension}
|
||||
disable={disableExtension}
|
||||
uninstall={confirmUninstallExtension}
|
||||
/>
|
||||
</section>
|
||||
</PageLayout>
|
||||
</DropFileInput>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user