diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 76e64f0f2c..1b54dd1678 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -40,7 +40,7 @@ jobs: git clone "https://${GH_TOKEN}@github.com/lensapp/lens-ide.git" .lens-ide-overlay rm -rf .lens-ide-overlay/.git cp -r .lens-ide-overlay/* ./ - cp build/package.json.patch . && patch package.json package.json.patch + jq -s '.[0] * .[1]' package.json package.ide.json > package.custom.json && mv package.custom.json package.json displayName: Customize config env: GH_TOKEN: $(LENS_IDE_GH_TOKEN) @@ -90,7 +90,7 @@ jobs: git clone "https://${GH_TOKEN}@github.com/lensapp/lens-ide.git" .lens-ide-overlay rm -rf .lens-ide-overlay/.git cp -r .lens-ide-overlay/* ./ - cp build/package.json.patch . && patch package.json package.json.patch + jq -s '.[0] * .[1]' package.json package.ide.json > package.custom.json && mv package.custom.json package.json displayName: Customize config env: GH_TOKEN: $(LENS_IDE_GH_TOKEN) @@ -142,7 +142,7 @@ jobs: git clone "https://${GH_TOKEN}@github.com/lensapp/lens-ide.git" .lens-ide-overlay rm -rf .lens-ide-overlay/.git cp -r .lens-ide-overlay/* ./ - cp build/package.json.patch . && patch package.json package.json.patch + jq -s '.[0] * .[1]' package.json package.ide.json > package.custom.json && mv package.custom.json package.json displayName: Customize config env: GH_TOKEN: $(LENS_IDE_GH_TOKEN) diff --git a/package.json b/package.json index 76710a913d..94dff13821 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "open-lens", "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", - "version": "5.0.0-alpha.3", + "version": "5.0.0-alpha.4", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", "license": "MIT", diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index e4622ffa64..7e007efb0d 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -60,13 +60,13 @@ describe("user store tests", () => { it("allows setting and getting preferences", () => { const us = UserStore.getInstance(); - us.preferences.httpsProxy = "abcd://defg"; + us.httpsProxy = "abcd://defg"; - expect(us.preferences.httpsProxy).toBe("abcd://defg"); - expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme); + expect(us.httpsProxy).toBe("abcd://defg"); + expect(us.colorTheme).toBe(UserStore.defaultTheme); - us.preferences.colorTheme = "light"; - expect(us.preferences.colorTheme).toBe("light"); + us.colorTheme = "light"; + expect(us.colorTheme).toBe("light"); }); it("correctly resets theme to default value", async () => { @@ -74,9 +74,9 @@ describe("user store tests", () => { us.isLoaded = true; - us.preferences.colorTheme = "some other theme"; + us.colorTheme = "some other theme"; await us.resetTheme(); - expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme); + expect(us.colorTheme).toBe(UserStore.defaultTheme); }); it("correctly calculates if the last seen version is an old release", () => { diff --git a/src/common/request.ts b/src/common/request.ts index 8c78c865fa..e2b9062916 100644 --- a/src/common/request.ts +++ b/src/common/request.ts @@ -6,7 +6,7 @@ import { UserStore } from "./user-store"; // https://github.com/lensapp/lens/issues/459 function getDefaultRequestOpts(): Partial { - const { httpsProxy, allowUntrustedCAs } = UserStore.getInstance().preferences; + const { httpsProxy, allowUntrustedCAs } = UserStore.getInstance(); return { proxy: httpsProxy || undefined, diff --git a/src/common/user-store.ts b/src/common/user-store.ts index 67dacc05e9..4ffed5a32e 100644 --- a/src/common/user-store.ts +++ b/src/common/user-store.ts @@ -2,24 +2,26 @@ import type { ThemeId } from "../renderer/theme.store"; import { app, remote } from "electron"; import semver from "semver"; import { readFile } from "fs-extra"; -import { action, computed, makeObservable, observable, reaction } from "mobx"; +import { action, computed, observable, reaction, makeObservable } from "mobx"; import moment from "moment-timezone"; import { BaseStore } from "./base-store"; -import migrations, { fileNameMigration } from "../migrations/user-store"; +import migrations from "../migrations/user-store"; import { getAppVersion, toJS } from "./utils"; import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers"; import { appEventBus } from "./event-bus"; import logger from "../main/logger"; import path from "path"; +import { fileNameMigration } from "../migrations/user-store"; +import { ObservableToggleSet } from "../renderer/utils"; export interface UserStoreModel { kubeConfigPath: string; lastSeenAppVersion: string; seenContexts: string[]; - preferences: UserPreferences; + preferences: UserPreferencesModel; } -export interface UserPreferences { +export interface UserPreferencesModel { httpsProxy?: string; shell?: string; colorTheme?: string; @@ -31,7 +33,7 @@ export interface UserPreferences { downloadBinariesPath?: string; kubectlBinariesPath?: string; openAtLogin?: boolean; - hiddenTableColumns?: Record; + hiddenTableColumns?: [string, string[]][]; } export class UserStore extends BaseStore { @@ -44,25 +46,33 @@ export class UserStore extends BaseStore { }); makeObservable(this); - this.handleOnLoad(); } @observable lastSeenAppVersion = "0.0.0"; - @observable kubeConfigPath = kubeConfigDefaultPath; // used in add-cluster page for providing context + + /** + * used in add-cluster page for providing context + */ + @observable kubeConfigPath = kubeConfigDefaultPath; @observable seenContexts = observable.set(); @observable newContexts = observable.set(); + @observable allowTelemetry = true; + @observable allowUntrustedCAs = false; + @observable colorTheme = UserStore.defaultTheme; + @observable localeTimezone = moment.tz.guess(true) || "UTC"; + @observable downloadMirror = "default"; + @observable httpsProxy?: string; + @observable shell?: string; + @observable downloadBinariesPath?: string; + @observable kubectlBinariesPath?: string; - @observable preferences: UserPreferences = { - allowTelemetry: true, - allowUntrustedCAs: false, - colorTheme: UserStore.defaultTheme, - localeTimezone: moment.tz.guess(true) || "UTC", - downloadMirror: "default", - downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version - openAtLogin: false, - hiddenTableColumns: {}, - }; + /** + * Download kubectl binaries matching cluster version + */ + @observable downloadKubectlBinaries = true; + @observable openAtLogin = false; + hiddenTableColumns = observable.map>(); protected async handleOnLoad() { await this.whenLoaded; @@ -73,12 +83,12 @@ export class UserStore extends BaseStore { if (app) { // track telemetry availability - reaction(() => this.preferences.allowTelemetry, allowed => { + reaction(() => this.allowTelemetry, allowed => { appEventBus.emit({ name: "telemetry", action: allowed ? "enabled" : "disabled" }); }); // open at system start-up - reaction(() => this.preferences.openAtLogin, openAtLogin => { + reaction(() => this.openAtLogin, openAtLogin => { app.setLoginItemSettings({ openAtLogin, openAsHidden: true, @@ -100,17 +110,40 @@ export class UserStore extends BaseStore { return super.load(); } - get isNewVersion() { + @computed get isNewVersion() { return semver.gt(getAppVersion(), this.lastSeenAppVersion); } - @action - setHiddenTableColumns(tableId: string, names: Set | string[]) { - this.preferences.hiddenTableColumns[tableId] = Array.from(names); + @computed get resolvedShell(): string | undefined { + return this.shell || process.env.SHELL || process.env.PTYSHELL; } - getHiddenTableColumns(tableId: string): Set { - return new Set(this.preferences.hiddenTableColumns[tableId]); + /** + * Checks if a column (by ID) for a table (by ID) is configured to be hidden + * @param tableId The ID of the table to be checked against + * @param columnIds The list of IDs the check if one is hidden + * @returns true if at least one column under the table is set to hidden + */ + isTableColumnHidden(tableId: string, ...columnIds: string[]): boolean { + if (columnIds.length === 0) { + return true; + } + + const config = this.hiddenTableColumns.get(tableId); + + if (!config) { + return true; + } + + return columnIds.some(columnId => config.has(columnId)); + } + + @action + /** + * Toggles the hidden configuration of a table's column + */ + toggleTableColumnVisibility(tableId: string, columnId: string) { + this.hiddenTableColumns.get(tableId)?.toggle(columnId); } @action @@ -125,7 +158,7 @@ export class UserStore extends BaseStore { @action async resetTheme() { await this.whenLoaded; - this.preferences.colorTheme = UserStore.defaultTheme; + this.colorTheme = UserStore.defaultTheme; } @action @@ -136,7 +169,7 @@ export class UserStore extends BaseStore { @action setLocaleTimezone(tz: string) { - this.preferences.localeTimezone = tz; + this.localeTimezone = tz; } protected refreshNewContexts = async () => { @@ -176,16 +209,58 @@ export class UserStore extends BaseStore { this.kubeConfigPath = kubeConfigPath; } this.seenContexts.replace(seenContexts); - Object.assign(this.preferences, preferences); + + if (!preferences) { + return; + } + + this.httpsProxy = preferences.httpsProxy; + this.shell = preferences.shell; + this.colorTheme = preferences.colorTheme; + this.localeTimezone = preferences.localeTimezone; + this.allowUntrustedCAs = preferences.allowUntrustedCAs; + this.allowTelemetry = preferences.allowTelemetry; + this.downloadMirror = preferences.downloadMirror; + this.downloadKubectlBinaries = preferences.downloadKubectlBinaries; + this.downloadBinariesPath = preferences.downloadBinariesPath; + this.kubectlBinariesPath = preferences.kubectlBinariesPath; + this.openAtLogin = preferences.openAtLogin; + + this.hiddenTableColumns.clear(); + + for (const [tableId, columnIds] of preferences.hiddenTableColumns ?? []) { + this.hiddenTableColumns.set(tableId, new ObservableToggleSet(columnIds)); + } } toJSON(): UserStoreModel { - return toJS({ + const hiddenTableColumns: [string, string[]][] = []; + + for (const [key, values] of this.hiddenTableColumns.entries()) { + hiddenTableColumns.push([key, Array.from(values)]); + } + + const model: UserStoreModel = { kubeConfigPath: this.kubeConfigPath, lastSeenAppVersion: this.lastSeenAppVersion, seenContexts: Array.from(this.seenContexts), - preferences: this.preferences, - }); + preferences: { + httpsProxy: this.httpsProxy, + shell: this.shell, + colorTheme: this.colorTheme, + localeTimezone: this.localeTimezone, + allowUntrustedCAs: this.allowUntrustedCAs, + allowTelemetry: this.allowTelemetry, + downloadMirror: this.downloadMirror, + downloadKubectlBinaries: this.downloadKubectlBinaries, + downloadBinariesPath: this.downloadBinariesPath, + kubectlBinariesPath: this.kubectlBinariesPath, + openAtLogin: this.openAtLogin, + hiddenTableColumns, + }, + }; + + return toJS(model); } } diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 8d3815a116..01b14353ca 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -8,19 +8,20 @@ export * from "./base64"; export * from "./camelCase"; export * from "./toJS"; export * from "./cloneJson"; -export * from "./delay"; export * from "./debouncePromise"; export * from "./defineGlobal"; -export * from "./getRandId"; -export * from "./splitArray"; -export * from "./saveToAppFiles"; -export * from "./singleton"; -export * from "./openExternal"; +export * from "./delay"; +export * from "./disposer"; export * from "./downloadFile"; export * from "./escapeRegExp"; +export * from "./getRandId"; +export * from "./openExternal"; +export * from "./saveToAppFiles"; +export * from "./singleton"; +export * from "./splitArray"; export * from "./tar"; +export * from "./toggle-set"; export * from "./type-narrowing"; -export * from "./disposer"; import * as iter from "./iter"; diff --git a/src/common/utils/toggle-set.ts b/src/common/utils/toggle-set.ts new file mode 100644 index 0000000000..1a8b22f186 --- /dev/null +++ b/src/common/utils/toggle-set.ts @@ -0,0 +1,20 @@ +import { action, ObservableSet } from "mobx"; + +export class ToggleSet extends Set { + public toggle(value: T): void { + if (!this.delete(value)) { + // Set.prototype.delete returns false if `value` was not in the set + this.add(value); + } + } +} + +export class ObservableToggleSet extends ObservableSet { + @action + public toggle(value: T): void { + if (!this.delete(value)) { + // Set.prototype.delete returns false if `value` was not in the set + this.add(value); + } + } +} diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index 903f54ff07..104f44f0d7 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -113,12 +113,12 @@ export class Kubectl { } public getPathFromPreferences() { - return UserStore.getInstance().preferences?.kubectlBinariesPath || this.getBundledPath(); + return UserStore.getInstance().kubectlBinariesPath || this.getBundledPath(); } protected getDownloadDir() { - if (UserStore.getInstance().preferences?.downloadBinariesPath) { - return path.join(UserStore.getInstance().preferences.downloadBinariesPath, "kubectl"); + if (UserStore.getInstance().downloadBinariesPath) { + return path.join(UserStore.getInstance().downloadBinariesPath, "kubectl"); } return Kubectl.kubectlDir; @@ -129,7 +129,7 @@ export class Kubectl { return this.getBundledPath(); } - if (UserStore.getInstance().preferences?.downloadKubectlBinaries === false) { + if (UserStore.getInstance().downloadKubectlBinaries === false) { return this.getPathFromPreferences(); } @@ -223,7 +223,7 @@ export class Kubectl { } public async ensureKubectl(): Promise { - if (UserStore.getInstance().preferences?.downloadKubectlBinaries === false) { + if (UserStore.getInstance().downloadKubectlBinaries === false) { return true; } @@ -303,7 +303,7 @@ export class Kubectl { } protected async writeInitScripts() { - const kubectlPath = UserStore.getInstance().preferences?.downloadKubectlBinaries ? this.dirname : path.dirname(this.getPathFromPreferences()); + const kubectlPath = UserStore.getInstance().downloadKubectlBinaries ? this.dirname : path.dirname(this.getPathFromPreferences()); const helmPath = helmCli.getBinaryDir(); const fsPromises = fs.promises; const bashScriptPath = path.join(this.dirname, ".bash_set_path"); @@ -361,7 +361,7 @@ export class Kubectl { } protected getDownloadMirror() { - const mirror = packageMirrors.get(UserStore.getInstance().preferences?.downloadMirror); + const mirror = packageMirrors.get(UserStore.getInstance().downloadMirror); if (mirror) { return mirror; diff --git a/src/main/shell-session/local-shell-session.ts b/src/main/shell-session/local-shell-session.ts index c500ff169f..6bfc9441cf 100644 --- a/src/main/shell-session/local-shell-session.ts +++ b/src/main/shell-session/local-shell-session.ts @@ -21,8 +21,8 @@ export class LocalShellSession extends ShellSession { protected async getShellArgs(shell: string): Promise { const helmpath = helmCli.getBinaryDir(); - const pathFromPreferences = UserStore.getInstance().preferences.kubectlBinariesPath || this.kubectl.getBundledPath(); - const kubectlPathDir = UserStore.getInstance().preferences.downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences); + const pathFromPreferences = UserStore.getInstance().kubectlBinariesPath || this.kubectl.getBundledPath(); + const kubectlPathDir = UserStore.getInstance().downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences); switch(path.basename(shell)) { case "powershell.exe": diff --git a/src/main/shell-session/shell-session.ts b/src/main/shell-session/shell-session.ts index b3710aab70..599f84e42b 100644 --- a/src/main/shell-session/shell-session.ts +++ b/src/main/shell-session/shell-session.ts @@ -119,7 +119,7 @@ export abstract class ShellSession { protected async getShellEnv() { const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(await shellEnv()))); const pathStr = [...this.getPathEntries(), await this.kubectlBinDirP, process.env.PATH].join(path.delimiter); - const shell = UserStore.getInstance().preferences.shell || process.env.SHELL || process.env.PTYSHELL; + const shell = UserStore.getInstance().resolvedShell; delete env.DEBUG; // don't pass DEBUG into shells @@ -143,7 +143,7 @@ export abstract class ShellSession { if (path.basename(env.PTYSHELL) === "zsh") { env.OLD_ZDOTDIR = env.ZDOTDIR || env.HOME; - env.ZDOTDIR = this.kubectlBinDirP; + env.ZDOTDIR = await this.kubectlBinDirP; env.DISABLE_AUTO_UPDATE = "true"; } diff --git a/src/migrations/user-store/5.0.0-alpha.3.ts b/src/migrations/user-store/5.0.0-alpha.3.ts new file mode 100644 index 0000000000..ae90b85525 --- /dev/null +++ b/src/migrations/user-store/5.0.0-alpha.3.ts @@ -0,0 +1,18 @@ +// Switch representation of hiddenTableColumns in store +import { migration } from "../migration-wrapper"; + +export default migration({ + version: "5.0.0-alpha.3", + run(store) { + const preferences = store.get("preferences"); + const oldHiddenTableColumns: Record = preferences?.hiddenTableColumns; + + if (!oldHiddenTableColumns) { + return; + } + + preferences.hiddenTableColumns = Object.entries(oldHiddenTableColumns); + + store.set("preferences", preferences); + } +}); diff --git a/src/migrations/user-store/index.ts b/src/migrations/user-store/index.ts index 5f5085b475..c8733fdd8a 100644 --- a/src/migrations/user-store/index.ts +++ b/src/migrations/user-store/index.ts @@ -1,6 +1,7 @@ // User store migrations import version210Beta4 from "./2.1.0-beta.4"; +import version500Alpha3 from "./5.0.0-alpha.3"; import { fileNameMigration } from "./file-name-migration"; export { @@ -9,4 +10,5 @@ export { export default { ...version210Beta4, + ...version500Alpha3, }; diff --git a/src/renderer/components/+catalog/catalog-add-button.scss b/src/renderer/components/+catalog/catalog-add-button.scss index 7377a60dfc..4b2bb13490 100644 --- a/src/renderer/components/+catalog/catalog-add-button.scss +++ b/src/renderer/components/+catalog/catalog-add-button.scss @@ -5,5 +5,20 @@ .MuiFab-primary { background-color: var(--blue); + + &:hover { + background-color: #317bb3; + } } } + +.MuiTooltip-popper { + .MuiTooltip-tooltip { + background-color: #222; + font-size: 12px + } + + .MuiTooltip-arrow { + color: #222; + } +} \ No newline at end of file diff --git a/src/renderer/components/+catalog/catalog-add-button.tsx b/src/renderer/components/+catalog/catalog-add-button.tsx index 33ae38eba1..376036f0ae 100644 --- a/src/renderer/components/+catalog/catalog-add-button.tsx +++ b/src/renderer/components/+catalog/catalog-add-button.tsx @@ -50,6 +50,13 @@ export class CatalogAddButton extends React.Component { this.isOpen = false; } + @autobind() + onButtonClick() { + if (this.menuItems.length == 1) { + this.menuItems[0].onClick(); + } + } + render() { if (this.menuItems.length === 0) { return null; @@ -64,6 +71,7 @@ export class CatalogAddButton extends React.Component { onClose={this.onClose} icon={} direction="up" + onClick={this.onButtonClick} > {this.menuItems.map((menuItem, index) => { return { - const [downloadPath, setDownloadPath] = useState(preferences.downloadBinariesPath || ""); - const [binariesPath, setBinariesPath] = useState(preferences.kubectlBinariesPath || ""); +export const KubectlBinaries = observer(() => { + const userStore = UserStore.getInstance(); + const [downloadPath, setDownloadPath] = useState(userStore.downloadBinariesPath || ""); + const [binariesPath, setBinariesPath] = useState(userStore.kubectlBinariesPath || ""); const pathValidator = downloadPath ? InputValidators.isPath : undefined; const downloadMirrorOptions: SelectOption[] = [ @@ -18,8 +19,8 @@ export const KubectlBinaries = observer(({ preferences }: { preferences: UserPre ]; const save = () => { - preferences.downloadBinariesPath = downloadPath; - preferences.kubectlBinariesPath = binariesPath; + userStore.downloadBinariesPath = downloadPath; + userStore.kubectlBinariesPath = binariesPath; }; return ( @@ -29,8 +30,8 @@ export const KubectlBinaries = observer(({ preferences }: { preferences: UserPre preferences.downloadKubectlBinaries = v.target.checked} + checked={userStore.downloadKubectlBinaries} + onChange={v => userStore.downloadKubectlBinaries = v.target.checked} name="kubectl-download" /> } @@ -45,9 +46,9 @@ export const KubectlBinaries = observer(({ preferences }: { preferences: UserPre
The directory to download binaries into. @@ -81,7 +82,7 @@ export const KubectlBinaries = observer(({ preferences }: { preferences: UserPre validators={pathValidator} onChange={setBinariesPath} onBlur={save} - disabled={preferences.downloadKubectlBinaries} + disabled={userStore.downloadKubectlBinaries} /> diff --git a/src/renderer/components/+preferences/preferences.tsx b/src/renderer/components/+preferences/preferences.tsx index cd00f10bf4..6c68008027 100644 --- a/src/renderer/components/+preferences/preferences.tsx +++ b/src/renderer/components/+preferences/preferences.tsx @@ -30,8 +30,8 @@ enum Pages { @observer export class Preferences extends React.Component { - @observable httpProxy = UserStore.getInstance().preferences.httpsProxy || ""; - @observable shell = UserStore.getInstance().preferences.shell || ""; + @observable httpProxy = UserStore.getInstance().httpsProxy || ""; + @observable shell = UserStore.getInstance().shell || ""; @observable activeTab = Pages.Application; constructor(props: object) { @@ -105,7 +105,6 @@ export class Preferences extends React.Component { render() { const extensions = appPreferenceRegistry.getItems(); const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == Pages.Telemetry); - const { preferences } = UserStore.getInstance(); const defaultShell = process.env.SHELL || process.env.PTYSHELL || ( @@ -128,8 +127,8 @@ export class Preferences extends React.Component { UserStore.getInstance().setLocaleTimezone(value)} themeName="lens" /> @@ -186,7 +185,7 @@ export class Preferences extends React.Component { placeholder="Type HTTP proxy url (example: http://proxy.acme.org:8080)" value={this.httpProxy} onChange={v => this.httpProxy = v} - onBlur={() => preferences.httpsProxy = this.httpProxy} + onBlur={() => UserStore.getInstance().httpsProxy = this.httpProxy} /> Proxy is used only for non-cluster communication. @@ -200,8 +199,8 @@ export class Preferences extends React.Component { preferences.allowUntrustedCAs = v.target.checked} + checked={UserStore.getInstance().allowUntrustedCAs} + onChange={v => UserStore.getInstance().allowUntrustedCAs = v.target.checked} name="startup" /> } @@ -220,7 +219,7 @@ export class Preferences extends React.Component {

Kubernetes

- +

diff --git a/src/renderer/components/animate/animate.scss b/src/renderer/components/animate/animate.scss index 6272e9f62b..ec5900e958 100644 --- a/src/renderer/components/animate/animate.scss +++ b/src/renderer/components/animate/animate.scss @@ -63,6 +63,6 @@ } &.opacity-scale { - @include animate-opacity-scale; + @include animate-opacity-scale(100ms); } } diff --git a/src/renderer/components/command-palette/command-container.scss b/src/renderer/components/command-palette/command-container.scss index c8f76a4698..8b1c1f55e0 100644 --- a/src/renderer/components/command-palette/command-container.scss +++ b/src/renderer/components/command-palette/command-container.scss @@ -1,11 +1,86 @@ #command-container { position: absolute; top: 20px; - width: 40%; left: 0; right: 0; margin-left: auto; margin-right: auto; - padding: 10px; - background-color: var(--dockInfoBackground); + background-color: var(--layoutBackground); + border-radius: 8px; + box-shadow: rgba(0, 0, 0, 0.5) 0px 16px 70px; + max-width: 640px; + color: var(--settingsColor); + transition: all 0.3s; + + .Input { + label { + caret-color: var(--blue); + color: var(--settingsColor); + background: transparent; + border: 0; + font-size: 18px; + padding: 15px 19px; + border-bottom: 1px solid var(--borderFaintColor); + + &:focus { + box-shadow: none; + } + } + } + + .hint { + padding: 8px; + display: block; + } + + .errors { + padding: 8px; + } + + .Select__menu { + position: relative; + } + + .Select__control { + padding: var(--padding); + box-shadow: none; + border-bottom: 1px solid var(--borderFaintColor); + caret-color: var(--blue); + font-size: 18px; + + &:focus { + box-shadow: none; + } + } + + .Select__menu { + box-shadow: none; + background: transparent; + margin: 0; + } + + .Select__menu-list { + padding: 0; + } + + .Select__option { + background-color: transparent; + padding: 10px 18px; + + &:hover { + background-color: var(--menuSelectedOptionBgc); + border-left: 4px solid var(--blue); + padding-left: 14px; + } + + &.Select__option--is-focused { + background-color: var(--menuSelectedOptionBgc); + border-left: 4px solid var(--blue); + padding-left: 14px; + } + } + + .Select__menu-notice--no-options { + padding: 12px; + } } diff --git a/src/renderer/components/command-palette/command-container.tsx b/src/renderer/components/command-palette/command-container.tsx index 308eda85ca..49ff413a76 100644 --- a/src/renderer/components/command-palette/command-container.tsx +++ b/src/renderer/components/command-palette/command-container.tsx @@ -78,7 +78,7 @@ export class CommandContainer extends React.Component<{ clusterId?: string }> { render() { return ( - this.commandComponent = null}> + this.commandComponent = null} modal={false}>
{this.commandComponent}
diff --git a/src/renderer/components/command-palette/command-dialog.tsx b/src/renderer/components/command-palette/command-dialog.tsx index 7942826e5f..ce5bf8bed1 100644 --- a/src/renderer/components/command-palette/command-dialog.tsx +++ b/src/renderer/components/command-palette/command-dialog.tsx @@ -75,6 +75,7 @@ export class CommandDialog extends React.Component { render() { return ( this.onChange(v.value)} components={{ DropdownIndicator: null, IndicatorSeparator: null }} menuIsOpen={true} diff --git a/src/renderer/components/item-object-list/item-list-layout.tsx b/src/renderer/components/item-object-list/item-list-layout.tsx index 956a49482c..00eb682ef1 100644 --- a/src/renderer/components/item-object-list/item-list-layout.tsx +++ b/src/renderer/components/item-object-list/item-list-layout.tsx @@ -6,7 +6,7 @@ import { computed, makeObservable } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { ConfirmDialog, ConfirmDialogParams } from "../confirm-dialog"; import { Table, TableCell, TableCellProps, TableHead, TableProps, TableRow, TableRowProps, TableSortCallback } from "../table"; -import { autobind, createStorage, cssNames, IClassName, isReactNode, noop, prevDefault, stopPropagation } from "../../utils"; +import { autobind, createStorage, cssNames, IClassName, isReactNode, noop, ObservableToggleSet, prevDefault, stopPropagation } from "../../utils"; import { AddRemoveButtons, AddRemoveButtonsProps } from "../add-remove-buttons"; import { NoItems } from "../no-items"; import { Spinner } from "../spinner"; @@ -122,6 +122,10 @@ export class ItemListLayout extends React.Component { throw new Error("[ItemListLayout]: configurable list require props.tableId to be specified"); } + if (isConfigurable && !UserStore.getInstance().hiddenTableColumns.has(tableId)) { + UserStore.getInstance().hiddenTableColumns.set(tableId, new ObservableToggleSet()); + } + if (preloadStores) { this.loadStores(); @@ -256,7 +260,7 @@ export class ItemListLayout extends React.Component { cellProps.className = cssNames(cellProps.className, headCell.className); } - if (!headCell || !this.isHiddenColumn(headCell)) { + if (!headCell || this.showColumn(headCell)) { return ; } }) @@ -425,11 +429,11 @@ export class ItemListLayout extends React.Component { onClick={prevDefault(() => store.toggleSelectionAll(enabledItems))} /> )} - {renderTableHeader.map((cellProps, index) => { - if (!this.isHiddenColumn(cellProps)) { - return ; - } - })} + {renderTableHeader.map((cellProps, index) => ( + this.showColumn(cellProps) && ( + + ) + ))} {isConfigurable && this.renderColumnVisibilityMenu()} @@ -473,34 +477,14 @@ export class ItemListLayout extends React.Component { ); } - @computed get hiddenColumns() { - return UserStore.getInstance().getHiddenTableColumns(this.props.tableId); - } + showColumn({ id: columnId, showWithColumn }: TableCellProps): boolean { + const { tableId, isConfigurable } = this.props; - isHiddenColumn({ id: columnId, showWithColumn }: TableCellProps): boolean { - if (!this.props.isConfigurable) { - return false; - } - - return this.hiddenColumns.has(columnId) || ( - showWithColumn && this.hiddenColumns.has(showWithColumn) - ); - } - - updateColumnVisibility({ id: columnId }: TableCellProps, isVisible: boolean) { - const hiddenColumns = new Set(this.hiddenColumns); - - if (!isVisible) { - hiddenColumns.add(columnId); - } else { - hiddenColumns.delete(columnId); - } - - UserStore.getInstance().setHiddenTableColumns(this.props.tableId, hiddenColumns); + return !isConfigurable || !UserStore.getInstance().isTableColumnHidden(tableId, columnId, showWithColumn); } renderColumnVisibilityMenu() { - const { renderTableHeader } = this.props; + const { renderTableHeader, tableId } = this.props; return ( @@ -509,8 +493,8 @@ export class ItemListLayout extends React.Component { `} - value={!this.isHiddenColumn(cellProps)} - onChange={isVisible => this.updateColumnVisibility(cellProps, isVisible)} + value={this.showColumn(cellProps)} + onChange={() => UserStore.getInstance().toggleTableColumnVisibility(tableId, cellProps.id)} /> ) diff --git a/src/renderer/components/locale-date/locale-date.tsx b/src/renderer/components/locale-date/locale-date.tsx index 42223bbf69..f8dcd19ef8 100644 --- a/src/renderer/components/locale-date/locale-date.tsx +++ b/src/renderer/components/locale-date/locale-date.tsx @@ -10,9 +10,8 @@ interface Props { @observer export class LocaleDate extends React.Component { render() { - const { preferences } = UserStore.getInstance(); const { date } = this.props; - return <>{moment.tz(date, preferences.localeTimezone).format()}; + return moment.tz(date, UserStore.getInstance().localeTimezone).format(); } } diff --git a/src/renderer/theme.store.ts b/src/renderer/theme.store.ts index c2e760869b..70c4d7d7b5 100644 --- a/src/renderer/theme.store.ts +++ b/src/renderer/theme.store.ts @@ -37,7 +37,7 @@ export class ThemeStore extends Singleton { } @computed get activeThemeId(): string { - return UserStore.getInstance().preferences.colorTheme; + return UserStore.getInstance().colorTheme; } @computed get activeTheme(): Theme {