diff --git a/package.json b/package.json index 15085b2a84..450def5778 100644 --- a/package.json +++ b/package.json @@ -252,6 +252,7 @@ "devDependencies": { "@emeraldpay/hashicon-react": "^0.4.0", "@material-ui/core": "^4.10.1", + "@material-ui/lab": "^4.0.0-alpha.57", "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", "@testing-library/jest-dom": "^5.11.5", "@testing-library/react": "^11.1.0", @@ -317,6 +318,7 @@ "concurrently": "^5.2.0", "css-element-queries": "^1.2.3", "css-loader": "^3.5.3", + "deepdash": "^5.3.5", "dompurify": "^2.0.11", "electron": "^9.4.0", "electron-builder": "^22.7.0", diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index f30168145a..c14a6988b6 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -487,7 +487,7 @@ export class Extensions extends React.Component { return ( - +

Lens Extensions

Add new features and functionality via Lens Extensions. diff --git a/src/renderer/components/+landing-page/landing-page.scss b/src/renderer/components/+landing-page/landing-page.scss index ea1eb664ef..8045003b33 100644 --- a/src/renderer/components/+landing-page/landing-page.scss +++ b/src/renderer/components/+landing-page/landing-page.scss @@ -10,6 +10,7 @@ .content { margin: unset; max-width: unset; + height: 100%; } } } diff --git a/src/renderer/components/+preferences/helm-charts.scss b/src/renderer/components/+preferences/helm-charts.scss new file mode 100644 index 0000000000..69d952d186 --- /dev/null +++ b/src/renderer/components/+preferences/helm-charts.scss @@ -0,0 +1,11 @@ +.HelmCharts { + .repos { + margin-top: var(--margin); + + .Badge { + display: flex; + margin-bottom: 1px!important; + padding: 6px 8px; + } + } +} \ No newline at end of file diff --git a/src/renderer/components/+preferences/helm-charts.tsx b/src/renderer/components/+preferences/helm-charts.tsx new file mode 100644 index 0000000000..9bcdd5f755 --- /dev/null +++ b/src/renderer/components/+preferences/helm-charts.tsx @@ -0,0 +1,139 @@ +import "./helm-charts.scss"; + +import React from "react"; +import { action, computed, observable } from "mobx"; + +import { HelmRepo, repoManager } from "../../../main/helm/helm-repo-manager"; +import { Badge } from "../badge"; +import { Button } from "../button"; +import { Icon } from "../icon"; +import { Notifications } from "../notifications"; +import { Select, SelectOption } from "../select"; +import { Tooltip } from "../tooltip"; +import { AddHelmRepoDialog } from "./add-helm-repo-dialog"; +import { observer } from "mobx-react"; + +@observer +export class HelmCharts extends React.Component { + @observable loading = false; + @observable repos: HelmRepo[] = []; + @observable addedRepos = observable.map(); + + @computed get options(): SelectOption[] { + return this.repos.map(repo => ({ + label: repo.name, + value: repo, + })); + } + + async componentDidMount() { + await this.loadRepos(); + } + + @action + async loadRepos() { + this.loading = true; + + try { + if (!this.repos.length) { + this.repos = await repoManager.loadAvailableRepos(); // via https://helm.sh + } + const repos = await repoManager.repositories(); // via helm-cli + + this.addedRepos.clear(); + repos.forEach(repo => this.addedRepos.set(repo.name, repo)); + } catch (err) { + Notifications.error(String(err)); + } + + this.loading = false; + } + + async addRepo(repo: HelmRepo) { + try { + await repoManager.addRepo(repo); + this.addedRepos.set(repo.name, repo); + } catch (err) { + Notifications.error(<>Adding helm branch {repo.name} has failed: {String(err)}); + } + } + + async removeRepo(repo: HelmRepo) { + try { + await repoManager.removeRepo(repo); + this.addedRepos.delete(repo.name); + } catch (err) { + Notifications.error( + <>Removing helm branch {repo.name} has failed: {String(err)} + ); + } + } + + onRepoSelect = async ({ value: repo }: SelectOption) => { + const isAdded = this.addedRepos.has(repo.name); + + if (isAdded) { + Notifications.ok(<>Helm branch {repo.name} already in use); + + return; + } + this.loading = true; + await this.addRepo(repo); + this.loading = false; + }; + + formatOptionLabel = ({ value: repo }: SelectOption) => { + const isAdded = this.addedRepos.has(repo.name); + + return ( +
+ {repo.name} + {isAdded && } +
+ ); + }; + + render() { + return ( +
+
+ The directory to download binaries into. - + (); @observable httpProxy = userStore.preferences.httpsProxy || ""; @observable shell = userStore.preferences.shell || ""; @@ -35,79 +29,6 @@ export class Preferences extends React.Component { })); } - @computed get helmOptions(): SelectOption[] { - return this.helmRepos.map(repo => ({ - label: repo.name, - value: repo, - })); - } - - async componentDidMount() { - await this.loadHelmRepos(); - } - - @action - async loadHelmRepos() { - this.helmLoading = true; - - try { - if (!this.helmRepos.length) { - this.helmRepos = await repoManager.loadAvailableRepos(); // via https://helm.sh - } - const repos = await repoManager.repositories(); // via helm-cli - - this.helmAddedRepos.clear(); - repos.forEach(repo => this.helmAddedRepos.set(repo.name, repo)); - } catch (err) { - Notifications.error(String(err)); - } - this.helmLoading = false; - } - - async addRepo(repo: HelmRepo) { - try { - await repoManager.addRepo(repo); - this.helmAddedRepos.set(repo.name, repo); - } catch (err) { - Notifications.error(<>Adding helm branch {repo.name} has failed: {String(err)}); - } - } - - async removeRepo(repo: HelmRepo) { - try { - await repoManager.removeRepo(repo); - this.helmAddedRepos.delete(repo.name); - } catch (err) { - Notifications.error( - <>Removing helm branch {repo.name} has failed: {String(err)} - ); - } - } - - onRepoSelect = async ({ value: repo }: SelectOption) => { - const isAdded = this.helmAddedRepos.has(repo.name); - - if (isAdded) { - Notifications.ok(<>Helm branch {repo.name} already in use); - - return; - } - this.helmLoading = true; - await this.addRepo(repo); - this.helmLoading = false; - }; - - formatHelmOptionLabel = ({ value: repo }: SelectOption) => { - const isAdded = this.helmAddedRepos.has(repo.name); - - return ( -
- {repo.name} - {isAdded && } -
- ); - }; - render() { const { preferences } = userStore; const header =

Preferences

; @@ -122,110 +43,110 @@ export class Preferences extends React.Component { } return ( - -

Color Theme

- preferences.colorTheme = value} + /> + +
+

Proxy

+ + this.httpProxy = v} + onBlur={() => preferences.httpsProxy = this.httpProxy} + /> + + Proxy is used only for non-cluster communication. + -

HTTP Proxy

- this.httpProxy = v} - onBlur={() => preferences.httpsProxy = this.httpProxy} - /> - - Proxy is used only for non-cluster communication. - -

Terminal Shell

- this.shell = v} - onBlur={() => preferences.shell = this.shell} - /> - - The path of the shell that the terminal uses. - - + + preferences.allowUntrustedCAs = v} + /> + + This will make Lens to trust ANY certificate authority without any validations.{" "} + Needed with some corporate proxies that do certificate re-writing.{" "} + Does not affect cluster communications! + +
+
+

Terminal Shell

+ + this.shell = v} + onBlur={() => preferences.shell = this.shell} + /> + + The path of the shell that the terminal uses. + +
+
+

Start-up

+ + preferences.openAtLogin = v} + /> +
+ -

Helm

-
- - - - -
- ); - })} -
- +
+
+

Extensions

+
+ {appPreferenceRegistry.getItems().map(({ title, components: { Hint, Input } }, index) => { + return ( +
+

{title}

+ + + + +
+ ); + })} +
+ + )}/> ); } } diff --git a/src/renderer/components/cluster-manager/bottom-bar.scss b/src/renderer/components/cluster-manager/bottom-bar.scss index a40146ad97..0f290eaf82 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.scss +++ b/src/renderer/components/cluster-manager/bottom-bar.scss @@ -4,7 +4,7 @@ background-color: var(--blue); padding: 0 2px; - height: 22px; + height: var(--bottom-bar-height); #current-workspace { font-size: var(--font-size-small); diff --git a/src/renderer/components/cluster-manager/cluster-manager.scss b/src/renderer/components/cluster-manager/cluster-manager.scss index accc72ef40..f05d6b109d 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.scss +++ b/src/renderer/components/cluster-manager/cluster-manager.scss @@ -1,4 +1,6 @@ .ClusterManager { + --bottom-bar-height: 22px; + display: grid; grid-template-areas: "menu main" "menu main" "bottom-bar bottom-bar"; grid-template-rows: auto 1fr min-content; diff --git a/src/renderer/components/layout/page-layout.scss b/src/renderer/components/layout/page-layout.scss index d7fd0a8544..c4629f0dbe 100644 --- a/src/renderer/components/layout/page-layout.scss +++ b/src/renderer/components/layout/page-layout.scss @@ -1,17 +1,35 @@ .PageLayout { - $spacing: $padding * 2; --width: 60%; - --max-width: 1000px; - --min-width: 570px; + --nav-width: 180px; + --nav-column-width: 30vw; + --spacing: calc(var(--unit) * 2); + --wrapper-padding: calc(var(--spacing) * 2); + --header-height: 64px; + --header-height-mac: 80px; position: relative; width: 100%; height: 100%; display: grid !important; grid-template-rows: min-content 1fr; + grid-template-columns: 1fr; + + &.showNavigation { + --width: 70%; + + grid-template-columns: var(--nav-column-width) 1fr; + + > .content-wrapper { + > .content { + width: 100%; + padding-left: 1px; // Fix visual content crop + padding-right: calc(var(--nav-column-width) - var(--nav-width)); + } + } + } // covers whole app view area - &.top { + &.showOnTop { position: fixed !important; // allow to cover ClustersMenu z-index: 1; left: 0; @@ -19,37 +37,44 @@ right: 0; bottom: 24px; height: unset; - background-color: $mainBackground; + background-color: var(--mainBackground); // adds extra space for traffic-light top buttons (mac only) .is-mac & > .header { - padding-top: $spacing * 2; + height: var(--header-height-mac); + padding-top: calc(var(--spacing) * 2); } } > .header { position: sticky; - padding: $spacing; - background-color: $layoutTabsBackground; + padding: var(--spacing); + background-color: var(--layoutTabsBackground); + height: var(--header-height); + grid-column-start: 1; + grid-column-end: 4; } - > .content-wrapper { - overflow: auto; - padding: $spacing * 2; + > .content-navigation { display: flex; - flex-direction: column; + justify-content: flex-end; + overflow-y: auto; + margin-top: 32px; - > .content { - flex: 1; - margin: 0 auto; - width: var(--width); - min-width: var(--min-width); - max-width: var(--max-width); + ul.TreeView { + width: var(--nav-width); + padding-right: 24px; } } - h2:not(:first-of-type) { - margin-top: $spacing; + > .content-wrapper { + padding: 32px; + overflow: auto; + + > .content { + width: var(--width); + margin: 0 auto; + } } p { @@ -57,21 +82,49 @@ } a { - color: $colorInfo; + color: var(--colorInfo); } .SubTitle { text-transform: none; margin-bottom: 0 !important; - - + * + .hint { - margin-top: -$padding / 2; - } } .Select { &__control { - box-shadow: 0 0 0 1px $borderFaintColor; + box-shadow: 0 0 0 1px var(--borderFaintColor); + } + } + + section { + display: flex; + flex-direction: column; + margin-bottom: var(--spacing); + + > :not(:last-child) { + margin-bottom: var(--spacing); + } + + h1, h2 { + color: var(--textColorAccent); + } + + h1 { + font-size: x-large; + border-bottom: 1px solid var(--borderFaintColor); + padding-bottom: var(--padding); + } + + h2 { + font-size: large; + } + + small.hint { + margin-top: calc(var(--unit) * -1.5); + } + + .SubTitle { + margin-top: 0; } } } diff --git a/src/renderer/components/layout/page-layout.tsx b/src/renderer/components/layout/page-layout.tsx index ce7e1d54c2..b437a53d56 100644 --- a/src/renderer/components/layout/page-layout.tsx +++ b/src/renderer/components/layout/page-layout.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import { autobind, cssNames, IClassName } from "../../utils"; import { Icon } from "../icon"; import { navigation } from "../../navigation"; +import { NavigationTree, RecursiveTreeView } from "../tree-view"; export interface PageLayoutProps extends React.DOMAttributes { className?: IClassName; @@ -14,6 +15,7 @@ export interface PageLayoutProps extends React.DOMAttributes { provideBackButtonNavigation?: boolean; contentGaps?: boolean; showOnTop?: boolean; // covers whole app view + navigation?: NavigationTree[]; back?: (evt: React.MouseEvent | KeyboardEvent) => void; } @@ -57,9 +59,9 @@ export class PageLayout extends React.Component { render() { const { contentClass, header, headerClass, provideBackButtonNavigation, - contentGaps, showOnTop, children, ...elemProps + contentGaps, showOnTop, navigation, children, ...elemProps } = this.props; - const className = cssNames("PageLayout", { top: showOnTop }, this.props.className); + const className = cssNames("PageLayout", { showOnTop, showNavigation: navigation }, this.props.className); return (
@@ -73,8 +75,13 @@ export class PageLayout extends React.Component { /> )}
-
-
+ { navigation && ( + + )} +
+
{children}
diff --git a/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx b/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx new file mode 100644 index 0000000000..512d3251ba --- /dev/null +++ b/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx @@ -0,0 +1,186 @@ +import React from "react"; +import "@testing-library/jest-dom/extend-expect"; +import { render, waitFor } from "@testing-library/react"; +import { ScrollSpy } from "../scroll-spy"; +import { RecursiveTreeView } from "../../tree-view"; + +const observe = jest.fn(); + +Object.defineProperty(window, "IntersectionObserver", { + writable: true, + value: jest.fn().mockImplementation(() => ({ + observe, + unobserve: jest.fn(), + })), +}); + +describe("", () => { + it("renders w/o errors", () => { + const { container } = render( ( +
+
+

Application

+
+
+ )}/>); + + expect(container).toBeInstanceOf(HTMLElement); + }); + + it("calls intersection observer", () => { + render( ( +
+
+

Application

+
+
+ )}/>); + + expect(observe).toHaveBeenCalled(); + }); + + it("renders dataTree component", async () => { + const { queryByTestId } = render( ( +
+ +
+

Application

+
+
+ )}/>); + + await waitFor(() => { + expect(queryByTestId("TreeView")).toBeInTheDocument(); + }); + }); + + it("throws if no sections founded", () => { + // Prevent writing to stderr during this render. + const err = console.error; + + console.error = jest.fn(); + + expect(() => render( ( +
+ Content +
+ )}/>)).toThrow(); + + // Restore writing to stderr. + console.error = err; + }); +}); + + +describe(" dataTree inside ", () => { + it("contains links to all sections", async () => { + const { queryByTitle } = render( ( +
+ +
+

Application

+
+

Appearance

+
+
+

Theme

+
description
+
+
+
+ )}/>); + + await waitFor(() => { + expect(queryByTitle("Application")).toBeInTheDocument(); + expect(queryByTitle("Appearance")).toBeInTheDocument(); + expect(queryByTitle("Theme")).toBeInTheDocument(); + }); + }); + + it("not showing links to sections without id", async () => { + const { queryByTitle } = render( ( +
+ +
+

Application

+
+

Kubectl

+
+
+

Appearance

+
+
+
+ )}/>); + + await waitFor(() => { + expect(queryByTitle("Application")).toBeInTheDocument(); + expect(queryByTitle("Appearance")).toBeInTheDocument(); + expect(queryByTitle("Kubectl")).not.toBeInTheDocument(); + }); + }); + + it("expands parent sections", async () => { + const { queryByTitle } = render( ( +
+ +
+

Application

+
+

Appearance

+
+
+

Theme

+
description
+
+
+
+

Kubernetes

+
+

Kubectl

+
+
+
+ )}/>); + + await waitFor(() => { + expect(queryByTitle("Application")).toHaveAttribute("aria-expanded"); + expect(queryByTitle("Kubernetes")).toHaveAttribute("aria-expanded"); + }); + + // console.log(prettyDOM()); + }); + + it("skips sections without headings", async () => { + const { queryByTitle } = render( ( +
+ +
+

Application

+
+

Appearance

+
+
+

Theme

+
+
+
+ )}/>); + + await waitFor(() => { + expect(queryByTitle("Application")).toBeInTheDocument(); + expect(queryByTitle("appearance")).not.toBeInTheDocument(); + expect(queryByTitle("Appearance")).not.toBeInTheDocument(); + }); + }); +}); diff --git a/src/renderer/components/scroll-spy/scroll-spy.tsx b/src/renderer/components/scroll-spy/scroll-spy.tsx new file mode 100644 index 0000000000..565eabc9ba --- /dev/null +++ b/src/renderer/components/scroll-spy/scroll-spy.tsx @@ -0,0 +1,96 @@ +import { observer } from "mobx-react"; +import React, { useEffect, useRef, useState } from "react"; +import { useMutationObserver } from "../../hooks"; +import { NavigationTree } from "../tree-view"; + +interface Props extends React.DOMAttributes { + render: (data: NavigationTree[]) => JSX.Element + htmlFor?: string // Id of the element to put observers on + rootMargin?: string // https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#creating_an_intersection_observer +} + +export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100% 0px" }: Props) => { + const parent = useRef(); + const sections = useRef>(); + const [tree, setTree] = useState([]); + const [activeElementId, setActiveElementId] = useState(""); + + const setSections = () => { + sections.current = parent.current.querySelectorAll("section"); + + if (!sections.current.length) { + throw new Error("No
tag founded! Content should be placed inside
elements to activate navigation."); + } + }; + + const getSectionsParentElement = () => { + return sections.current?.[0].parentElement; + }; + + const updateNavigation = () => { + setTree(getNavigation(getSectionsParentElement())); + }; + + const getNavigation = (element: Element) => { + const sections = element.querySelectorAll(":scope > section"); // Searching only direct children of an element. Impossible without :scope + const children: NavigationTree[] = []; + + sections.forEach(section => { + const id = section.getAttribute("id"); + const parentId = section.parentElement.id; + const name = section.querySelector("h1, h2, h3, h4, h5, h6")?.textContent; + const selected = id === activeElementId; + + if (!name || !id) { + return; + } + + children.push({ + id, + parentId, + name, + selected, + children: getNavigation(section) + }); + }); + + return children; + }; + + const handleIntersect = ([entry]: IntersectionObserverEntry[]) => { + if (entry.isIntersecting) { + setActiveElementId(entry.target.closest("section[id]").id); + } + }; + + const observeSections = () => { + const options: IntersectionObserverInit = { + root: document.getElementById(htmlFor) || getSectionsParentElement(), + rootMargin + }; + + sections.current.forEach((section) => { + const observer = new IntersectionObserver(handleIntersect, options); + const target = section.querySelector("section") || section; + + observer.observe(target); + }); + }; + + useEffect(() => { + setSections(); + observeSections(); + }, []); + + useEffect(() => { + updateNavigation(); + }, [activeElementId]); + + useMutationObserver(getSectionsParentElement(), updateNavigation); + + return ( +
+ {render(tree)} +
+ ); +}); diff --git a/src/renderer/components/tree-view/index.ts b/src/renderer/components/tree-view/index.ts new file mode 100644 index 0000000000..4514f03621 --- /dev/null +++ b/src/renderer/components/tree-view/index.ts @@ -0,0 +1 @@ +export * from "./tree-view"; diff --git a/src/renderer/components/tree-view/tree-view.scss b/src/renderer/components/tree-view/tree-view.scss new file mode 100644 index 0000000000..2817d5ddb7 --- /dev/null +++ b/src/renderer/components/tree-view/tree-view.scss @@ -0,0 +1,27 @@ +.TreeView { + .MuiTypography-body1 { + font-size: var(--font-size); + color: var(--textColorAccent); + } + + .MuiTreeItem-root { + > .MuiTreeItem-content .MuiTreeItem-label { + border-radius: 4px; + border: 1px solid transparent; + } + + &.selected { + > .MuiTreeItem-content .MuiTreeItem-label { + border-color: var(--blue); + font-weight: bold; + } + } + + // Make inner component selected state invisible + &.Mui-selected, &.Mui-selected:focus { + > .MuiTreeItem-content .MuiTreeItem-label { + background-color: transparent; + } + } + } +} \ No newline at end of file diff --git a/src/renderer/components/tree-view/tree-view.tsx b/src/renderer/components/tree-view/tree-view.tsx new file mode 100644 index 0000000000..450b0021aa --- /dev/null +++ b/src/renderer/components/tree-view/tree-view.tsx @@ -0,0 +1,100 @@ +import "./tree-view.scss"; + +import React, { useEffect, useRef } from "react"; +import { Icon } from "../icon"; +import TreeView from "@material-ui/lab/TreeView"; +import TreeItem from "@material-ui/lab/TreeItem"; +import { cssNames } from "../../utils"; + +import _ from "lodash"; +import getDeepDash from "deepdash"; + +const deepDash = getDeepDash(_); + +export interface NavigationTree { + id: string; + parentId: string; + name: string; + selected?: boolean; + children?: NavigationTree[]; +} + +interface Props { + data: NavigationTree[] +} + +function scrollToItem(id: string) { + document.getElementById(id)?.scrollIntoView(); +} + +function getSelectedNode(data: NavigationTree[]) { + return deepDash.findDeep(data, (value, key) => key === "selected" && value === true)?.parent; +} + +export function RecursiveTreeView({ data }: Props) { + const [expanded, setExpanded] = React.useState([]); + const prevData = useRef(data); + + const handleToggle = (event: React.ChangeEvent<{}>, nodeIds: string[]) => { + setExpanded(nodeIds); + }; + + const expandTopLevelNodes = () => { + setExpanded(data.map(node => node.id)); + }; + + const expandParentNode = () => { + const node = getSelectedNode(data) as any as NavigationTree; + const id = node?.parentId; + + if (id && !expanded.includes(id)) { + setExpanded([...expanded, id]); + } + }; + + const onLabelClick = (event: React.MouseEvent, nodeId: string) => { + event.preventDefault(); + scrollToItem(nodeId); + }; + + const renderTree = (nodes: NavigationTree[]) => { + return nodes.map(node => ( + onLabelClick(event, node.id)} + className={cssNames({selected: node.selected})} + title={node.name} + > + {Array.isArray(node.children) ? node.children.map((node) => renderTree([node])) : null} + + )); + }; + + useEffect(() => { + if (!prevData.current.length) { + expandTopLevelNodes(); + } else { + expandParentNode(); + } + prevData.current = data; + }, [data]); + + if (!data.length) { + return null; + } + + return ( + } + defaultExpandIcon={} + > + {renderTree(data)} + + ); +} diff --git a/src/renderer/hooks/index.ts b/src/renderer/hooks/index.ts index 860847e979..3d94196ac0 100644 --- a/src/renderer/hooks/index.ts +++ b/src/renderer/hooks/index.ts @@ -3,3 +3,4 @@ export * from "./useStorage"; export * from "./useOnUnmount"; export * from "./useInterval"; +export * from "./useMutationObserver"; diff --git a/src/renderer/hooks/useMutationObserver.ts b/src/renderer/hooks/useMutationObserver.ts new file mode 100644 index 0000000000..327f1e1997 --- /dev/null +++ b/src/renderer/hooks/useMutationObserver.ts @@ -0,0 +1,27 @@ +import { useEffect } from "react"; + +const config: MutationObserverInit = { + subtree: true, + childList: true, + attributes: false, + characterData: false +}; + +export function useMutationObserver( + root: Element, + callback: MutationCallback, + options: MutationObserverInit = config +) { + + useEffect(() => { + if (root) { + const observer = new MutationObserver(callback); + + observer.observe(root, options); + + return () => { + observer.disconnect(); + }; + } + }, [callback, options]); +} diff --git a/yarn.lock b/yarn.lock index bc2e606b71..6da8f68c34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -834,6 +834,17 @@ react-is "^16.8.0" react-transition-group "^4.4.0" +"@material-ui/lab@^4.0.0-alpha.57": + version "4.0.0-alpha.57" + resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.57.tgz#e8961bcf6449e8a8dabe84f2700daacfcafbf83a" + integrity sha512-qo/IuIQOmEKtzmRD2E4Aa6DB4A87kmY6h0uYhjUmrrgmEAgbbw9etXpWPVXuRK6AGIQCjFzV6WO2i21m1R4FCw== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/utils" "^4.11.2" + clsx "^1.0.4" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + "@material-ui/styles@^4.10.0": version "4.10.0" resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071" @@ -871,6 +882,15 @@ resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== +"@material-ui/utils@^4.11.2": + version "4.11.2" + resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.11.2.tgz#f1aefa7e7dff2ebcb97d31de51aecab1bb57540a" + integrity sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA== + dependencies: + "@babel/runtime" "^7.4.4" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + "@material-ui/utils@^4.9.12", "@material-ui/utils@^4.9.6": version "4.9.12" resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.9.12.tgz#0d639f1c1ed83fffb2ae10c21d15a938795d9e65" @@ -4373,6 +4393,14 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepdash@^5.3.5: + version "5.3.5" + resolved "https://registry.yarnpkg.com/deepdash/-/deepdash-5.3.5.tgz#611bec9c1f2829832d21971dcbefe712e408647d" + integrity sha512-1ZdPPCI1pCEqeAWGSw+Nbpb/2iIV4w3sGPc22H/PDtcApb8+psTzPIoOVD040iBaT2wqZab29Kjrz6G+cd5mqQ== + dependencies: + lodash "^4.17.20" + lodash-es "^4.17.20" + deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" @@ -8634,6 +8662,11 @@ lockfile@^1.0.4: dependencies: signal-exit "^3.0.2" +lodash-es@^4.17.20: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -8692,6 +8725,11 @@ lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.1 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +lodash@^4.17.20: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + logform@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360" @@ -11226,7 +11264,7 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-i resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1: +"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1: version "17.0.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==