/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import { app } from "electron"; import { action, computed, observable, reaction, makeObservable, isObservableArray, isObservableSet, isObservableMap } from "mobx"; import { BaseStore } from "../base-store"; import migrations from "../../migrations/user-store"; import { kubeConfigDefaultPath } from "../kube-helpers"; import { getOrInsertSet, toggle, toJS, object } from "../../renderer/utils"; import { DESCRIPTORS } from "./preferences-helpers"; import type { UserPreferencesModel, StoreType } from "./preferences-helpers"; import logger from "../../main/logger"; import type { SelectedUpdateChannel } from "../application-update/selected-update-channel/selected-update-channel.injectable"; import type { ReleaseChannel } from "../application-update/update-channels"; export interface UserStoreModel { lastSeenAppVersion: string; preferences: UserPreferencesModel; } interface Dependencies { readonly selectedUpdateChannel: SelectedUpdateChannel; } export class UserStore extends BaseStore /* implements UserStoreFlatModel (when strict null is enabled) */ { readonly displayName = "UserStore"; constructor(private readonly dependencies: Dependencies) { super({ configName: "lens-user-store", migrations, }); makeObservable(this); } @observable lastSeenAppVersion = "0.0.0"; /** * used in add-cluster page for providing context * @deprecated No longer used */ @observable kubeConfigPath = kubeConfigDefaultPath; /** * @deprecated No longer used */ @observable seenContexts = observable.set(); /** * @deprecated No longer used */ @observable newContexts = observable.set(); @observable allowErrorReporting!: StoreType; @observable allowUntrustedCAs!: StoreType; @observable colorTheme!: StoreType; @observable terminalTheme!: StoreType; @observable localeTimezone!: StoreType; @observable downloadMirror!: StoreType; @observable httpsProxy!: StoreType; @observable shell!: StoreType; @observable downloadBinariesPath!: StoreType; @observable kubectlBinariesPath!: StoreType; @observable terminalCopyOnSelect!: StoreType; @observable terminalConfig!: StoreType; @observable extensionRegistryUrl!: StoreType; /** * Download kubectl binaries matching cluster version */ @observable downloadKubectlBinaries!: StoreType; /** * Whether the application should open itself at login. */ @observable openAtLogin!: StoreType; /** * The column IDs under each configurable table ID that have been configured * to not be shown */ @observable hiddenTableColumns!: StoreType; /** * Monaco editor configs */ @observable editorConfiguration!: StoreType; /** * The set of file/folder paths to be synced */ @observable syncKubeconfigEntries!: StoreType; @computed get resolvedShell(): string | undefined { return this.shell || process.env.SHELL || process.env.PTYSHELL; } startMainReactions() { // open at system start-up reaction(() => this.openAtLogin, openAtLogin => { app.setLoginItemSettings({ openAtLogin, openAsHidden: true, args: ["--hidden"], }); }, { fireImmediately: true, }); } /** * 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 | undefined)[]): boolean { if (columnIds.length === 0) { return false; } const config = this.hiddenTableColumns.get(tableId); if (!config) { return false; } return columnIds.some(columnId => columnId && config.has(columnId)); } /** * Toggles the hidden configuration of a table's column */ toggleTableColumnVisibility(tableId: string, columnId: string) { toggle(getOrInsertSet(this.hiddenTableColumns, tableId), columnId); } @action resetTheme() { this.colorTheme = DESCRIPTORS.colorTheme.fromStore(undefined); } @action protected fromStore({ lastSeenAppVersion, preferences }: Partial = {}) { logger.debug("UserStore.fromStore()", { lastSeenAppVersion, preferences }); if (lastSeenAppVersion) { this.lastSeenAppVersion = lastSeenAppVersion; } for (const [key, { fromStore }] of object.entries(DESCRIPTORS)) { const curVal = this[key]; const newVal = fromStore((preferences)?.[key] as never) as never; if (isObservableArray(curVal)) { curVal.replace(newVal); } else if (isObservableSet(curVal) || isObservableMap(curVal)) { curVal.replace(newVal); } else { this[key] = newVal; } } // TODO: Switch to action-based saving instead saving stores by reaction if (preferences?.updateChannel) { this.dependencies.selectedUpdateChannel.setValue(preferences?.updateChannel as ReleaseChannel); } } toJSON(): UserStoreModel { const preferences = object.fromEntries( object.entries(DESCRIPTORS) .map(([key, { toStore }]) => [key, toStore(this[key] as never)]), ) as UserPreferencesModel; return toJS({ lastSeenAppVersion: this.lastSeenAppVersion, preferences: { ...preferences, updateChannel: this.dependencies.selectedUpdateChannel.value.get().id, }, }); } }