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:
parent
ef21eba724
commit
0899ace037
@ -57,17 +57,17 @@ describe("Lens integration tests", () => {
|
|||||||
const appName: string = process.platform === "darwin" ? "OpenLens" : "File";
|
const appName: string = process.platform === "darwin" ? "OpenLens" : "File";
|
||||||
|
|
||||||
await app.electron.ipcRenderer.send("test-menu-item-click", appName, "Preferences");
|
await app.electron.ipcRenderer.send("test-menu-item-click", appName, "Preferences");
|
||||||
await app.client.waitUntilTextExists("[data-testid=application-header]", "APPLICATION");
|
await app.client.waitUntilTextExists("[data-testid=application-header]", "Application");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows all tabs and their contents", async () => {
|
it("shows all tabs and their contents", async () => {
|
||||||
await app.client.click("[data-testid=application-tab]");
|
await app.client.click("[data-testid=application-tab]");
|
||||||
await app.client.click("[data-testid=proxy-tab]");
|
await app.client.click("[data-testid=proxy-tab]");
|
||||||
await app.client.waitUntilTextExists("[data-testid=proxy-header]", "PROXY");
|
await app.client.waitUntilTextExists("[data-testid=proxy-header]", "Proxy");
|
||||||
await app.client.click("[data-testid=kube-tab]");
|
await app.client.click("[data-testid=kube-tab]");
|
||||||
await app.client.waitUntilTextExists("[data-testid=kubernetes-header]", "KUBERNETES");
|
await app.client.waitUntilTextExists("[data-testid=kubernetes-header]", "Kubernetes");
|
||||||
await app.client.click("[data-testid=telemetry-tab]");
|
await app.client.click("[data-testid=telemetry-tab]");
|
||||||
await app.client.waitUntilTextExists("[data-testid=telemetry-header]", "TELEMETRY");
|
await app.client.waitUntilTextExists("[data-testid=telemetry-header]", "Telemetry");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ensures helm repos", async () => {
|
it("ensures helm repos", async () => {
|
||||||
|
|||||||
@ -241,7 +241,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@emeraldpay/hashicon-react": "^0.4.0",
|
"@emeraldpay/hashicon-react": "^0.4.0",
|
||||||
"@material-ui/core": "^4.10.1",
|
"@material-ui/core": "^4.11.4",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
||||||
@ -280,6 +280,7 @@
|
|||||||
"@types/react-dom": "^17.0.0",
|
"@types/react-dom": "^17.0.0",
|
||||||
"@types/react-router-dom": "^5.1.6",
|
"@types/react-router-dom": "^5.1.6",
|
||||||
"@types/react-select": "^3.0.13",
|
"@types/react-select": "^3.0.13",
|
||||||
|
"@types/react-table": "^7.7.0",
|
||||||
"@types/react-window": "^1.8.2",
|
"@types/react-window": "^1.8.2",
|
||||||
"@types/readable-stream": "^2.3.9",
|
"@types/readable-stream": "^2.3.9",
|
||||||
"@types/request": "^2.48.5",
|
"@types/request": "^2.48.5",
|
||||||
@ -336,6 +337,8 @@
|
|||||||
"nodemon": "^2.0.4",
|
"nodemon": "^2.0.4",
|
||||||
"open": "^7.3.1",
|
"open": "^7.3.1",
|
||||||
"patch-package": "^6.2.2",
|
"patch-package": "^6.2.2",
|
||||||
|
"postcss": "^8.2.14",
|
||||||
|
"postcss-loader": "~3.0.0",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.2.0",
|
"prettier": "^2.2.0",
|
||||||
"progress-bar-webpack-plugin": "^2.1.0",
|
"progress-bar-webpack-plugin": "^2.1.0",
|
||||||
@ -346,11 +349,13 @@
|
|||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-select": "^3.1.0",
|
"react-select": "^3.1.0",
|
||||||
"react-select-event": "^5.1.0",
|
"react-select-event": "^5.1.0",
|
||||||
|
"react-table": "^7.7.0",
|
||||||
"react-window": "^1.8.5",
|
"react-window": "^1.8.5",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"sharp": "^0.26.1",
|
"sharp": "^0.26.1",
|
||||||
"spectron": "11.0.0",
|
"spectron": "11.0.0",
|
||||||
"style-loader": "^1.2.1",
|
"style-loader": "^1.2.1",
|
||||||
|
"tailwindcss": "^2.1.2",
|
||||||
"ts-jest": "26.3.0",
|
"ts-jest": "26.3.0",
|
||||||
"ts-loader": "^7.0.5",
|
"ts-loader": "^7.0.5",
|
||||||
"ts-node": "^8.10.2",
|
"ts-node": "^8.10.2",
|
||||||
@ -359,6 +364,7 @@
|
|||||||
"typedoc-plugin-markdown": "^2.4.0",
|
"typedoc-plugin-markdown": "^2.4.0",
|
||||||
"typeface-roboto": "^0.0.75",
|
"typeface-roboto": "^0.0.75",
|
||||||
"typescript": "4.0.2",
|
"typescript": "4.0.2",
|
||||||
|
"typescript-plugin-css-modules": "^3.2.0",
|
||||||
"url-loader": "^4.1.0",
|
"url-loader": "^4.1.0",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^4.44.2",
|
||||||
"webpack-cli": "^3.3.11",
|
"webpack-cli": "^3.3.11",
|
||||||
|
|||||||
28
postcss.config.js
Normal file
28
postcss.config.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const tailwindcss = require("tailwindcss");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
tailwindcss("./tailwind.config.js")
|
||||||
|
],
|
||||||
|
};
|
||||||
@ -61,7 +61,7 @@ export class ExtensionLoader extends Singleton {
|
|||||||
whenLoaded = when(() => this.isLoaded);
|
whenLoaded = when(() => this.isLoaded);
|
||||||
|
|
||||||
@computed get userExtensions(): Map<LensExtensionId, InstalledExtension> {
|
@computed get userExtensions(): Map<LensExtensionId, InstalledExtension> {
|
||||||
const extensions = this.extensions.toJS();
|
const extensions = this.toJSON();
|
||||||
|
|
||||||
extensions.forEach((ext, extId) => {
|
extensions.forEach((ext, extId) => {
|
||||||
if (ext.isBundled) {
|
if (ext.isBundled) {
|
||||||
|
|||||||
@ -88,9 +88,13 @@ describe("Extensions", () => {
|
|||||||
ExtensionDiscovery.getInstance().isLoaded = true;
|
ExtensionDiscovery.getInstance().isLoaded = true;
|
||||||
|
|
||||||
const res = render(<><Extensions /><ConfirmDialog /></>);
|
const res = render(<><Extensions /><ConfirmDialog /></>);
|
||||||
|
const table = res.getByTestId("extensions-table");
|
||||||
|
const menuTrigger = table.querySelector("div[role=row]:first-of-type .actions .Icon");
|
||||||
|
|
||||||
expect(res.getByText("Disable").closest("button")).not.toBeDisabled();
|
fireEvent.click(menuTrigger);
|
||||||
expect(res.getByText("Uninstall").closest("button")).not.toBeDisabled();
|
|
||||||
|
expect(res.getByText("Disable")).toHaveAttribute("aria-disabled", "false");
|
||||||
|
expect(res.getByText("Uninstall")).toHaveAttribute("aria-disabled", "false");
|
||||||
|
|
||||||
fireEvent.click(res.getByText("Uninstall"));
|
fireEvent.click(res.getByText("Uninstall"));
|
||||||
|
|
||||||
@ -99,8 +103,9 @@ describe("Extensions", () => {
|
|||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(ExtensionDiscovery.getInstance().uninstallExtension).toHaveBeenCalled();
|
expect(ExtensionDiscovery.getInstance().uninstallExtension).toHaveBeenCalled();
|
||||||
expect(res.getByText("Disable").closest("button")).toBeDisabled();
|
fireEvent.click(menuTrigger);
|
||||||
expect(res.getByText("Uninstall").closest("button")).toBeDisabled();
|
expect(res.getByText("Disable")).toHaveAttribute("aria-disabled", "true");
|
||||||
|
expect(res.getByText("Uninstall")).toHaveAttribute("aria-disabled", "true");
|
||||||
}, {
|
}, {
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
});
|
});
|
||||||
@ -111,7 +116,7 @@ describe("Extensions", () => {
|
|||||||
|
|
||||||
(fse.unlink as jest.MockedFunction<typeof fse.unlink>).mockReturnValue(Promise.resolve() as any);
|
(fse.unlink as jest.MockedFunction<typeof fse.unlink>).mockReturnValue(Promise.resolve() as any);
|
||||||
|
|
||||||
fireEvent.change(res.getByPlaceholderText("Path or URL to an extension package", {
|
fireEvent.change(res.getByPlaceholderText("File path or URL", {
|
||||||
exact: false
|
exact: false
|
||||||
}), {
|
}), {
|
||||||
target: {
|
target: {
|
||||||
@ -134,8 +139,6 @@ describe("Extensions", () => {
|
|||||||
ExtensionDiscovery.getInstance().isLoaded = true;
|
ExtensionDiscovery.getInstance().isLoaded = true;
|
||||||
const { container } = render(<Extensions />);
|
const { container } = render(<Extensions />);
|
||||||
|
|
||||||
waitFor(() =>
|
expect(container.querySelector(".Spinner")).not.toBeInTheDocument();
|
||||||
expect(container.querySelector(".Spinner")).not.toBeInTheDocument()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -210,31 +210,45 @@ export class ExtensionInstallationStateStore {
|
|||||||
return ExtensionInstallationStateStore.InstallingExtensions.size;
|
return ExtensionInstallationStateStore.InstallingExtensions.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current number of extensions uninstalling
|
||||||
|
*/
|
||||||
|
static get uninstalling(): number {
|
||||||
|
return ExtensionInstallationStateStore.UninstallingExtensions.size;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is at least one extension currently installing
|
* If there is at least one extension currently installing
|
||||||
*/
|
*/
|
||||||
@computed static get anyInstalling(): boolean {
|
static get anyInstalling(): boolean {
|
||||||
return ExtensionInstallationStateStore.installing > 0;
|
return ExtensionInstallationStateStore.installing > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is at least one extension currently ininstalling
|
||||||
|
*/
|
||||||
|
static get anyUninstalling(): boolean {
|
||||||
|
return ExtensionInstallationStateStore.uninstalling > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current number of extensions preinstalling
|
* The current number of extensions preinstalling
|
||||||
*/
|
*/
|
||||||
@computed static get preinstalling(): number {
|
static get preinstalling(): number {
|
||||||
return ExtensionInstallationStateStore.PreInstallIds.size;
|
return ExtensionInstallationStateStore.PreInstallIds.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is at least one extension currently downloading
|
* If there is at least one extension currently downloading
|
||||||
*/
|
*/
|
||||||
@computed static get anyPreinstalling(): boolean {
|
static get anyPreinstalling(): boolean {
|
||||||
return ExtensionInstallationStateStore.preinstalling > 0;
|
return ExtensionInstallationStateStore.preinstalling > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is at least one installing or preinstalling step taking place
|
* If there is at least one installing or preinstalling step taking place
|
||||||
*/
|
*/
|
||||||
@computed static get anyPreInstallingOrInstalling(): boolean {
|
static get anyPreInstallingOrInstalling(): boolean {
|
||||||
return ExtensionInstallationStateStore.anyInstalling || ExtensionInstallationStateStore.anyPreinstalling;
|
return ExtensionInstallationStateStore.anyInstalling || ExtensionInstallationStateStore.anyPreinstalling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,52 +21,16 @@
|
|||||||
|
|
||||||
.PageLayout.Extensions {
|
.PageLayout.Extensions {
|
||||||
$spacing: $padding * 2;
|
$spacing: $padding * 2;
|
||||||
--width: 50%;
|
width: 100%;
|
||||||
|
|
||||||
h2 {
|
.contentRegion {
|
||||||
margin-bottom: $padding;
|
.content {
|
||||||
}
|
max-width: 740px;
|
||||||
|
|
||||||
.no-extensions {
|
> section {
|
||||||
--flex-gap: #{$padding};
|
height: 100%;
|
||||||
padding: $padding;
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-size: $font-size-small;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.install-extension {
|
|
||||||
margin: $spacing * 2 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.installed-extensions {
|
|
||||||
--flex-gap: #{$spacing};
|
|
||||||
|
|
||||||
.extension {
|
|
||||||
padding: $padding $spacing;
|
|
||||||
background: $layoutBackground;
|
|
||||||
border-radius: $radius;
|
|
||||||
|
|
||||||
.actions > button:not(:last-child) {
|
|
||||||
margin-right: $spacing / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5, h6 {
|
|
||||||
color: var(--textColorSecondary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .spinner-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.SearchInput {
|
|
||||||
--spacing: 10px;
|
|
||||||
|
|
||||||
max-width: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,33 +20,47 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "./extensions.scss";
|
import "./extensions.scss";
|
||||||
|
|
||||||
import { remote, shell } from "electron";
|
import { remote, shell } from "electron";
|
||||||
import fse from "fs-extra";
|
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 { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { autobind, disposer, Disposer, downloadFile, downloadJson, ExtendableDisposer, extractTar, listTarEntries, noop, readFileFromTar } from "../../../common/utils";
|
import { SemVer } from "semver";
|
||||||
import { docsUrl } from "../../../common/vars";
|
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 { ExtensionDiscovery, InstalledExtension, manifestFilename } from "../../../extensions/extension-discovery";
|
||||||
import { ExtensionLoader } from "../../../extensions/extension-loader";
|
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 logger from "../../../main/logger";
|
||||||
import { prevDefault } from "../../utils";
|
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
import { Icon } from "../icon";
|
import { DropFileInput, InputValidators } from "../input";
|
||||||
import { DropFileInput, Input, InputValidator, InputValidators, SearchInput } from "../input";
|
|
||||||
import { PageLayout } from "../layout/page-layout";
|
import { PageLayout } from "../layout/page-layout";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { Spinner } from "../spinner/spinner";
|
|
||||||
import { TooltipPosition } from "../tooltip";
|
|
||||||
import { ExtensionInstallationState, ExtensionInstallationStateStore } from "./extension-install.store";
|
import { ExtensionInstallationState, ExtensionInstallationStateStore } from "./extension-install.store";
|
||||||
import URLParse from "url-parse";
|
import { Install } from "./install";
|
||||||
import { SemVer } from "semver";
|
import { InstalledExtensions } from "./installed-extensions";
|
||||||
import _ from "lodash";
|
import { Notice } from "./notice";
|
||||||
|
|
||||||
function getMessageFromError(error: any): string {
|
function getMessageFromError(error: any): string {
|
||||||
if (!error || typeof error !== "object") {
|
if (!error || typeof error !== "object") {
|
||||||
@ -89,6 +103,22 @@ interface InstallRequestValidated {
|
|||||||
tempFile: string; // temp system path to packed extension for unpacking
|
tempFile: string; // temp system path to packed extension for unpacking
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setExtensionEnabled(id: LensExtensionId, isEnabled: boolean): void {
|
||||||
|
const extension = ExtensionLoader.getInstance().getExtension(id);
|
||||||
|
|
||||||
|
if (extension) {
|
||||||
|
extension.isEnabled = isEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableExtension(id: LensExtensionId) {
|
||||||
|
setExtensionEnabled(id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableExtension(id: LensExtensionId) {
|
||||||
|
setExtensionEnabled(id, false);
|
||||||
|
}
|
||||||
|
|
||||||
async function uninstallExtension(extensionId: LensExtensionId): Promise<boolean> {
|
async function uninstallExtension(extensionId: LensExtensionId): Promise<boolean> {
|
||||||
const loader = ExtensionLoader.getInstance();
|
const loader = ExtensionLoader.getInstance();
|
||||||
const { manifest } = loader.getExtension(extensionId);
|
const { manifest } = loader.getExtension(extensionId);
|
||||||
@ -465,32 +495,8 @@ async function installFromSelectFileDialog() {
|
|||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class Extensions extends React.Component {
|
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 = "";
|
@observable installPath = "";
|
||||||
|
|
||||||
@computed get searchedForExtensions() {
|
|
||||||
const searchText = this.search.toLowerCase();
|
|
||||||
|
|
||||||
return Array.from(ExtensionLoader.getInstance().userExtensions.values())
|
|
||||||
.filter(({ manifest: { name, description }}) => (
|
|
||||||
name.toLowerCase().includes(searchText)
|
|
||||||
|| description?.toLowerCase().includes(searchText)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
// TODO: change this after upgrading to mobx6 as that versions' reactions have this functionality
|
// TODO: change this after upgrading to mobx6 as that versions' reactions have this functionality
|
||||||
let prevSize = ExtensionLoader.getInstance().userExtensions.size;
|
let prevSize = ExtensionLoader.getInstance().userExtensions.size;
|
||||||
@ -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() {
|
render() {
|
||||||
const { installPath } = this;
|
const extensions = Array.from(ExtensionLoader.getInstance().userExtensions.values());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropFileInput onDropFiles={installOnDrop}>
|
<DropFileInput onDropFiles={installOnDrop}>
|
||||||
<PageLayout showOnTop className="Extensions" contentGaps={false}>
|
<PageLayout showOnTop className="Extensions" contentGaps={false}>
|
||||||
<h2>Lens Extensions</h2>
|
<section>
|
||||||
<div>
|
<h1>Extensions</h1>
|
||||||
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>
|
|
||||||
|
|
||||||
<div className="install-extension flex column gaps">
|
<Notice/>
|
||||||
<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>
|
|
||||||
|
|
||||||
<h2>Installed Extensions</h2>
|
<Install
|
||||||
<div className="installed-extensions flex column gaps">
|
supportedFormats={supportedFormats}
|
||||||
<SearchInput
|
onChange={(value) => this.installPath = value}
|
||||||
placeholder="Search installed extensions by name or description"
|
installFromInput={() => installFromInput(this.installPath)}
|
||||||
value={this.search}
|
installFromSelectFileDialog={installFromSelectFileDialog}
|
||||||
onChange={(value) => this.search = value}
|
installPath={this.installPath}
|
||||||
/>
|
/>
|
||||||
{this.renderExtensions()}
|
|
||||||
</div>
|
{extensions.length > 0 && <hr/>}
|
||||||
|
|
||||||
|
<InstalledExtensions
|
||||||
|
extensions={extensions}
|
||||||
|
enable={enableExtension}
|
||||||
|
disable={disableExtension}
|
||||||
|
uninstall={confirmUninstallExtension}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
</DropFileInput>
|
</DropFileInput>
|
||||||
);
|
);
|
||||||
|
|||||||
7
src/renderer/components/+extensions/install.module.css
Normal file
7
src/renderer/components/+extensions/install.module.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.icon {
|
||||||
|
@apply h-5 w-5 mr-3 cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon:hover {
|
||||||
|
color: var(--textColorAccent);
|
||||||
|
}
|
||||||
100
src/renderer/components/+extensions/install.tsx
Normal file
100
src/renderer/components/+extensions/install.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styles from "./install.module.css";
|
||||||
|
import React from "react";
|
||||||
|
import { prevDefault } from "../../utils";
|
||||||
|
import { Button } from "../button";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import { Input, InputValidator, InputValidators } from "../input";
|
||||||
|
import { SubTitle } from "../layout/sub-title";
|
||||||
|
import { TooltipPosition } from "../tooltip";
|
||||||
|
import { ExtensionInstallationStateStore } from "./extension-install.store";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
installPath: string;
|
||||||
|
supportedFormats: string[];
|
||||||
|
onChange: (path: string) => void;
|
||||||
|
installFromInput: () => void;
|
||||||
|
installFromSelectFileDialog: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const installInputValidators = [
|
||||||
|
InputValidators.isUrl,
|
||||||
|
InputValidators.isPath,
|
||||||
|
InputValidators.isExtensionNameInstall,
|
||||||
|
];
|
||||||
|
|
||||||
|
const installInputValidator: InputValidator = {
|
||||||
|
message: "Invalid URL, absolute path, or extension name",
|
||||||
|
validate: (value: string) => (
|
||||||
|
installInputValidators.some(({ validate }) => validate(value))
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Install = observer((props: Props) => {
|
||||||
|
const { installPath, supportedFormats, onChange, installFromInput, installFromSelectFileDialog } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="mt-2">
|
||||||
|
<SubTitle title={`Name or file path or URL to an extension package (${supportedFormats.join(", ")})`}/>
|
||||||
|
<div className="flex">
|
||||||
|
<div className="flex-1">
|
||||||
|
<Input
|
||||||
|
className="box grow mr-6"
|
||||||
|
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
|
||||||
|
interactive={false}
|
||||||
|
className={styles.icon}
|
||||||
|
material="folder_open"
|
||||||
|
onClick={prevDefault(installFromSelectFileDialog)}
|
||||||
|
tooltip="Browse"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-initial">
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
label="Install"
|
||||||
|
className="w-80 h-full"
|
||||||
|
disabled={ExtensionInstallationStateStore.anyPreInstallingOrInstalling}
|
||||||
|
waiting={ExtensionInstallationStateStore.anyPreInstallingOrInstalling}
|
||||||
|
onClick={installFromInput}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small className="mt-3">
|
||||||
|
<b>Pro-Tip</b>: you can drag-n-drop tarball-file to this area
|
||||||
|
</small>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
.extensionName {
|
||||||
|
@apply font-bold;
|
||||||
|
color: var(--textColorAccent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.extensionDescription {
|
||||||
|
@apply mt-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enabled {
|
||||||
|
color: var(--colorOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 0!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noItemsIcon {
|
||||||
|
@apply opacity-10;
|
||||||
|
--size: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frozenRow {
|
||||||
|
@apply opacity-30 pointer-events-none;
|
||||||
|
}
|
||||||
169
src/renderer/components/+extensions/installed-extensions.tsx
Normal file
169
src/renderer/components/+extensions/installed-extensions.tsx
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styles from "./installed-extensions.module.css";
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import { ExtensionDiscovery, InstalledExtension } from "../../../extensions/extension-discovery";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import { List } from "../list/list";
|
||||||
|
import { MenuActions, MenuItem } from "../menu";
|
||||||
|
import { Spinner } from "../spinner";
|
||||||
|
import { ExtensionInstallationStateStore } from "./extension-install.store";
|
||||||
|
import { cssNames } from "../../utils";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import type { Row } from "react-table";
|
||||||
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
extensions: InstalledExtension[];
|
||||||
|
enable: (id: LensExtensionId) => void;
|
||||||
|
disable: (id: LensExtensionId) => void;
|
||||||
|
uninstall: (extension: InstalledExtension) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatus(isEnabled: boolean) {
|
||||||
|
return isEnabled ? "Enabled" : "Disabled";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InstalledExtensions = observer(({ extensions, uninstall, enable, disable }: Props) => {
|
||||||
|
const filters = [
|
||||||
|
(extension: InstalledExtension) => extension.manifest.name,
|
||||||
|
(extension: InstalledExtension) => getStatus(extension.isEnabled),
|
||||||
|
(extension: InstalledExtension) => extension.manifest.version,
|
||||||
|
];
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: "Name",
|
||||||
|
accessor: "extension",
|
||||||
|
width: 200,
|
||||||
|
sortType: (rowA: Row, rowB: Row) => { // Custom sorting for extension name
|
||||||
|
const nameA = extensions[rowA.index].manifest.name;
|
||||||
|
const nameB = extensions[rowB.index].manifest.name;
|
||||||
|
|
||||||
|
if (nameA > nameB) return -1;
|
||||||
|
if (nameB > nameA) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: "Version",
|
||||||
|
accessor: "version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: "Status",
|
||||||
|
accessor: "status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: "",
|
||||||
|
accessor: "actions",
|
||||||
|
disableSortBy: true,
|
||||||
|
width: 20,
|
||||||
|
className: "actions"
|
||||||
|
}
|
||||||
|
], []
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = useMemo(
|
||||||
|
() => {
|
||||||
|
return extensions.map(extension => {
|
||||||
|
const { id, isEnabled, manifest } = extension;
|
||||||
|
const { name, description, version } = manifest;
|
||||||
|
const isUninstalling = ExtensionInstallationStateStore.isExtensionUninstalling(id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
extension: (
|
||||||
|
<div className={"flex items-start"}>
|
||||||
|
<div>
|
||||||
|
<div className={styles.extensionName}>{name}</div>
|
||||||
|
<div className={styles.extensionDescription}>{description}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
version,
|
||||||
|
status: (
|
||||||
|
<div className={cssNames({[styles.enabled]: getStatus(isEnabled) == "Enabled"})}>
|
||||||
|
{getStatus(isEnabled)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
actions: (
|
||||||
|
<MenuActions usePortal toolbar={false}>
|
||||||
|
{isEnabled ? (
|
||||||
|
<MenuItem
|
||||||
|
disabled={isUninstalling}
|
||||||
|
onClick={() => disable(id)}
|
||||||
|
>
|
||||||
|
<Icon material="unpublished"/>
|
||||||
|
<span className="title" aria-disabled={isUninstalling}>Disable</span>
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
<MenuItem
|
||||||
|
disabled={isUninstalling}
|
||||||
|
onClick={() => enable(id)}
|
||||||
|
>
|
||||||
|
<Icon material="check_circle"/>
|
||||||
|
<span className="title" aria-disabled={isUninstalling}>Enable</span>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
disabled={isUninstalling}
|
||||||
|
onClick={() => uninstall(extension)}
|
||||||
|
>
|
||||||
|
<Icon material="delete"/>
|
||||||
|
<span className="title" aria-disabled={isUninstalling}>Uninstall</span>
|
||||||
|
</MenuItem>
|
||||||
|
</MenuActions>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [extensions, ExtensionInstallationStateStore.anyUninstalling]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ExtensionDiscovery.getInstance().isLoaded) {
|
||||||
|
return <div><Spinner center /></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extensions.length == 0) {
|
||||||
|
return (
|
||||||
|
<div className="flex column h-full items-center justify-center">
|
||||||
|
<Icon material="extension" className={styles.noItemsIcon}/>
|
||||||
|
<h3 className="font-medium text-3xl mt-5 mb-2">
|
||||||
|
There are no extensions installed.
|
||||||
|
</h3>
|
||||||
|
<p>Please use the form above to install or drag tarbar-file here.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section data-testid="extensions-table">
|
||||||
|
<List
|
||||||
|
title={<h2 className={styles.title}>Installed extensions</h2>}
|
||||||
|
columns={columns}
|
||||||
|
data={data}
|
||||||
|
items={extensions}
|
||||||
|
filters={filters}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
});
|
||||||
27
src/renderer/components/+extensions/notice.module.css
Normal file
27
src/renderer/components/+extensions/notice.module.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
@apply p-8 flex flex-col gap-2 mb-14 mt-3 rounded-lg;
|
||||||
|
background-color: var(--inputControlBackground);
|
||||||
|
border: 1px solid var(--boxShadow);
|
||||||
|
color: var(--textColorTertiary);
|
||||||
|
}
|
||||||
36
src/renderer/components/+extensions/notice.tsx
Normal file
36
src/renderer/components/+extensions/notice.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styles from "./notice.module.css";
|
||||||
|
import React from "react";
|
||||||
|
import { docsUrl } from "../../../common/vars";
|
||||||
|
|
||||||
|
export function Notice() {
|
||||||
|
return (
|
||||||
|
<div className={styles.notice}>
|
||||||
|
<p>
|
||||||
|
Add new features via Lens Extensions.{" "}
|
||||||
|
Check out <a href={`${docsUrl}/extensions/`} target="_blank" rel="noreferrer">docs</a>{" "}
|
||||||
|
and list of <a href="https://github.com/lensapp/lens-extensions/blob/main/README.md" target="_blank" rel="noreferrer">available extensions</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -19,6 +19,9 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import "tailwindcss/base";
|
||||||
|
@import "tailwindcss/components";
|
||||||
|
@import "tailwindcss/utilities";
|
||||||
@import "~flex.box";
|
@import "~flex.box";
|
||||||
@import "fonts";
|
@import "fonts";
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
|
|||||||
@ -112,6 +112,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus:not(:active) {
|
&:focus:not(:active) {
|
||||||
transition: box-shadow 350ms;
|
transition: box-shadow 350ms;
|
||||||
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .5);
|
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .5);
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import { cssNames } from "../../utils";
|
|||||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
index: number;
|
index: number;
|
||||||
innerRef?: React.LegacyRef<HTMLDivElement>;
|
innerRef?: React.Ref<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HotbarCell({ innerRef, children, className, ...rest }: Props) {
|
export function HotbarCell({ innerRef, children, className, ...rest }: Props) {
|
||||||
|
|||||||
@ -21,10 +21,7 @@
|
|||||||
|
|
||||||
.DropFileInput {
|
.DropFileInput {
|
||||||
&.droppable {
|
&.droppable {
|
||||||
box-shadow: 0 0 0 5px $primary; // fixme: might not work sometimes
|
box-shadow: inset 0 0 0 6px $primary;
|
||||||
|
transition: all 0.3s;
|
||||||
> * {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,16 +39,22 @@ export interface DropFileMeta<T extends HTMLElement = any> {
|
|||||||
@observer
|
@observer
|
||||||
export class DropFileInput<T extends HTMLElement = any> extends React.Component<DropFileInputProps> {
|
export class DropFileInput<T extends HTMLElement = any> extends React.Component<DropFileInputProps> {
|
||||||
@observable dropAreaActive = false;
|
@observable dropAreaActive = false;
|
||||||
|
dragCounter = 0; // Counter preventing firing onDragLeave() too early (https://stackoverflow.com/questions/7110353/html5-dragleave-fired-when-hovering-a-child-element)
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
onDragEnter() {
|
onDragEnter() {
|
||||||
|
this.dragCounter++;
|
||||||
this.dropAreaActive = true;
|
this.dropAreaActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
onDragLeave() {
|
onDragLeave() {
|
||||||
|
this.dragCounter--;
|
||||||
|
|
||||||
|
if (this.dragCounter == 0) {
|
||||||
this.dropAreaActive = false;
|
this.dropAreaActive = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
onDragOver(evt: React.DragEvent<T>) {
|
onDragOver(evt: React.DragEvent<T>) {
|
||||||
|
|||||||
@ -50,6 +50,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding: $padding /4 * 3 0;
|
padding: $padding /4 * 3 0;
|
||||||
border-bottom: 1px solid $halfGray;
|
border-bottom: 1px solid $halfGray;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
&:after {
|
&:after {
|
||||||
content: "";
|
content: "";
|
||||||
|
|||||||
@ -30,12 +30,13 @@
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: $radius;
|
border-radius: var(--border-radius);
|
||||||
box-shadow: 0 0 0 1px $halfGray;
|
box-shadow: 0 0 0 1px var(--halfGray);
|
||||||
padding: var(--spacing);
|
padding: var(--spacing);
|
||||||
|
|
||||||
.Icon {
|
.Icon {
|
||||||
height: $margin * 2;
|
height: calc(var(--margin) * 2);
|
||||||
|
width: calc(var(--margin) * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -111,6 +111,10 @@
|
|||||||
&.active {
|
&.active {
|
||||||
background-color: var(--navSelectedBackground);
|
background-color: var(--navSelectedBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,10 +175,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--colorInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -189,16 +189,26 @@
|
|||||||
|
|
||||||
h1, h2 {
|
h1, h2 {
|
||||||
color: var(--textColorAccent);
|
color: var(--textColorAccent);
|
||||||
text-transform: uppercase;
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: var(--textColorAccent);
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--colorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
.hint {
|
.hint {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
8
src/renderer/components/list/list.module.css
Normal file
8
src/renderer/components/list/list.module.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.notFound {
|
||||||
|
@apply flex items-center justify-center h-24;
|
||||||
|
color: var(--textColorDimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchInput>label {
|
||||||
|
box-shadow: none!important;
|
||||||
|
}
|
||||||
65
src/renderer/components/list/list.tsx
Normal file
65
src/renderer/components/list/list.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styles from "./list.module.css";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { SearchInput } from "../input";
|
||||||
|
|
||||||
|
import type { UseTableOptions } from "react-table";
|
||||||
|
import { ReactTable } from "../table/react-table";
|
||||||
|
export type SearchFilter = (item: object) => string | number;
|
||||||
|
|
||||||
|
interface Props extends UseTableOptions<any> {
|
||||||
|
items: object[];
|
||||||
|
filters: SearchFilter[];
|
||||||
|
title?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function List({ columns, data, title, items, filters, }: Props) {
|
||||||
|
const [search, setSearch] = useState<string>("");
|
||||||
|
const query = search.toLowerCase();
|
||||||
|
|
||||||
|
const filteredData = data.filter((item, index) => (
|
||||||
|
filters.some(getText => String(getText(items[index])).toLowerCase().includes(query))
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex align-center justify-between mb-6">
|
||||||
|
<div className="mr-6">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<SearchInput
|
||||||
|
value={search}
|
||||||
|
theme="round-black"
|
||||||
|
onChange={setSearch}
|
||||||
|
className={styles.searchInput}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ReactTable columns={columns} data={filteredData}/>
|
||||||
|
{filteredData.length == 0 && (
|
||||||
|
<div className={styles.notFound}>No data found</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -52,6 +52,7 @@ html {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
box-shadow: 0 0 0 1px $halfGray;
|
box-shadow: 0 0 0 1px $halfGray;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
&--is-focused {
|
&--is-focused {
|
||||||
box-shadow: 0 0 0 1px $primary;
|
box-shadow: 0 0 0 1px $primary;
|
||||||
|
|||||||
43
src/renderer/components/table/react-table.module.css
Normal file
43
src/renderer/components/table/react-table.module.css
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.table {
|
||||||
|
border-spacing: 0;
|
||||||
|
border-top: thin solid var(--hrColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thead {
|
||||||
|
@apply overflow-hidden font-bold;
|
||||||
|
border-bottom: thin solid var(--hrColor);
|
||||||
|
color: var(--textColorAccent);
|
||||||
|
padding-top: 7px;
|
||||||
|
height: 33px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thead .tr {
|
||||||
|
margin: 0 var(--margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tr {
|
||||||
|
margin: calc(var(--margin) * 1.6) var(--margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
.th {
|
||||||
|
@apply relative whitespace-nowrap;
|
||||||
|
padding: 0 var(--padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.headIcon {
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--textColorAccent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabledArrow {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td {
|
||||||
|
@apply flex items-center;
|
||||||
|
padding: 0 var(--padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
@apply justify-end;
|
||||||
|
}
|
||||||
109
src/renderer/components/table/react-table.tsx
Normal file
109
src/renderer/components/table/react-table.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styles from "./react-table.module.css";
|
||||||
|
import React from "react";
|
||||||
|
import { useCallback, useMemo } from "react";
|
||||||
|
import { useFlexLayout, useSortBy, useTable, UseTableOptions } from "react-table";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import { cssNames } from "../../utils";
|
||||||
|
|
||||||
|
interface Props extends UseTableOptions<any> {
|
||||||
|
headless?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ReactTable({ columns, data, headless }: Props) {
|
||||||
|
const defaultColumn = useMemo(
|
||||||
|
() => ({
|
||||||
|
minWidth: 20,
|
||||||
|
width: 100,
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
getTableProps,
|
||||||
|
getTableBodyProps,
|
||||||
|
headerGroups,
|
||||||
|
rows,
|
||||||
|
prepareRow,
|
||||||
|
} = useTable(
|
||||||
|
{
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
defaultColumn,
|
||||||
|
},
|
||||||
|
useFlexLayout,
|
||||||
|
useSortBy,
|
||||||
|
);
|
||||||
|
|
||||||
|
const RenderRow = useCallback(
|
||||||
|
({ index, style }) => {
|
||||||
|
const row = rows[index];
|
||||||
|
|
||||||
|
prepareRow(row);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...row.getRowProps({
|
||||||
|
style,
|
||||||
|
})}
|
||||||
|
className={styles.tr}
|
||||||
|
>
|
||||||
|
{row.cells.map((cell, index) => (
|
||||||
|
<div {...cell.getCellProps()} key={cell.getCellProps().key} className={cssNames(styles.td, columns[index].accessor)}>
|
||||||
|
{cell.render("Cell")}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[columns, prepareRow, rows]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...getTableProps()} className={styles.table}>
|
||||||
|
{!headless && <div className={styles.thead}>
|
||||||
|
{headerGroups.map(headerGroup => (
|
||||||
|
<div {...headerGroup.getHeaderGroupProps()} key={headerGroup.getHeaderGroupProps().key} className={styles.tr}>
|
||||||
|
{headerGroup.headers.map(column => (
|
||||||
|
<div {...column.getHeaderProps(column.getSortByToggleProps())} key={column.getHeaderProps().key} className={styles.th}>
|
||||||
|
{column.render("Header")}
|
||||||
|
{/* Sort direction indicator */}
|
||||||
|
<span>
|
||||||
|
{column.isSorted
|
||||||
|
? column.isSortedDesc
|
||||||
|
? <Icon material="arrow_drop_down" small/>
|
||||||
|
: <Icon material="arrow_drop_up" small/>
|
||||||
|
: !column.disableSortBy && <Icon material="arrow_drop_down" small className={styles.disabledArrow}/>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
<div {...getTableBodyProps()}>
|
||||||
|
{rows.map((row, index) => RenderRow({index}))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Binary file not shown.
35
tailwind.config.js
Normal file
35
tailwind.config.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
purge: ["src/**/*.tsx"],
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Roboto", "Helvetica", "Arial", "sans-serif"],
|
||||||
|
},
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
@ -30,7 +30,8 @@
|
|||||||
"node_modules/*",
|
"node_modules/*",
|
||||||
"types/*"
|
"types/*"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"plugins": [{ "name": "typescript-plugin-css-modules" }]
|
||||||
},
|
},
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
|||||||
4
types/mocks.d.ts
vendored
4
types/mocks.d.ts
vendored
@ -36,3 +36,7 @@ declare module "*.ttf" {
|
|||||||
const content: string;
|
const content: string;
|
||||||
export = content;
|
export = content;
|
||||||
}
|
}
|
||||||
|
declare module "*.module.css" {
|
||||||
|
const classes: { [key: string]: string };
|
||||||
|
export default classes;
|
||||||
|
}
|
||||||
|
|||||||
141
types/react-table.config.d.ts
vendored
Normal file
141
types/react-table.config.d.ts
vendored
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
UseColumnOrderInstanceProps,
|
||||||
|
UseColumnOrderState,
|
||||||
|
UseExpandedHooks,
|
||||||
|
UseExpandedInstanceProps,
|
||||||
|
UseExpandedOptions,
|
||||||
|
UseExpandedRowProps,
|
||||||
|
UseExpandedState,
|
||||||
|
UseFiltersColumnOptions,
|
||||||
|
UseFiltersColumnProps,
|
||||||
|
UseFiltersInstanceProps,
|
||||||
|
UseFiltersOptions,
|
||||||
|
UseFiltersState,
|
||||||
|
UseGlobalFiltersColumnOptions,
|
||||||
|
UseGlobalFiltersInstanceProps,
|
||||||
|
UseGlobalFiltersOptions,
|
||||||
|
UseGlobalFiltersState,
|
||||||
|
UseGroupByCellProps,
|
||||||
|
UseGroupByColumnOptions,
|
||||||
|
UseGroupByColumnProps,
|
||||||
|
UseGroupByHooks,
|
||||||
|
UseGroupByInstanceProps,
|
||||||
|
UseGroupByOptions,
|
||||||
|
UseGroupByRowProps,
|
||||||
|
UseGroupByState,
|
||||||
|
UsePaginationInstanceProps,
|
||||||
|
UsePaginationOptions,
|
||||||
|
UsePaginationState,
|
||||||
|
UseResizeColumnsColumnOptions,
|
||||||
|
UseResizeColumnsColumnProps,
|
||||||
|
UseResizeColumnsOptions,
|
||||||
|
UseResizeColumnsState,
|
||||||
|
UseRowSelectHooks,
|
||||||
|
UseRowSelectInstanceProps,
|
||||||
|
UseRowSelectOptions,
|
||||||
|
UseRowSelectRowProps,
|
||||||
|
UseRowSelectState,
|
||||||
|
UseRowStateCellProps,
|
||||||
|
UseRowStateInstanceProps,
|
||||||
|
UseRowStateOptions,
|
||||||
|
UseRowStateRowProps,
|
||||||
|
UseRowStateState,
|
||||||
|
UseSortByColumnOptions,
|
||||||
|
UseSortByColumnProps,
|
||||||
|
UseSortByHooks,
|
||||||
|
UseSortByInstanceProps,
|
||||||
|
UseSortByOptions,
|
||||||
|
UseSortByState
|
||||||
|
} from "react-table";
|
||||||
|
|
||||||
|
declare module "react-table" {
|
||||||
|
// take this file as-is, or comment out the sections that don't apply to your plugin configuration
|
||||||
|
|
||||||
|
export interface TableOptions<D extends Record<string, unknown>>
|
||||||
|
extends UseExpandedOptions<D>,
|
||||||
|
UseFiltersOptions<D>,
|
||||||
|
UseGlobalFiltersOptions<D>,
|
||||||
|
UseGroupByOptions<D>,
|
||||||
|
UsePaginationOptions<D>,
|
||||||
|
UseResizeColumnsOptions<D>,
|
||||||
|
UseRowSelectOptions<D>,
|
||||||
|
UseRowStateOptions<D>,
|
||||||
|
UseSortByOptions<D>,
|
||||||
|
// note that having Record here allows you to add anything to the options, this matches the spirit of the
|
||||||
|
// underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
|
||||||
|
// feature set, this is a safe default.
|
||||||
|
Record<string, any> {}
|
||||||
|
|
||||||
|
export interface Hooks<D extends Record<string, unknown> = Record<string, unknown>>
|
||||||
|
extends UseExpandedHooks<D>,
|
||||||
|
UseGroupByHooks<D>,
|
||||||
|
UseRowSelectHooks<D>,
|
||||||
|
UseSortByHooks<D> {}
|
||||||
|
|
||||||
|
export interface TableInstance<D extends Record<string, unknown> = Record<string, unknown>>
|
||||||
|
extends UseColumnOrderInstanceProps<D>,
|
||||||
|
UseExpandedInstanceProps<D>,
|
||||||
|
UseFiltersInstanceProps<D>,
|
||||||
|
UseGlobalFiltersInstanceProps<D>,
|
||||||
|
UseGroupByInstanceProps<D>,
|
||||||
|
UsePaginationInstanceProps<D>,
|
||||||
|
UseRowSelectInstanceProps<D>,
|
||||||
|
UseRowStateInstanceProps<D>,
|
||||||
|
UseSortByInstanceProps<D> {}
|
||||||
|
|
||||||
|
export interface TableState<D extends Record<string, unknown> = Record<string, unknown>>
|
||||||
|
extends UseColumnOrderState<D>,
|
||||||
|
UseExpandedState<D>,
|
||||||
|
UseFiltersState<D>,
|
||||||
|
UseGlobalFiltersState<D>,
|
||||||
|
UseGroupByState<D>,
|
||||||
|
UsePaginationState<D>,
|
||||||
|
UseResizeColumnsState<D>,
|
||||||
|
UseRowSelectState<D>,
|
||||||
|
UseRowStateState<D>,
|
||||||
|
UseSortByState<D> {}
|
||||||
|
|
||||||
|
export interface ColumnInterface<D extends Record<string, unknown> = Record<string, unknown>>
|
||||||
|
extends UseFiltersColumnOptions<D>,
|
||||||
|
UseGlobalFiltersColumnOptions<D>,
|
||||||
|
UseGroupByColumnOptions<D>,
|
||||||
|
UseResizeColumnsColumnOptions<D>,
|
||||||
|
UseSortByColumnOptions<D> {}
|
||||||
|
|
||||||
|
export interface ColumnInstance<D extends Record<string, unknown> = Record<string, unknown>>
|
||||||
|
extends UseFiltersColumnProps<D>,
|
||||||
|
UseGroupByColumnProps<D>,
|
||||||
|
UseResizeColumnsColumnProps<D>,
|
||||||
|
UseSortByColumnProps<D> {}
|
||||||
|
|
||||||
|
export interface Cell<D extends Record<string, unknown> = Record<string, unknown>>
|
||||||
|
extends UseGroupByCellProps<D>,
|
||||||
|
UseRowStateCellProps<D> {}
|
||||||
|
|
||||||
|
export interface Row<D extends Record<string, unknown> = Record<string, unknown>>
|
||||||
|
extends UseExpandedRowProps<D>,
|
||||||
|
UseGroupByRowProps<D>,
|
||||||
|
UseRowSelectRowProps<D>,
|
||||||
|
UseRowStateRowProps<D> {}
|
||||||
|
}
|
||||||
@ -120,14 +120,20 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
|||||||
{
|
{
|
||||||
test: /\.s?css$/,
|
test: /\.s?css$/,
|
||||||
use: [
|
use: [
|
||||||
// https://webpack.js.org/plugins/mini-css-extract-plugin/
|
|
||||||
isDevelopment ? "style-loader" : MiniCssExtractPlugin.loader,
|
isDevelopment ? "style-loader" : MiniCssExtractPlugin.loader,
|
||||||
{
|
{
|
||||||
loader: "css-loader",
|
loader: "css-loader",
|
||||||
options: {
|
options: {
|
||||||
sourceMap: isDevelopment
|
modules: {
|
||||||
|
auto: true,
|
||||||
|
mode: "local",
|
||||||
|
localIdentName: "[name]__[local]--[hash:base64:5]",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
loader: "postcss-loader"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
loader: "sass-loader",
|
loader: "sass-loader",
|
||||||
options: {
|
options: {
|
||||||
@ -139,11 +145,11 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new ProgressBarPlugin(),
|
new ProgressBarPlugin(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user