1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Convert Preferences to Route-based component

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
Alex Andreev 2021-08-05 16:36:19 +03:00
parent aa9b059695
commit 3ea47fe764
8 changed files with 427 additions and 235 deletions

View File

@ -26,4 +26,29 @@ export const preferencesRoute: RouteProps = {
path: "/preferences"
};
export const appRoute: RouteProps = {
path: `${preferencesRoute.path}/app`
};
export const proxyRoute: RouteProps = {
path: `${preferencesRoute.path}/proxy`
};
export const kubernetesRoute: RouteProps = {
path: `${preferencesRoute.path}/kubernetes`
};
export const telemetryRoute: RouteProps = {
path: `${preferencesRoute.path}/telemetry`
};
export const extensionRoute: RouteProps = {
path: `${preferencesRoute.path}/extensions`
};
export const preferencesURL = buildURL(preferencesRoute.path);
export const appURL = buildURL(appRoute.path);
export const proxyURL = buildURL(proxyRoute.path);
export const kubernetesURL = buildURL(kubernetesRoute.path);
export const telemetryURL = buildURL(telemetryRoute.path);
export const extensionURL = buildURL(extensionRoute.path);

View File

@ -0,0 +1,104 @@
/**
* 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 React from "react";
import { observer } from "mobx-react";
import { SubTitle } from "../layout/sub-title";
import { Select, SelectOption } from "../select";
import { ThemeStore } from "../../theme.store";
import { UserStore } from "../../../common/user-store";
import { Input } from "../input";
import { isWindows } from "../../../common/vars";
import { FormSwitch, Switcher } from "../switch";
import moment from "moment-timezone";
const timezoneOptions: SelectOption<string>[] = moment.tz.names().map(zone => ({
label: zone,
value: zone,
}));
export const Application = observer(() => {
const defaultShell = process.env.SHELL
|| process.env.PTYSHELL
|| (
isWindows
? "powershell.exe"
: "System default shell"
);
const [shell, setShell] = React.useState(UserStore.getInstance().shell || "");
return (
<section id="application">
<h2 data-testid="application-header">Application</h2>
<section id="appearance">
<SubTitle title="Theme"/>
<Select
options={ThemeStore.getInstance().themeOptions}
value={UserStore.getInstance().colorTheme}
onChange={({ value }: SelectOption) => UserStore.getInstance().colorTheme = value}
themeName="lens"
/>
</section>
<hr/>
<section id="shell">
<SubTitle title="Terminal Shell Path"/>
<Input
theme="round-black"
placeholder={defaultShell}
value={shell}
onChange={v => setShell(v)}
onBlur={() => UserStore.getInstance().shell = shell}
/>
</section>
<hr/>
<section id="other">
<SubTitle title="Start-up"/>
<FormSwitch
control={
<Switcher
checked={UserStore.getInstance().openAtLogin}
onChange={v => UserStore.getInstance().openAtLogin = v.target.checked}
name="startup"
/>
}
label="Automatically start Lens on login"
/>
</section>
<hr />
<section id="locale">
<SubTitle title="Locale Timezone" />
<Select
options={timezoneOptions}
value={UserStore.getInstance().localeTimezone}
onChange={({ value }: SelectOption) => UserStore.getInstance().setLocaleTimezone(value)}
themeName="lens"
/>
</section>
</section>
);
});

View File

@ -0,0 +1,38 @@
/**
* 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 { observer } from "mobx-react";
import React from "react";
import { AppPreferenceRegistry } from "../../../extensions/registries";
import { ExtensionSettings } from "./preferences";
export const Telemetry = observer(() => {
const extensions = AppPreferenceRegistry.getInstance().getItems();
return (
<section id="extensions">
<h2>Extensions</h2>
{extensions.filter(e => !e.showInPreferencesTab).map((extension) =>
<ExtensionSettings key={extension.id} {...extension}/>
)}
</section>
);
});

View File

@ -0,0 +1,47 @@
/**
* 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 { observer } from "mobx-react";
import React from "react";
import { HelmCharts } from "./helm-charts";
import { KubeconfigSyncs } from "./kubeconfig-syncs";
import { KubectlBinaries } from "./kubectl-binaries";
export const Kubernetes = observer(() => {
return (
<section id="kubernetes">
<section id="kubectl">
<h2 data-testid="kubernetes-header">Kubernetes</h2>
<KubectlBinaries />
</section>
<hr/>
<section id="kube-sync">
<h2 data-testid="kubernetes-sync-header">Kubeconfig Syncs</h2>
<KubeconfigSyncs />
</section>
<hr/>
<section id="helm">
<h2>Helm Charts</h2>
<HelmCharts/>
</section>
</section>
);
});

View File

@ -18,279 +18,115 @@
* 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 "./preferences.scss";
import { makeObservable, observable } from "mobx";
import { observer } from "mobx-react";
import React from "react";
import moment from "moment-timezone";
import { computed, observable, reaction, makeObservable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { matchPath, Redirect, Route, RouteComponentProps, RouteProps, Switch } from "react-router";
import { isWindows } from "../../../common/vars";
import {
appRoute,
appURL,
extensionRoute,
extensionURL,
kubernetesRoute,
kubernetesURL,
preferencesURL,
proxyRoute,
proxyURL,
telemetryRoute,
telemetryURL,
} from "../../../common/routes";
import { AppPreferenceRegistry, RegisteredAppPreference } from "../../../extensions/registries/app-preference-registry";
import { UserStore } from "../../../common/user-store";
import { ThemeStore } from "../../theme.store";
import { Input } from "../input";
import { SubTitle } from "../layout/sub-title";
import { Select, SelectOption } from "../select";
import { HelmCharts } from "./helm-charts";
import { KubectlBinaries } from "./kubectl-binaries";
import { navigation } from "../../navigation";
import { Tab, Tabs } from "../tabs";
import { FormSwitch, Switcher } from "../switch";
import { KubeconfigSyncs } from "./kubeconfig-syncs";
import { navigate, navigation } from "../../navigation";
import { SettingLayout } from "../layout/setting-layout";
import { Checkbox } from "../checkbox";
import { sentryDsn } from "../../../common/vars";
import { SubTitle } from "../layout/sub-title";
import { Tab, Tabs } from "../tabs";
import { Application } from "./application";
import { Kubernetes } from "./kubernetes";
import { LensProxy } from "./proxy";
import { Telemetry } from "./telemetry";
import { boundMethod } from "autobind-decorator";
enum Pages {
Application = "application",
Proxy = "proxy",
Kubernetes = "kubernetes",
Telemetry = "telemetry",
Extensions = "extensions",
Other = "other"
interface Props extends RouteComponentProps<any> {
}
@observer
export class Preferences extends React.Component {
@observable httpProxy = UserStore.getInstance().httpsProxy || "";
@observable shell = UserStore.getInstance().shell || "";
@observable activeTab = Pages.Application;
export class Preferences extends React.Component<Props> {
@observable historyLength: number | undefined;
constructor(props: {}) {
constructor(props: Props) {
super(props);
makeObservable(this);
}
@computed get themeOptions(): SelectOption<string>[] {
return ThemeStore.getInstance().themes.map(theme => ({
label: theme.name,
value: theme.id,
}));
}
timezoneOptions: SelectOption<string>[] = moment.tz.names().map(zone => ({
label: zone,
value: zone,
}));
componentDidMount() {
disposeOnUnmount(this, [
reaction(() => navigation.location.hash, hash => {
const fragment = hash.slice(1); // hash is /^(#\w.)?$/
if (fragment) {
// ignore empty fragments
document.getElementById(fragment)?.scrollIntoView();
}
}, {
fireImmediately: true
})
]);
this.historyLength = this.props.history?.length;
}
onTabChange = (tabId: Pages) => {
this.activeTab = tabId;
};
@boundMethod
onClose() {
const stepsToGoBack = navigation.length - this.historyLength + 1;
if (isNaN(stepsToGoBack)) {
navigation.goBack();
} else {
navigation.go(-stepsToGoBack);
}
}
renderNavigation() {
const extensions = AppPreferenceRegistry.getInstance().getItems().filter(e => !e.showInPreferencesTab);
const currentLocation = navigation.location.pathname;
const isActive = (route: RouteProps) => !!matchPath(currentLocation, { path: route.path, exact: route.exact });
return (
<Tabs className="flex column" scrollable={false} onChange={this.onTabChange} value={this.activeTab}>
<Tabs className="flex column" scrollable={false} onChange={(url) => navigate(url)}>
<div className="header">Preferences</div>
<Tab value={Pages.Application} label="Application" data-testid="application-tab"/>
<Tab value={Pages.Proxy} label="Proxy" data-testid="proxy-tab"/>
<Tab value={Pages.Kubernetes} label="Kubernetes" data-testid="kube-tab"/>
<Tab value={Pages.Telemetry} label="Telemetry" data-testid="telemetry-tab"/>
<Tab value={appURL()} label="Application" data-testid="application-tab" active={isActive(appRoute)}/>
<Tab value={proxyURL()} label="Proxy" data-testid="proxy-tab" active={isActive(proxyRoute)}/>
<Tab value={kubernetesURL()} label="Kubernetes" data-testid="kube-tab" active={isActive(kubernetesRoute)}/>
<Tab value={telemetryURL()} label="Telemetry" data-testid="telemetry-tab" active={isActive(telemetryRoute)}/>
{extensions.length > 0 &&
<Tab value={Pages.Extensions} label="Extensions" data-testid="extensions-tab"/>
<Tab value={extensionURL()} label="Extensions" data-testid="extensions-tab" active={isActive(extensionRoute)}/>
}
</Tabs>
);
}
renderExtension({ title, id, components: { Hint, Input } }: RegisteredAppPreference) {
return (
<React.Fragment key={id}>
<section id={id} className="small">
<SubTitle title={title}/>
<Input/>
<div className="hint">
<Hint/>
</div>
</section>
<hr className="small"/>
</React.Fragment>
);
}
render() {
const extensions = AppPreferenceRegistry.getInstance().getItems();
const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == Pages.Telemetry);
const defaultShell = process.env.SHELL
|| process.env.PTYSHELL
|| (
isWindows
? "powershell.exe"
: "System default shell"
);
return (
<SettingLayout
navigation={this.renderNavigation()}
className="Preferences"
contentGaps={false}
back={this.onClose}
>
{this.activeTab == Pages.Application && (
<section id="application">
<h2 data-testid="application-header">Application</h2>
<section id="appearance">
<SubTitle title="Theme"/>
<Select
options={this.themeOptions}
value={UserStore.getInstance().colorTheme}
onChange={({ value }: SelectOption) => UserStore.getInstance().colorTheme = value}
themeName="lens"
/>
</section>
<hr/>
<section id="shell">
<SubTitle title="Terminal Shell Path"/>
<Input
theme="round-black"
placeholder={defaultShell}
value={this.shell}
onChange={v => this.shell = v}
onBlur={() => UserStore.getInstance().shell = this.shell}
/>
</section>
<hr/>
<section id="other">
<SubTitle title="Start-up"/>
<FormSwitch
control={
<Switcher
checked={UserStore.getInstance().openAtLogin}
onChange={v => UserStore.getInstance().openAtLogin = v.target.checked}
name="startup"
/>
}
label="Automatically start Lens on login"
/>
</section>
<hr />
<section id="locale">
<SubTitle title="Locale Timezone" />
<Select
options={this.timezoneOptions}
value={UserStore.getInstance().localeTimezone}
onChange={({ value }: SelectOption) => UserStore.getInstance().setLocaleTimezone(value)}
themeName="lens"
/>
</section>
</section>
)}
{this.activeTab == Pages.Proxy && (
<section id="proxy">
<section>
<h2 data-testid="proxy-header">Proxy</h2>
<SubTitle title="HTTP Proxy"/>
<Input
theme="round-black"
placeholder="Type HTTP proxy url (example: http://proxy.acme.org:8080)"
value={this.httpProxy}
onChange={v => this.httpProxy = v}
onBlur={() => UserStore.getInstance().httpsProxy = this.httpProxy}
/>
<small className="hint">
Proxy is used only for non-cluster communication.
</small>
</section>
<hr className="small"/>
<section className="small">
<SubTitle title="Certificate Trust"/>
<FormSwitch
control={
<Switcher
checked={UserStore.getInstance().allowUntrustedCAs}
onChange={v => UserStore.getInstance().allowUntrustedCAs = v.target.checked}
name="startup"
/>
}
label="Allow untrusted Certificate Authorities"
/>
<small className="hint">
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!
</small>
</section>
</section>
)}
{this.activeTab == Pages.Kubernetes && (
<section id="kubernetes">
<section id="kubectl">
<h2 data-testid="kubernetes-header">Kubernetes</h2>
<KubectlBinaries />
</section>
<hr/>
<section id="kube-sync">
<h2 data-testid="kubernetes-sync-header">Kubeconfig Syncs</h2>
<KubeconfigSyncs />
</section>
<hr/>
<section id="helm">
<h2>Helm Charts</h2>
<HelmCharts/>
</section>
</section>
)}
{this.activeTab == Pages.Telemetry && (
<section id="telemetry">
<h2 data-testid="telemetry-header">Telemetry</h2>
{telemetryExtensions.map(this.renderExtension)}
{sentryDsn ? (
<React.Fragment key='sentry'>
<section id='sentry' className="small">
<SubTitle title='Automatic Error Reporting' />
<Checkbox
label="Allow automatic error reporting"
value={UserStore.getInstance().allowErrorReporting}
onChange={value => {
UserStore.getInstance().allowErrorReporting = value;
}}
/>
<div className="hint">
<span>
Automatic error reports provide vital information about issues and application crashes.
It is highly recommended to keep this feature enabled to ensure fast turnaround for issues you might encounter.
</span>
</div>
</section>
<hr className="small" />
</React.Fragment>) :
// we don't need to shows the checkbox at all if Sentry dsn is not a valid url
null
}
</section>
)}
{this.activeTab == Pages.Extensions && (
<section id="extensions">
<h2>Extensions</h2>
{extensions.filter(e => !e.showInPreferencesTab).map(this.renderExtension)}
</section>
)}
<Switch>
<Route path={appURL()} component={Application}/>
<Route path={proxyURL()} component={LensProxy}/>
<Route path={kubernetesURL()} component={Kubernetes}/>
<Route path={telemetryURL()} component={Telemetry}/>
<Route path={extensionURL()} component={Telemetry}/>
<Redirect exact from={`${preferencesURL()}/`} to={appURL()}/>
</Switch>
</SettingLayout>
);
}
}
export function ExtensionSettings({ title, id, components: { Hint, Input } }: RegisteredAppPreference) {
return (
<React.Fragment>
<section id={id} className="small">
<SubTitle title={title}/>
<Input/>
<div className="hint">
<Hint/>
</div>
</section>
<hr className="small"/>
</React.Fragment>
);
}

View File

@ -0,0 +1,71 @@
/**
* 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 { observer } from "mobx-react";
import React from "react";
import { UserStore } from "../../../common/user-store";
import { Input } from "../input";
import { SubTitle } from "../layout/sub-title";
import { FormSwitch, Switcher } from "../switch";
export const LensProxy = observer(() => {
const [proxy, setProxy] = React.useState(UserStore.getInstance().httpsProxy || "");
return (
<section id="proxy">
<section>
<h2 data-testid="proxy-header">Proxy</h2>
<SubTitle title="HTTP Proxy"/>
<Input
theme="round-black"
placeholder="Type HTTP proxy url (example: http://proxy.acme.org:8080)"
value={proxy}
onChange={v => setProxy(v)}
onBlur={() => UserStore.getInstance().httpsProxy = proxy}
/>
<small className="hint">
Proxy is used only for non-cluster communication.
</small>
</section>
<hr className="small"/>
<section className="small">
<SubTitle title="Certificate Trust"/>
<FormSwitch
control={
<Switcher
checked={UserStore.getInstance().allowUntrustedCAs}
onChange={v => UserStore.getInstance().allowUntrustedCAs = v.target.checked}
name="startup"
/>
}
label="Allow untrusted Certificate Authorities"
/>
<small className="hint">
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!
</small>
</section>
</section>
);
});

View File

@ -0,0 +1,63 @@
/**
* 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 { observer } from "mobx-react";
import React from "react";
import { UserStore } from "../../../common/user-store";
import { sentryDsn } from "../../../common/vars";
import { AppPreferenceRegistry } from "../../../extensions/registries";
import { Checkbox } from "../checkbox";
import { SubTitle } from "../layout/sub-title";
import { ExtensionSettings } from "./preferences";
export const Telemetry = observer(() => {
const extensions = AppPreferenceRegistry.getInstance().getItems();
const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == "telemetry");
return (
<section id="telemetry">
<h2 data-testid="telemetry-header">Telemetry</h2>
{telemetryExtensions.map((extension) => <ExtensionSettings key={extension.id} {...extension}/>)}
{sentryDsn ? (
<React.Fragment key='sentry'>
<section id='sentry' className="small">
<SubTitle title='Automatic Error Reporting' />
<Checkbox
label="Allow automatic error reporting"
value={UserStore.getInstance().allowErrorReporting}
onChange={value => {
UserStore.getInstance().allowErrorReporting = value;
}}
/>
<div className="hint">
<span>
Automatic error reports provide vital information about issues and application crashes.
It is highly recommended to keep this feature enabled to ensure fast turnaround for issues you might encounter.
</span>
</div>
</section>
<hr className="small" />
</React.Fragment>) :
// we don't need to shows the checkbox at all if Sentry dsn is not a valid url
null
}
</section>
);
});

View File

@ -25,6 +25,7 @@ import { UserStore } from "../common/user-store";
import logger from "../main/logger";
import darkTheme from "./themes/lens-dark.json";
import lightTheme from "./themes/lens-light.json";
import type { SelectOption } from "./components/select";
export type ThemeId = string;
@ -67,6 +68,13 @@ export class ThemeStore extends Singleton {
return this.allThemes.get(this.activeThemeId) ?? this.allThemes.get("lens-dark");
}
@computed get themeOptions(): SelectOption<string>[] {
return this.themes.map(theme => ({
label: theme.name,
value: theme.id,
}));
}
constructor() {
super();