diff --git a/integration/__tests__/app-preferences.tests.ts b/integration/__tests__/app-preferences.tests.ts index 6c0c8fd65b..e5cca7c19c 100644 --- a/integration/__tests__/app-preferences.tests.ts +++ b/integration/__tests__/app-preferences.tests.ts @@ -24,8 +24,8 @@ describe("preferences page tests", () => { await app.evaluate(async ({ app }) => { await app.applicationMenu - .getMenuItemById(process.platform === "darwin" ? "root" : "file") - .submenu.getMenuItemById("preferences") + .getMenuItemById(process.platform === "darwin" ? "mac" : "file") + .submenu.getMenuItemById("navigate-to-preferences") .click(); }); }, 10*60*1000); @@ -37,7 +37,7 @@ describe("preferences page tests", () => { it('shows "preferences" and can navigate through the tabs', async () => { const pages = [ { - id: "application", + id: "app", header: "Application", }, { @@ -51,8 +51,8 @@ describe("preferences page tests", () => { ]; for (const { id, header } of pages) { - await window.click(`[data-testid=tab-link-for-${id}]`); - await window.waitForSelector(`[data-testid=${id}-header] >> text=${header}`); + await window.click(`[data-preference-tab-link-test=${id}]`); + await window.waitForSelector(`[data-preference-page-title-test] >> text=${header}`); } }, 10*60*1000); diff --git a/integration/__tests__/command-palette.tests.ts b/integration/__tests__/command-palette.tests.ts index 7b4d675ef4..670a64c679 100644 --- a/integration/__tests__/command-palette.tests.ts +++ b/integration/__tests__/command-palette.tests.ts @@ -10,7 +10,7 @@ describe("Lens command palette", () => { let window: Page; let cleanup: undefined | (() => Promise); let app: ElectronApplication; - + beforeEach(async () => { ({ window, cleanup, app } = await utils.start()); await utils.clickWelcomeButton(window); @@ -25,7 +25,7 @@ describe("Lens command palette", () => { await app.evaluate(async ({ app }) => { await app.applicationMenu ?.getMenuItemById("view") - ?.submenu?.getMenuItemById("command-palette") + ?.submenu?.getMenuItemById("open-command-palette") ?.click(); }); await window.waitForSelector(".Select__option >> text=Hotbar: Switch"); diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index a90e03c75f..eeadbc1e6c 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -34,7 +34,7 @@ import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable"; import releaseChannelInjectable from "../vars/release-channel.injectable"; -import defaultUpdateChannelInjectable from "../application-update/selected-update-channel/default-update-channel.injectable"; +import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable"; console = new Console(stdout, stderr); diff --git a/src/common/front-end-routing/routes/preferences/app/app-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/app/app-preferences-route.injectable.ts deleted file mode 100644 index b4dfe5c05b..0000000000 --- a/src/common/front-end-routing/routes/preferences/app/app-preferences-route.injectable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; - -const appPreferencesRouteInjectable = getInjectable({ - id: "app-preferences-route", - - instantiate: () => ({ - path: "/preferences/app", - clusterFrame: false, - isEnabled: computed(() => true), - }), - - injectionToken: frontEndRouteInjectionToken, -}); - -export default appPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/app/navigate-to-app-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/app/navigate-to-app-preferences.injectable.ts deleted file mode 100644 index 4f83d47dec..0000000000 --- a/src/common/front-end-routing/routes/preferences/app/navigate-to-app-preferences.injectable.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import appPreferencesRouteInjectable from "./app-preferences-route.injectable"; -import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; - -const navigateToAppPreferencesInjectable = getInjectable({ - id: "navigate-to-app-preferences", - - instantiate: (di) => { - const navigateToRoute = di.inject(navigateToRouteInjectionToken); - const route = di.inject(appPreferencesRouteInjectable); - - return () => navigateToRoute(route); - }, -}); - -export default navigateToAppPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/extension/extension-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/extension/extension-preferences-route.injectable.ts deleted file mode 100644 index 38d25b4114..0000000000 --- a/src/common/front-end-routing/routes/preferences/extension/extension-preferences-route.injectable.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; -import type { Route } from "../../../front-end-route-injection-token"; - -interface ExtensionPreferenceRouteParams { - extensionId: string; - tabId?: string; -} - -const extensionPreferencesRouteInjectable = getInjectable({ - id: "extension-preferences-route", - - instantiate: (): Route => ({ - path: "/preferences/extension/:extensionId/:tabId?", - clusterFrame: false, - isEnabled: computed(() => true), - }), - - injectionToken: frontEndRouteInjectionToken, -}); - -export default extensionPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/extension/navigate-to-extension-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/extension/navigate-to-extension-preferences.injectable.ts deleted file mode 100644 index 085694a578..0000000000 --- a/src/common/front-end-routing/routes/preferences/extension/navigate-to-extension-preferences.injectable.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import extensionPreferencesRouteInjectable from "./extension-preferences-route.injectable"; -import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; - -const navigateToExtensionPreferencesInjectable = getInjectable({ - id: "navigate-to-extension-preferences", - - instantiate: (di) => { - const navigateToRoute = di.inject(navigateToRouteInjectionToken); - const route = di.inject(extensionPreferencesRouteInjectable); - - return (extensionId: string, tabId?: string) => navigateToRoute(route, { - parameters: { - extensionId, - tabId, - }, - withoutAffectingBackButton: true, - }); - }, -}); - -export default navigateToExtensionPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/kubernetes/kubernetes-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/kubernetes/kubernetes-preferences-route.injectable.ts deleted file mode 100644 index 7b3479c7fa..0000000000 --- a/src/common/front-end-routing/routes/preferences/kubernetes/kubernetes-preferences-route.injectable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; - -const kubernetesPreferencesRouteInjectable = getInjectable({ - id: "kubernetes-preferences-route", - - instantiate: () => ({ - path: "/preferences/kubernetes", - clusterFrame: false, - isEnabled: computed(() => true), - }), - - injectionToken: frontEndRouteInjectionToken, -}); - -export default kubernetesPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/navigate-to-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/navigate-to-preferences.injectable.ts deleted file mode 100644 index 545751d7c3..0000000000 --- a/src/common/front-end-routing/routes/preferences/navigate-to-preferences.injectable.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import navigateToAppPreferencesInjectable from "./app/navigate-to-app-preferences.injectable"; - -const navigateToPreferencesInjectable = getInjectable({ - id: "navigate-to-preferences", - - instantiate: (di) => di.inject(navigateToAppPreferencesInjectable), -}); - -export default navigateToPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/proxy/proxy-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/proxy/proxy-preferences-route.injectable.ts deleted file mode 100644 index 322e711225..0000000000 --- a/src/common/front-end-routing/routes/preferences/proxy/proxy-preferences-route.injectable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; - -const proxyPreferencesRouteInjectable = getInjectable({ - id: "proxy-preferences-route", - - instantiate: () => ({ - path: "/preferences/proxy", - clusterFrame: false, - isEnabled: computed(() => true), - }), - - injectionToken: frontEndRouteInjectionToken, -}); - -export default proxyPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/telemetry/telemetry-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/telemetry/telemetry-preferences-route.injectable.ts deleted file mode 100644 index 2ae9f14acd..0000000000 --- a/src/common/front-end-routing/routes/preferences/telemetry/telemetry-preferences-route.injectable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; - -const telemetryPreferencesRouteInjectable = getInjectable({ - id: "telemetry-preferences-route", - - instantiate: () => ({ - path: "/preferences/telemetry", - clusterFrame: false, - isEnabled: computed(() => true), - }), - - injectionToken: frontEndRouteInjectionToken, -}); - -export default telemetryPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/terminal/terminal-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/terminal/terminal-preferences-route.injectable.ts deleted file mode 100644 index c077c45bab..0000000000 --- a/src/common/front-end-routing/routes/preferences/terminal/terminal-preferences-route.injectable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; - -const terminalPreferencesRouteInjectable = getInjectable({ - id: "terminal-preferences-route", - - instantiate: () => ({ - path: "/preferences/terminal", - clusterFrame: false, - isEnabled: computed(() => true), - }), - - injectionToken: frontEndRouteInjectionToken, -}); - -export default terminalPreferencesRouteInjectable; diff --git a/src/common/log-error.global-override-for-injectable.ts b/src/common/log-error.global-override-for-injectable.ts new file mode 100644 index 0000000000..e3a03c2802 --- /dev/null +++ b/src/common/log-error.global-override-for-injectable.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getGlobalOverrideForFunction } from "./test-utils/get-global-override-for-function"; +import logErrorInjectable from "./log-error.injectable"; + +// Note: this should remain as it is, and throw if called. Logging error is something +// that cannot happen without a unit test explicitly causing it. It cannot be allowed +// to happen without author of unit test knowing it. +export default getGlobalOverrideForFunction(logErrorInjectable); diff --git a/src/common/test-utils/get-global-override-for-function.ts b/src/common/test-utils/get-global-override-for-function.ts new file mode 100644 index 0000000000..238ee5621a --- /dev/null +++ b/src/common/test-utils/get-global-override-for-function.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { Injectable } from "@ogre-tools/injectable"; +import { getGlobalOverride } from "./get-global-override"; +import { camelCase } from "lodash/fp"; + +export const getGlobalOverrideForFunction = ( + injectable: Injectable, +) => + getGlobalOverride(injectable, () => (...args: any[]) => { + console.warn( + `Tried to invoke a function "${injectable.id}" without override. The args were:`, + args, + ); + + throw new Error( + `Tried to invoke a function "${ + injectable.id + }" without override. Add eg. "di.override(${camelCase( + injectable.id, + )}Mock)" to the unit test interested in this.`, + ); + }); diff --git a/src/common/test-utils/use-fake-time.ts b/src/common/test-utils/use-fake-time.ts index 77cd385b4b..2a0956cebb 100644 --- a/src/common/test-utils/use-fake-time.ts +++ b/src/common/test-utils/use-fake-time.ts @@ -16,7 +16,7 @@ export const advanceFakeTime = (milliseconds: number) => { }); }; -export const useFakeTime = (dateTime: string) => { +export const useFakeTime = (dateTime = "2015-10-21T07:28:00Z") => { usingFakeTime = true; jest.useFakeTimers(); diff --git a/src/common/user-store/user-store.injectable.ts b/src/common/user-store/user-store.injectable.ts index ffd2310886..5b59a47df3 100644 --- a/src/common/user-store/user-store.injectable.ts +++ b/src/common/user-store/user-store.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { UserStore } from "./user-store"; -import selectedUpdateChannelInjectable from "../application-update/selected-update-channel/selected-update-channel.injectable"; +import selectedUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable"; const userStoreInjectable = getInjectable({ id: "user-store", diff --git a/src/common/user-store/user-store.ts b/src/common/user-store/user-store.ts index 8a8ff6735c..0a5874ad87 100644 --- a/src/common/user-store/user-store.ts +++ b/src/common/user-store/user-store.ts @@ -11,8 +11,10 @@ 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"; + +// TODO: Remove coupling with Feature +import type { SelectedUpdateChannel } from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable"; +import type { ReleaseChannel } from "../../features/application-update/common/update-channels"; export interface UserStoreModel { lastSeenAppVersion: string; diff --git a/src/common/utils/add-separator/add-separator.test.ts b/src/common/utils/add-separator/add-separator.test.ts new file mode 100644 index 0000000000..8a45b45739 --- /dev/null +++ b/src/common/utils/add-separator/add-separator.test.ts @@ -0,0 +1,53 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { addSeparator } from "./add-separator"; + +describe("add-separator", () => { + it("given multiple items, adds separators", () => { + const items = ["first", "second", "third"]; + + const actual = addSeparator((left, right) => `separator-between-${left}-and-${right}`, items); + + expect(actual).toEqual([ + "first", + "separator-between-first-and-second", + "second", + "separator-between-second-and-third", + "third", + ]); + }); + + it("given multiple items including falsy ones, adds separators", () => { + const items = [false, undefined, null, NaN]; + + const actual = addSeparator((left, right) => `separator-between-${left}-and-${right}`, items); + + expect(actual).toEqual([ + false, + "separator-between-false-and-undefined", + undefined, + "separator-between-undefined-and-null", + null, + "separator-between-null-and-NaN", + NaN, + ]); + }); + + it("given no items, does not add separator", () => { + const items: any[] = []; + + const actual = addSeparator(() => "separator", items); + + expect(actual).toEqual([]); + }); + + it("given one item, does not add separator", () => { + const items = ["first"]; + + const actual = addSeparator(() => "separator", items); + + expect(actual).toEqual(["first"]); + }); +}); diff --git a/src/common/utils/add-separator/add-separator.ts b/src/common/utils/add-separator/add-separator.ts new file mode 100644 index 0000000000..6fd6b14642 --- /dev/null +++ b/src/common/utils/add-separator/add-separator.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export type GetSeparator = (left: Item, right: Item) => Separator; + +export const addSeparator = ( + getSeparator: GetSeparator, + items: Item[], +) => items.flatMap(toSeparatedTupleUsing(getSeparator)); + +const toSeparatedTupleUsing = + (getSeparator: GetSeparator) => + (leftItem: Item, index: number, arr: Item[]) => { + const itemIsLast = arr.length === index + 1; + + if (itemIsLast) { + return [leftItem]; + } + + const rightItem = arr[index + 1]; + const separator = getSeparator(leftItem, rightItem); + + return [leftItem, separator]; + }; diff --git a/src/common/utils/composable-responsibilities/discriminable/discriminable.ts b/src/common/utils/composable-responsibilities/discriminable/discriminable.ts new file mode 100644 index 0000000000..7976004ee6 --- /dev/null +++ b/src/common/utils/composable-responsibilities/discriminable/discriminable.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions +export interface Discriminable { readonly kind: T } + +// Note: this will fail at transpilation time, if all kinds are not instructed in switch/case. +// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking +export const checkThatAllDiscriminablesAreExhausted = (value: T) => { + const _exhaustiveCheck: never = value; + + return new Error( + `Tried to exhaust discriminables, but no instructions were found for ${(_exhaustiveCheck as any).kind}`, + ); +}; diff --git a/src/common/utils/composable-responsibilities/labelable/labelable.ts b/src/common/utils/composable-responsibilities/labelable/labelable.ts new file mode 100644 index 0000000000..e0f2d98c21 --- /dev/null +++ b/src/common/utils/composable-responsibilities/labelable/labelable.ts @@ -0,0 +1,7 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +export interface Labelable { + readonly label: string; +} diff --git a/src/common/utils/composable-responsibilities/orderable/orderable.ts b/src/common/utils/composable-responsibilities/orderable/orderable.ts new file mode 100644 index 0000000000..e0425cc0a7 --- /dev/null +++ b/src/common/utils/composable-responsibilities/orderable/orderable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { sortBy } from "lodash/fp"; + +export interface Orderable { + readonly orderNumber: number; +} + +export type MaybeOrderable = Orderable | object; + +export const orderByOrderNumber = (maybeOrderables: T[]) => + sortBy( + (orderable) => + "orderNumber" in orderable + ? orderable.orderNumber + : Number.MAX_SAFE_INTEGER, + maybeOrderables, + ); diff --git a/src/common/utils/composable-responsibilities/showable/showable.ts b/src/common/utils/composable-responsibilities/showable/showable.ts new file mode 100644 index 0000000000..ad8e2ed25b --- /dev/null +++ b/src/common/utils/composable-responsibilities/showable/showable.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { IComputedValue } from "mobx"; +import { isBoolean } from "../../type-narrowing"; + +export interface Showable { + readonly isShown: IComputedValue | boolean; +} + +export type MaybeShowable = Showable | object; + +export const isShown = (showable: MaybeShowable) => { + if (!("isShown" in showable)) { + return true; + } + + if (showable.isShown === undefined) { + return true; + } + + if (isBoolean(showable.isShown)) { + return showable.isShown; + } + + return showable.isShown.get(); +}; diff --git a/src/common/utils/composite/composite-has-descendant/composite-has-descendant.test.ts b/src/common/utils/composite/composite-has-descendant/composite-has-descendant.test.ts new file mode 100644 index 0000000000..0b7f07bf2d --- /dev/null +++ b/src/common/utils/composite/composite-has-descendant/composite-has-descendant.test.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { Composite } from "../get-composite/get-composite"; +import { compositeHasDescendant } from "./composite-has-descendant"; +import { getCompositeFor } from "../get-composite/get-composite"; + +describe("composite-has-descendant, given composite with children and grand children", () => { + let composite: Composite<{ id: string; parentId?: string }>; + + beforeEach(() => { + const items = [ + { id: "some-root-id", parentId: undefined }, + { id: "some-child-item", parentId: "some-root-id" }, + + { + id: "some-grand-child-item", + parentId: "some-child-item", + }, + ]; + + const getComposite = getCompositeFor<{ + id: string; + parentId?: string; + }>({ + rootId: "some-root-id", + getId: (x) => x.id, + getParentId: (x) => x.parentId, + }); + + composite = getComposite(items); + }); + + it("has a child as descendant", () => { + const actual = compositeHasDescendant( + (referenceComposite) => referenceComposite.value.id === "some-child-item", + )(composite); + + expect(actual).toBe(true); + }); + + it("has a grand child as descendant", () => { + const actual = compositeHasDescendant( + (referenceComposite) => + referenceComposite.value.id === "some-grand-child-item", + )(composite); + + expect(actual).toBe(true); + }); + + it("does not have an unrelated descendant", () => { + const actual = compositeHasDescendant( + (referenceComposite) => + referenceComposite.value.id === "some-unknown-item", + )(composite); + + expect(actual).toBe(false); + }); +}); diff --git a/src/common/utils/composite/composite-has-descendant/composite-has-descendant.ts b/src/common/utils/composite/composite-has-descendant/composite-has-descendant.ts new file mode 100644 index 0000000000..030f79ad74 --- /dev/null +++ b/src/common/utils/composite/composite-has-descendant/composite-has-descendant.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { Composite } from "../get-composite/get-composite"; + +const compositeHasDescendant = ( + predicate: (referenceComposite: Composite) => boolean, +) => { + const _compositeHasDescendant = (composite: Composite): boolean => + predicate(composite) || + !!composite.children.find((childComposite) => + _compositeHasDescendant(childComposite), + ); + + return _compositeHasDescendant; +}; + +export { compositeHasDescendant }; diff --git a/src/common/utils/composite/find-composite/find-composite.test.ts b/src/common/utils/composite/find-composite/find-composite.test.ts new file mode 100644 index 0000000000..5b4147070e --- /dev/null +++ b/src/common/utils/composite/find-composite/find-composite.test.ts @@ -0,0 +1,91 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { Composite } from "../get-composite/get-composite"; +import { findComposite } from "./find-composite"; +import { getCompositeFor } from "../get-composite/get-composite"; + +describe("find-composite", () => { + let composite: Composite<{ id: string; parentId?: string }>; + + beforeEach(() => { + const getComposite = getCompositeFor<{ + id: string; + parentId?: string; + }>({ + rootId: "some-root-id", + getId: (x) => x.id, + getParentId: (x) => x.parentId, + }); + + composite = getComposite([ + { id: "some-root-id" }, + { id: "some-child-id", parentId: "some-root-id" }, + { id: "some-grandchild-id", parentId: "some-child-id" }, + { id: "some-other-grandchild-id", parentId: "some-child-id" }, + ]); + }); + + it("when finding root using path, does so", () => { + const actual = findComposite("some-root-id")(composite); + + expect(actual.id).toBe("some-root-id"); + }); + + it("when finding child using path, does so", () => { + const actual = findComposite("some-root-id", "some-child-id")(composite); + + expect(actual.id).toBe("some-child-id"); + }); + + it("when finding grandchild using path, does so", () => { + const actual = findComposite( + "some-root-id", + "some-child-id", + "some-grandchild-id", + )(composite); + + expect(actual.id).toBe("some-grandchild-id"); + }); + + it("when finding with non existing leaf-level path, throws", () => { + expect(() => { + findComposite( + "some-root-id", + "some-child-id", + "some-non-existing-grandchild-id", + )(composite); + }).toThrow(`Tried to find 'some-root-id -> some-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing. + +Node 'some-root-id -> some-child-id' had only following children: +some-grandchild-id +some-other-grandchild-id`); + }); + + it("when finding with non-existing mid-level path, throws", () => { + expect(() => { + findComposite( + "some-root-id", + "some-non-existing-child-id", + "some-non-existing-grandchild-id", + )(composite); + }).toThrow(`Tried to find 'some-root-id -> some-non-existing-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing. + +Node 'some-root-id' had only following children: +some-child-id`); + }); + + it("when finding with non-existing root-level path, throws", () => { + expect(() => { + findComposite( + "some-non-existing-root-id", + "some-non-existing-child-id", + "some-non-existing-grandchild-id", + )(composite); + }).toThrow(`Tried to find 'some-non-existing-root-id -> some-non-existing-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing. + +Node 'some-root-id' had only following children: +some-child-id`); + }); +}); diff --git a/src/common/utils/composite/find-composite/find-composite.ts b/src/common/utils/composite/find-composite/find-composite.ts new file mode 100644 index 0000000000..71dcf5baa3 --- /dev/null +++ b/src/common/utils/composite/find-composite/find-composite.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { Composite } from "../get-composite/get-composite"; + +const _findComposite = (currentLeftIds: string[], currentId: string, currentRightIds: string[], composite: Composite): Composite => { + const [nextId, ...nextRightIds] = currentRightIds; + const nextLeftIds = [...currentLeftIds, currentId]; + + if (currentRightIds.length === 0 && composite.id === currentId) { + return composite; + } + + const foundChildComposite = composite.children.find((child) => child.id === nextId); + + if (foundChildComposite) { + return _findComposite(nextLeftIds, nextId, nextRightIds, foundChildComposite); + } + + const fullPathString = [...currentLeftIds, currentId, ...currentRightIds].join(" -> "); + + throw new Error(`Tried to find '${fullPathString}' from a composite, but found nothing. + +Node '${[...currentLeftIds, composite.id].join(" -> ")}' had only following children: +${composite.children.map((child) => child.id).join("\n")}`); +}; + +export const findComposite = + (...path: string[]) => + (composite: Composite): Composite => { + const [currentId, ...rightIds] = path; + const leftIds: string[] = []; + + return _findComposite(leftIds, currentId, rightIds, composite); + }; diff --git a/src/common/utils/composite/get-composite-normalization/get-composite-normalization.test.ts b/src/common/utils/composite/get-composite-normalization/get-composite-normalization.test.ts new file mode 100644 index 0000000000..ac88973251 --- /dev/null +++ b/src/common/utils/composite/get-composite-normalization/get-composite-normalization.test.ts @@ -0,0 +1,53 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getCompositeNormalization } from "./get-composite-normalization"; +import { getCompositeFor } from "../get-composite/get-composite"; + + +describe("get-composite-normalization", () => { + it("given a composite, flattens it to paths and composites", () => { + const someRootItem = { + id: "some-root-id", + parentId: undefined, + }; + + const someItem = { + id: "some-id", + parentId: "some-root-id", + }; + + const someNestedItem = { + id: "some-child-id", + parentId: "some-id", + }; + + const items = [someRootItem, someItem, someNestedItem]; + + const getComposite = getCompositeFor<{ + id: string; + parentId?: string; + orderNumber?: number; + }>({ + rootId: "some-root-id", + getId: (x) => x.id, + getParentId: (x) => x.parentId, + }); + + const composite = getComposite(items); + + const actual = getCompositeNormalization(composite); + + expect(actual).toEqual([ + [["some-root-id"], expect.objectContaining({ value: someRootItem })], + + [["some-root-id", "some-id"], expect.objectContaining({ value: someItem })], + + [ + ["some-root-id", "some-id", "some-child-id"], + expect.objectContaining({ value: someNestedItem }), + ], + ]); + }); +}); diff --git a/src/common/utils/composite/get-composite-normalization/get-composite-normalization.ts b/src/common/utils/composite/get-composite-normalization/get-composite-normalization.ts new file mode 100644 index 0000000000..eb5628171a --- /dev/null +++ b/src/common/utils/composite/get-composite-normalization/get-composite-normalization.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { Composite } from "../get-composite/get-composite"; + +export const getCompositeNormalization = (composite: Composite) => { + const _normalizeComposite = ( + composite: Composite, + previousPath: string[] = [], + ): (readonly [path: string[], composite: Composite])[] => { + const currentPath = [...previousPath, composite.id]; + + const pathAndCompositeTuple = [currentPath, composite] as const; + + return [ + pathAndCompositeTuple, + + ...composite.children.flatMap((child) => + _normalizeComposite(child, currentPath), + ), + ]; + }; + + return _normalizeComposite(composite); +}; diff --git a/src/common/utils/composite/get-composite-paths/get-composite-paths.test.ts b/src/common/utils/composite/get-composite-paths/get-composite-paths.test.ts new file mode 100644 index 0000000000..079f7e1b83 --- /dev/null +++ b/src/common/utils/composite/get-composite-paths/get-composite-paths.test.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getCompositePaths } from "./get-composite-paths"; +import { sortBy } from "lodash/fp"; +import { getCompositeFor } from "../get-composite/get-composite"; + +describe("get-composite-paths", () => { + it("given composite with transformed children, returns paths of transformed children in hierarchical order", () => { + const someRootItem = { + id: "some-root-id", + }; + + const someChildItem1 = { + id: "some-child-id-1", + parentId: "some-root-id", + orderNumber: 1, + }; + + const someChildItem2 = { + id: "some-child-id-2", + parentId: "some-root-id", + orderNumber: 2, + }; + + const someGrandchildItem1 = { + id: "some-grandchild-id-1", + parentId: "some-child-id-1", + orderNumber: 1, + }; + + const someGrandchildItem2 = { + id: "some-grandchild-id-2", + parentId: "some-child-id-1", + orderNumber: 2, + }; + + const items = [ + someRootItem, + // Note: not in order yet. + someChildItem2, + someChildItem1, + someGrandchildItem2, + someGrandchildItem1, + ]; + + const getComposite = getCompositeFor<{ + id: string; + parentId?: string; + orderNumber?: number; + }>({ + rootId: "some-root-id", + getId: (x) => x.id, + getParentId: (x) => x.parentId, + transformChildren: children => sortBy(child => child.orderNumber, children), + }); + + const composite = getComposite(items); + + const actual = getCompositePaths(composite); + + expect(actual).toEqual([ + ["some-root-id"], + ["some-root-id", "some-child-id-1"], + ["some-root-id", "some-child-id-1", "some-grandchild-id-1"], + ["some-root-id", "some-child-id-1", "some-grandchild-id-2"], + ["some-root-id", "some-child-id-2"], + ]); + }); +}); diff --git a/src/common/utils/composite/get-composite-paths/get-composite-paths.ts b/src/common/utils/composite/get-composite-paths/get-composite-paths.ts new file mode 100644 index 0000000000..2b97467431 --- /dev/null +++ b/src/common/utils/composite/get-composite-paths/get-composite-paths.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { pipeline } from "@ogre-tools/fp"; +import { map } from "lodash/fp"; +import type { Composite } from "../get-composite/get-composite"; +import { getCompositeNormalization } from "../get-composite-normalization/get-composite-normalization"; + +export const getCompositePaths = ( + composite: Composite, +): string[][] => pipeline(composite, getCompositeNormalization, map(([path]) => path)); diff --git a/src/common/utils/composite/get-composite/get-composite.test.ts b/src/common/utils/composite/get-composite/get-composite.test.ts new file mode 100644 index 0000000000..594d0f3d01 --- /dev/null +++ b/src/common/utils/composite/get-composite/get-composite.test.ts @@ -0,0 +1,363 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { Composite } from "./get-composite"; +import { getCompositePaths } from "../get-composite-paths/get-composite-paths"; +import { sortBy } from "lodash/fp"; +import { getCompositeFor } from "./get-composite"; + +interface SomeItem { + id: string; + parentId?: string; + orderNumber?: number; +} + +describe("get-composite", () => { + it("given items and an explicit root id, creates a composite", () => { + const someRootItem = { + id: "some-root-id", + someProperty: "some-root-content", + }; + + const someIrrelevantRootItem = { + id: "some-irrelevant-root-id", + someProperty: "some-other-root-content", + }; + + const someItem = { + id: "some-id", + parentId: "some-root-id", + someProperty: "some-content", + }; + + const someNestedItem = { + id: "some-nested-id", + parentId: "some-id", + someProperty: "some-nested-content", + }; + + const items = [someRootItem, someIrrelevantRootItem, someItem, someNestedItem]; + + const getComposite = getCompositeFor({ + rootId: "some-root-id", + getId: (x) => x.id, + getParentId: (x) => x.parentId, + }); + + const composite = getComposite(items); + + expect(composite).toEqual({ + id: "some-root-id", + value: someRootItem, + + children: [ + { + id: "some-id", + parentId: "some-root-id", + value: someItem, + + children: [ + { + id: "some-nested-id", + parentId: "some-id", + value: someNestedItem, + children: [], + }, + ], + }, + ], + }); + }); + + it("given items and implicit root, creates a composite", () => { + const someRootItem = { + id: "some-root-id", + someProperty: "some-root-content", + // Notice: no "parentId" makes this the implicit root. + parentId: undefined, + }; + + const someItem = { + id: "some-id", + parentId: "some-root-id", + someProperty: "some-content", + }; + + const someNestedItem = { + id: "some-nested-id", + parentId: "some-id", + someProperty: "some-nested-content", + }; + + const items = [someRootItem, someItem, someNestedItem]; + + const getComposite = getCompositeFor({ + // Notice: no root id + // rootId: "some-root-id", + getId: (x) => x.id, + getParentId: (x) => x.parentId, + }); + + const composite = getComposite(items); + + expect(composite).toEqual({ + id: "some-root-id", + value: someRootItem, + + children: [ + { + id: "some-id", + parentId: "some-root-id", + value: someItem, + + children: [ + { + id: "some-nested-id", + parentId: "some-id", + value: someNestedItem, + children: [], + }, + ], + }, + ], + }); + }); + + it("given items and an unspecified root id and multiple items without parent as root, throws", () => { + const someRootItem = { + id: "some-root-id", + // Notice: no "parentId" makes this a root. + parentId: undefined, + }; + + const someOtherRootItem = { + id: "some-other-root-id", + // Notice: no "parentId" makes also this a root. + parentId: undefined, + }; + + const items = [someRootItem, someOtherRootItem]; + + const getComposite = getCompositeFor({ + getId: (x) => x.id, + getParentId: (x) => x.parentId, + }); + + expect(() => { + getComposite(items); + }).toThrow( + 'Tried to get a composite, but multiple roots where encountered: "some-root-id", "some-other-root-id"', + ); + }); + + it("given non-unique ids, throws", () => { + const someItem = { + id: "some-id", + parentId: "irrelevant", + }; + + const someOtherItem = { + id: "some-id", + parentId: "irrelevant", + }; + + const items = [someItem, someOtherItem]; + + const getComposite = getCompositeFor({ + getId: (x) => x.id, + getParentId: (x) => x.parentId, + }); + + expect(() => { + getComposite(items); + }).toThrow( + 'Tried to get a composite but encountered non-unique ids: "some-id"', + ); + }); + + it("given items with missing parent ids, when creating composite without handling for unknown parents, throws", () => { + const someItem = { + id: "some-id", + parentId: undefined, + }; + + const someItemWithMissingParentId = { + id: "some-other-id", + parentId: "some-missing-id", + }; + + const items = [someItem, someItemWithMissingParentId]; + + const getComposite = getCompositeFor({ + getId: (x) => x.id, + getParentId: (x) => x.parentId, + }); + + expect(() => { + getComposite(items); + }).toThrow( + `Tried to get a composite but encountered missing parent ids: "some-missing-id". + +Available parent ids are: +"some-id", +"some-other-id"`, + ); + }); + + describe("given items with missing parents, when creating composite with handling for missing parents", () => { + let composite: Composite; + let handleMissingParentIdMock: jest.Mock; + + beforeEach(() => { + const someItem = { + id: "some-root-id", + }; + + const someItemWithMissingParentId = { + id: "some-orphan-id", + // Note: the item corresponding to this id does not exist, + // making this item have a "missing parent". + parentId: "some-missing-id", + }; + + const items = [someItem, someItemWithMissingParentId]; + + handleMissingParentIdMock = jest.fn(); + + const getComposite = getCompositeFor({ + getId: (x) => x.id, + getParentId: (x) => x.parentId, + handleMissingParentIds: handleMissingParentIdMock, + }); + + composite = getComposite(items); + }); + + it("creates composite without the orphan item, and without throwing", () => { + const paths = getCompositePaths(composite); + + expect(paths).toEqual([["some-root-id"]]); + }); + + it("handles the missing parent ids", () => { + expect(handleMissingParentIdMock).toHaveBeenCalledWith({ + missingParentIds: ["some-missing-id"], + availableParentIds: ["some-root-id", "some-orphan-id"], + }); + }); + }); + + it("given items with same id and parent id, throws", () => { + const someItem = { + id: "some-id", + parentId: "some-id", + }; + + const someRoot = { + id: "root", + parentId: undefined, + }; + + const items = [someItem, someRoot]; + + const getComposite = getCompositeFor({ + getId: (x) => x.id, + getParentId: (x) => x.parentId, + }); + + expect(() => { + getComposite(items); + }).toThrow( + 'Tried to get a composite, but found items with self as parent: "some-id"', + ); + }); + + it("given undefined ids, throws", () => { + const root = { + parentId: undefined, + id: "some-root", + }; + + const someItem = { + parentId: "some-root", + id: undefined, + }; + + const someOtherItem = { + parentId: "some-root", + id: undefined, + }; + + const items = [root, someItem, someOtherItem]; + + const getComposite = getCompositeFor({ + getId: (x) => x.id, + getParentId: (x) => x.parentId, + }); + + expect(() => { + getComposite(items); + }).toThrow("Tried to get a composite but encountered 2 undefined ids"); + }); + + it("given transformed children, creates a composite with transformed children", () => { + const someRootItem = { + id: "some-root-id", + orderNumber: 1, + }; + + const someItem1 = { + id: "some-id-1", + parentId: "some-root-id", + orderNumber: 1, + }; + + const someItem2 = { + id: "some-id-2", + parentId: "some-root-id", + orderNumber: 2, + }; + + const someChildItem1 = { + id: "some-child-id-1", + parentId: "some-id-1", + orderNumber: 1, + }; + + const someChildItem2 = { + id: "some-child-id-2", + parentId: "some-id-1", + orderNumber: 2, + }; + + const items = [ + someRootItem, + // Note: not in order yet. + someItem2, + someItem1, + someChildItem2, + someChildItem1, + ]; + + const getComposite = getCompositeFor({ + getId: (x) => x.id, + getParentId: (x) => x.parentId, + transformChildren: (things) => + sortBy((thing) => thing.orderNumber, things), + }); + + const composite = getComposite(items); + + const orderedPaths = getCompositePaths(composite); + + expect(orderedPaths).toEqual([ + ["some-root-id"], + ["some-root-id", "some-id-1"], + ["some-root-id", "some-id-1", "some-child-id-1"], + ["some-root-id", "some-id-1", "some-child-id-2"], + ["some-root-id", "some-id-2"], + ]); + }); +}); diff --git a/src/common/utils/composite/get-composite/get-composite.ts b/src/common/utils/composite/get-composite/get-composite.ts new file mode 100644 index 0000000000..7e499ad40a --- /dev/null +++ b/src/common/utils/composite/get-composite/get-composite.ts @@ -0,0 +1,147 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { pipeline } from "@ogre-tools/fp"; +import { + countBy, + filter, + toPairs, + nth, + map, + uniq, + without, + compact, + identity, +} from "lodash/fp"; + +export interface Composite { + id: string; + parentId: string | undefined; + value: T; + children: Composite[]; +} + +interface Configuration { + rootId?: string; + getId: (thing: T) => string; + getParentId: (thing: T) => string | undefined; + transformChildren?: (things: T[]) => T[]; + handleMissingParentIds?: (parentIdsForHandling: ParentIdsForHandling) => void; +} + +export const getCompositeFor = ({ + rootId = undefined, + getId, + getParentId, + transformChildren = identity, + handleMissingParentIds = throwMissingParentIds, +}: Configuration) => (source: T[]) => { + const undefinedIds = pipeline( + source, + filter((x) => getId(x) === undefined), + ); + + if (undefinedIds.length) { + throw new Error( + `Tried to get a composite but encountered ${undefinedIds.length} undefined ids`, + ); + } + + const selfReferencingIds = pipeline( + source, + filter((x) => getId(x) === getParentId(x)), + map(getId), + ); + + if (selfReferencingIds.length) { + throw new Error( + `Tried to get a composite, but found items with self as parent: "${selfReferencingIds.join( + '", ', + )}"`, + ); + } + + const duplicateIds = pipeline( + source, + countBy(getId), + toPairs, + filter(([, count]) => count > 1), + map(nth(0)), + ); + + if (duplicateIds.length) { + throw new Error( + `Tried to get a composite but encountered non-unique ids: "${duplicateIds + .map((x) => String(x)) + .join('", "')}"`, + ); + } + + const allIds = pipeline(source, map(getId)); + + const allParentIds = pipeline(source, map(getParentId), uniq, compact); + + const missingParentIds = without(allIds, allParentIds); + + if (missingParentIds.length) { + handleMissingParentIds({ missingParentIds, availableParentIds: allIds }); + } + + const toComposite = (thing: T): Composite => { + const thingId = getId(thing); + + return { + id: thingId, + parentId: getParentId(thing), + value: thing, + + children: pipeline( + source, + + filter((childThing) => { + const parentId = getParentId(childThing); + + return parentId === thingId; + }), + + transformChildren, + + map(toComposite), + ), + }; + }; + + const isRootId = rootId + ? (thing: T) => getId(thing) === rootId + : (thing: T) => getParentId(thing) === undefined; + + const roots = source.filter(isRootId); + + if (roots.length > 1) { + throw new Error( + `Tried to get a composite, but multiple roots where encountered: "${roots + .map(getId) + .join('", "')}"`, + ); + } + + return toComposite(roots[0]); + }; + +interface ParentIdsForHandling { + missingParentIds: string[]; + availableParentIds: string[]; +} + +const throwMissingParentIds = ({ + missingParentIds, + availableParentIds, +}: ParentIdsForHandling) => { + throw new Error( + `Tried to get a composite but encountered missing parent ids: "${missingParentIds.join( + '", "', + )}".\n\nAvailable parent ids are:\n"${availableParentIds.join('",\n"')}"`, + ); +}; diff --git a/src/common/utils/composite/interfaces.ts b/src/common/utils/composite/interfaces.ts new file mode 100644 index 0000000000..1c29a0e4ce --- /dev/null +++ b/src/common/utils/composite/interfaces.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export interface ParentOfChildComposite { + id: Id; +} + +export interface ChildOfParentComposite { + parentId: ParentId; +} + +export type RootComposite = + & { parentId: undefined } + & ParentOfChildComposite; diff --git a/src/common/utils/find-exactly-one/find-exactly-one.test.ts b/src/common/utils/find-exactly-one/find-exactly-one.test.ts new file mode 100644 index 0000000000..08dbb9ce72 --- /dev/null +++ b/src/common/utils/find-exactly-one/find-exactly-one.test.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { findExactlyOne } from "./find-exactly-one"; + +describe("find-exactly-one", () => { + it("when predicate matches to single item, returns the item", () => { + const actual = findExactlyOne((item) => item === "some-item")([ + "some-item", + "some-other-item", + ]); + + expect(actual).toBe("some-item"); + }); + + it("when predicate matches to many items, throws", () => { + expect(() => { + findExactlyOne((item) => item === "some-item")([ + "some-item", + "some-item", + ]); + }).toThrow("Tried to find exactly one, but found many"); + }); + + it("when predicate does not match, throws", () => { + expect(() => { + findExactlyOne((item) => item === "some-item")([ + "some-other-item", + ]); + }).toThrow("Tried to find exactly one, but didn't find any"); + }); +}); diff --git a/src/common/utils/find-exactly-one/find-exactly-one.ts b/src/common/utils/find-exactly-one/find-exactly-one.ts new file mode 100644 index 0000000000..181720d8b1 --- /dev/null +++ b/src/common/utils/find-exactly-one/find-exactly-one.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +export const findExactlyOne = (predicate: (item: T) => boolean) => (collection: T[]): T => { + const itemsFound = collection.filter(predicate); + + if (!itemsFound.length) { + throw new Error( + "Tried to find exactly one, but didn't find any", + ); + } + + if (itemsFound.length > 1) { + throw new Error( + "Tried to find exactly one, but found many", + ); + } + + return itemsFound[0]; +}; diff --git a/src/common/utils/type-narrowing.ts b/src/common/utils/type-narrowing.ts index d2d33929a2..936fb48816 100644 --- a/src/common/utils/type-narrowing.ts +++ b/src/common/utils/type-narrowing.ts @@ -35,6 +35,15 @@ export function hasTypedProperty(val return hasOwnProperty(val, key) && isValid(val[key]); } +/** + * Narrows `val` to include the property `key` with type string + * @param val the value that we are trying to type narrow + * @param key The key to test if it is present on the object (must be a literal for tsc to do any meaningful typing) + */ +export function hasStringProperty(val: S, key: K): val is (S & { [key in K]: string }) { + return hasOwnProperty(val, key) && isString(val[key]); +} + /** * Narrows `val` to include the property `key` with type `V | undefined` or doesn't contain it * @param val the value that we are trying to type narrow diff --git a/src/common/utils/with-error-logging/with-error-logging.test.ts b/src/common/utils/with-error-logging/with-error-logging.test.ts index b14b7278e9..b1140d4e54 100644 --- a/src/common/utils/with-error-logging/with-error-logging.test.ts +++ b/src/common/utils/with-error-logging/with-error-logging.test.ts @@ -4,28 +4,25 @@ */ import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting"; -import loggerInjectable from "../../logger.injectable"; -import type { Logger } from "../../logger"; import withErrorLoggingInjectable from "./with-error-logging.injectable"; import { pipeline } from "@ogre-tools/fp"; import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import { getPromiseStatus } from "../../test-utils/get-promise-status"; +import logErrorInjectable from "../../log-error.injectable"; describe("with-error-logging", () => { describe("given decorated sync function", () => { - let loggerStub: Logger; let toBeDecorated: jest.Mock; let decorated: (a: string, b: string) => number | undefined; + let logErrorMock: jest.Mock; beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); - loggerStub = { - error: jest.fn(), - } as unknown as Logger; + logErrorMock = jest.fn(); - di.override(loggerInjectable, () => loggerStub); + di.override(logErrorInjectable, () => logErrorMock); const withErrorLoggingFor = di.inject(withErrorLoggingInjectable); @@ -52,7 +49,7 @@ describe("with-error-logging", () => { }); it("does not log error", () => { - expect(loggerStub.error).not.toHaveBeenCalled(); + expect(logErrorMock).not.toHaveBeenCalled(); }); it("returns the value", () => { @@ -75,7 +72,7 @@ describe("with-error-logging", () => { }); it("does not log error", () => { - expect(loggerStub.error).not.toHaveBeenCalled(); + expect(logErrorMock).not.toHaveBeenCalled(); }); it("returns nothing", () => { @@ -104,7 +101,7 @@ describe("with-error-logging", () => { }); it("logs the error", () => { - expect(loggerStub.error).toHaveBeenCalledWith("some-error-message-for-some-error", error); + expect(logErrorMock).toHaveBeenCalledWith("some-error-message-for-some-error", error); }); it("throws", () => { @@ -114,18 +111,16 @@ describe("with-error-logging", () => { }); describe("given decorated async function", () => { - let loggerStub: Logger; let decorated: (a: string, b: string) => Promise; let toBeDecorated: AsyncFnMock; + let logErrorMock: jest.Mock; beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); - loggerStub = { - error: jest.fn(), - } as unknown as Logger; + logErrorMock = jest.fn(); - di.override(loggerInjectable, () => loggerStub); + di.override(logErrorInjectable, () => logErrorMock); const withErrorLoggingFor = di.inject(withErrorLoggingInjectable); @@ -153,7 +148,7 @@ describe("with-error-logging", () => { }); it("does not log error yet", () => { - expect(loggerStub.error).not.toHaveBeenCalled(); + expect(logErrorMock).not.toHaveBeenCalled(); }); it("does not resolve yet", async () => { @@ -176,7 +171,7 @@ describe("with-error-logging", () => { error = e; } - expect(loggerStub.error).toHaveBeenCalledWith("some-error-message-for-some-error", error); + expect(logErrorMock).toHaveBeenCalledWith("some-error-message-for-some-error", error); }); it("rejects", () => { @@ -198,7 +193,7 @@ describe("with-error-logging", () => { }); it("logs the rejection", () => { - expect(loggerStub.error).toHaveBeenCalledWith( + expect(logErrorMock).toHaveBeenCalledWith( "some-error-message-for-some-rejection", error, ); @@ -215,7 +210,7 @@ describe("with-error-logging", () => { }); it("does not log error", () => { - expect(loggerStub.error).not.toHaveBeenCalled(); + expect(logErrorMock).not.toHaveBeenCalled(); }); it("resolves with the value", async () => { @@ -231,7 +226,7 @@ describe("with-error-logging", () => { }); it("does not log error", () => { - expect(loggerStub.error).not.toHaveBeenCalled(); + expect(logErrorMock).not.toHaveBeenCalled(); }); it("resolves without value", async () => { diff --git a/src/common/utils/with-orphan-promise/with-orphan-promise.test.ts b/src/common/utils/with-orphan-promise/with-orphan-promise.test.ts index cea88b2352..51ebc18e13 100644 --- a/src/common/utils/with-orphan-promise/with-orphan-promise.test.ts +++ b/src/common/utils/with-orphan-promise/with-orphan-promise.test.ts @@ -5,21 +5,20 @@ import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting"; -import loggerInjectable from "../../logger.injectable"; -import type { Logger } from "../../logger"; import withOrphanPromiseInjectable from "./with-orphan-promise.injectable"; +import logErrorInjectable from "../../log-error.injectable"; describe("with orphan promise, when called", () => { let toBeDecorated: AsyncFnMock<(arg1: string, arg2: string) => Promise>; let actual: void; - let loggerStub: Logger; + let logErrorMock: jest.Mock; beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); - loggerStub = { error: jest.fn() } as unknown as Logger; + logErrorMock = jest.fn(); - di.override(loggerInjectable, () => loggerStub); + di.override(logErrorInjectable, () => logErrorMock); const withOrphanPromise = di.inject(withOrphanPromiseInjectable); @@ -49,7 +48,7 @@ describe("with orphan promise, when called", () => { }); it("logs the rejection", () => { - expect(loggerStub.error).toHaveBeenCalledWith("Orphan promise rejection encountered", "some-error"); + expect(logErrorMock).toHaveBeenCalledWith("Orphan promise rejection encountered", "some-error"); }); it("nothing else happens", () => { diff --git a/src/common/vars/platform.injectable.ts b/src/common/vars/platform.injectable.ts index 11939a7f06..f2c681d657 100644 --- a/src/common/vars/platform.injectable.ts +++ b/src/common/vars/platform.injectable.ts @@ -4,9 +4,12 @@ */ import { getInjectable } from "@ogre-tools/injectable"; +// Todo: OCP by creating distinct injectables for platforms. +export const allPlatforms = ["win32", "darwin", "linux"] as const; + const platformInjectable = getInjectable({ id: "platform", - instantiate: () => process.platform, + instantiate: () => process.platform as typeof allPlatforms[number], causesSideEffects: true, }); diff --git a/src/common/vars/release-channel.injectable.ts b/src/common/vars/release-channel.injectable.ts index d8275ff1cb..6554fbc0ac 100644 --- a/src/common/vars/release-channel.injectable.ts +++ b/src/common/vars/release-channel.injectable.ts @@ -2,9 +2,9 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ReleaseChannel } from "../application-update/update-channels"; import { createInitializableState } from "../initializable-state/create"; import buildSemanticVersionInjectable from "./build-semantic-version.injectable"; +import type { ReleaseChannel } from "../../features/application-update/common/update-channels"; const releaseChannelInjectable = createInitializableState({ id: "release-channel", diff --git a/src/extensions/common-api/registrations.ts b/src/extensions/common-api/registrations.ts index 5f19ec495d..4bfda25170 100644 --- a/src/extensions/common-api/registrations.ts +++ b/src/extensions/common-api/registrations.ts @@ -4,7 +4,7 @@ */ export type { StatusBarRegistration } from "../../renderer/components/status-bar/status-bar-registration"; export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/kube-object-menu-registration"; -export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration"; +export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../features/preferences/renderer/compliance-for-legacy-extension-api/app-preference-registration"; export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../../renderer/components/kube-object-details/kube-object-detail-registration"; export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration"; export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry"; diff --git a/src/extensions/lens-main-extension.ts b/src/extensions/lens-main-extension.ts index accc87989c..6c901db4ed 100644 --- a/src/extensions/lens-main-extension.ts +++ b/src/extensions/lens-main-extension.ts @@ -6,7 +6,7 @@ import { LensExtension, lensExtensionDependencies } from "./lens-extension"; import type { CatalogEntity } from "../common/catalog"; import type { IObservableArray } from "mobx"; -import type { MenuRegistration } from "../main/menu/menu-registration"; +import type { MenuRegistration } from "../features/application-menu/main/menu-registration"; import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration"; import type { ShellEnvModifier } from "../main/shell-session/shell-env-modifier/shell-env-modifier-registration"; import type { LensMainExtensionDependencies } from "./lens-extension-set-dependencies"; diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index d744d261d4..12203cc932 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -14,7 +14,7 @@ import type { KubernetesCluster } from "../common/catalog-entities"; import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration"; import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration"; import type { CommandRegistration } from "../renderer/components/command-palette/registered-commands/commands"; -import type { AppPreferenceRegistration } from "../renderer/components/+preferences/app-preferences/app-preference-registration"; +import type { AppPreferenceRegistration } from "../features/preferences/renderer/compliance-for-legacy-extension-api/app-preference-registration"; import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns"; import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views"; import type { StatusBarRegistration } from "../renderer/components/status-bar/status-bar-registration"; @@ -26,7 +26,7 @@ import { pipeline } from "@ogre-tools/fp"; import { getExtensionRoutePath } from "../renderer/routes/for-extension"; import type { LensRendererExtensionDependencies } from "./lens-extension-set-dependencies"; import type { KubeObjectHandlerRegistration } from "../renderer/kube-object/handler"; -import type { AppPreferenceTabRegistration } from "../renderer/components/+preferences/app-preference-tab/app-preference-tab-registration"; +import type { AppPreferenceTabRegistration } from "../features/preferences/renderer/compliance-for-legacy-extension-api/app-preference-tab-registration"; import type { KubeObjectDetailRegistration } from "../renderer/components/kube-object-details/kube-object-detail-registration"; export class LensRendererExtension extends LensExtension { diff --git a/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap b/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap index 3eb46b81e9..d755fa45fa 100644 --- a/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap +++ b/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap @@ -11,43 +11,61 @@ exports[`extension special characters in page registrations renders 1`] = `
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
{ describe("when navigating to add cluster using application menu", () => { beforeEach(async () => { - await applicationBuilder.applicationMenu.click("file.add-cluster"); + await applicationBuilder.applicationMenu.click( + "root", + "file", + "add-cluster", + ); }); it("renders", () => { diff --git a/src/features/application-menu/__snapshots__/application-menu.test.ts.snap b/src/features/application-menu/__snapshots__/application-menu.test.ts.snap new file mode 100644 index 0000000000..25ee762fea --- /dev/null +++ b/src/features/application-menu/__snapshots__/application-menu.test.ts.snap @@ -0,0 +1,132 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`application-menu, given platform is 'darwin' given enough time passes populates application menu 1`] = ` +Array [ + "root", + "root -> mac", + "root -> mac -> about", + "root -> mac -> separator-1", + "root -> mac -> navigate-to-preferences", + "root -> mac -> navigate-to-extensions", + "root -> mac -> separator-2", + "root -> mac -> services", + "root -> mac -> separator-3", + "root -> mac -> hide", + "root -> mac -> hide-others", + "root -> mac -> unhide", + "root -> mac -> separator-4", + "root -> mac -> quit", + "root -> file", + "root -> file -> add-cluster", + "root -> file -> separator-1-for-file", + "root -> file -> close-window", + "root -> edit", + "root -> edit -> undo", + "root -> edit -> redo", + "root -> edit -> separator-1-in-edit", + "root -> edit -> cut", + "root -> edit -> copy", + "root -> edit -> paste", + "root -> edit -> delete", + "root -> edit -> separator-2-in-edit", + "root -> edit -> selectAll", + "root -> view", + "root -> view -> navigate-to-catalog", + "root -> view -> open-command-palette", + "root -> view -> separator-1-for-view", + "root -> view -> go-back", + "root -> view -> go-forward", + "root -> view -> reload", + "root -> view -> toggle-dev-tools", + "root -> view -> separator-2-for-view", + "root -> view -> reset-zoom", + "root -> view -> zoom-in", + "root -> view -> zoom-out", + "root -> view -> separator-3-for-view", + "root -> view -> toggle-full-screen", + "root -> help", + "root -> help -> navigate-to-welcome", + "root -> help -> open-documentation", + "root -> help -> open-support", +] +`; + +exports[`application-menu, given platform is 'linux' given enough time passes populates application menu 1`] = ` +Array [ + "root", + "root -> file", + "root -> file -> add-cluster", + "root -> file -> navigate-to-preferences", + "root -> file -> navigate-to-extensions", + "root -> file -> quit", + "root -> edit", + "root -> edit -> undo", + "root -> edit -> redo", + "root -> edit -> separator-1-in-edit", + "root -> edit -> cut", + "root -> edit -> copy", + "root -> edit -> paste", + "root -> edit -> delete", + "root -> edit -> separator-2-in-edit", + "root -> edit -> selectAll", + "root -> view", + "root -> view -> navigate-to-catalog", + "root -> view -> open-command-palette", + "root -> view -> separator-1-for-view", + "root -> view -> go-back", + "root -> view -> go-forward", + "root -> view -> reload", + "root -> view -> toggle-dev-tools", + "root -> view -> separator-2-for-view", + "root -> view -> reset-zoom", + "root -> view -> zoom-in", + "root -> view -> zoom-out", + "root -> view -> separator-3-for-view", + "root -> view -> toggle-full-screen", + "root -> help", + "root -> help -> navigate-to-welcome", + "root -> help -> open-documentation", + "root -> help -> open-support", + "root -> help -> about", +] +`; + +exports[`application-menu, given platform is 'win32' given enough time passes populates application menu 1`] = ` +Array [ + "root", + "root -> file", + "root -> file -> add-cluster", + "root -> file -> navigate-to-preferences", + "root -> file -> navigate-to-extensions", + "root -> file -> quit", + "root -> edit", + "root -> edit -> undo", + "root -> edit -> redo", + "root -> edit -> separator-1-in-edit", + "root -> edit -> cut", + "root -> edit -> copy", + "root -> edit -> paste", + "root -> edit -> delete", + "root -> edit -> separator-2-in-edit", + "root -> edit -> selectAll", + "root -> view", + "root -> view -> navigate-to-catalog", + "root -> view -> open-command-palette", + "root -> view -> separator-1-for-view", + "root -> view -> go-back", + "root -> view -> go-forward", + "root -> view -> reload", + "root -> view -> toggle-dev-tools", + "root -> view -> separator-2-for-view", + "root -> view -> reset-zoom", + "root -> view -> zoom-in", + "root -> view -> zoom-out", + "root -> view -> separator-3-for-view", + "root -> view -> toggle-full-screen", + "root -> help", + "root -> help -> navigate-to-welcome", + "root -> help -> open-documentation", + "root -> help -> open-support", + "root -> help -> about", +] +`; diff --git a/src/features/application-menu/application-menu-in-legacy-extension-api.test.ts b/src/features/application-menu/application-menu-in-legacy-extension-api.test.ts new file mode 100644 index 0000000000..888f89f251 --- /dev/null +++ b/src/features/application-menu/application-menu-in-legacy-extension-api.test.ts @@ -0,0 +1,226 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { noop } from "lodash/fp"; +import { runInAction } from "mobx"; +import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake"; +import applicationMenuItemInjectionToken from "./main/menu-items/application-menu-item-injection-token"; +import logErrorInjectable from "../../common/log-error.injectable"; + +describe("application-menu-in-legacy-extension-api", () => { + let builder: ApplicationBuilder; + let logErrorMock: jest.Mock; + + beforeEach(async () => { + builder = getApplicationBuilder(); + + builder.beforeApplicationStart( + (mainDi) => { + runInAction(() => { + mainDi.register( + someTopMenuItemInjectable, + someNonExtensionBasedMenuItemInjectable, + ); + }); + + logErrorMock = jest.fn(); + + mainDi.override(logErrorInjectable, () => logErrorMock); + }, + ); + + await builder.startHidden(); + }); + + describe("when extension with application menu items is enabled", () => { + let onClickMock: jest.Mock; + let testExtensionOptions: FakeExtensionOptions; + + beforeEach(() => { + onClickMock = jest.fn(); + + testExtensionOptions = { + id: "some-test-extension", + name: "some-extension-name", + + mainOptions: { + appMenus: [ + { + id: "some-non-shown-item", + parentId: "some-top-menu-item", + click: noop, + label: "Irrelevant", + visible: false, + }, + + { + id: "some-clickable-item", + parentId: "some-top-menu-item", + click: onClickMock, + }, + + { + parentId: "some-top-menu-item", + type: "separator", + }, + + { + id: "some-os-action-menu-item-id", + parentId: "some-top-menu-item", + role: "help", + }, + + { + id: "some-submenu-with-explicit-children", + parentId: "some-top-menu-item", + + submenu: [ + { id: "some-explicit-child", label: "Some explicit child", click: noop }, + ], + }, + ], + }, + }; + + builder.extensions.enable(testExtensionOptions); + }); + + it("related menu items exist", () => { + const menuItemPathsForExtension = builder.applicationMenu.items.filter( + (x) => + x.join(".").startsWith("root.some-top-menu-item.some-extension-name"), + ); + + expect(menuItemPathsForExtension).toEqual([ + ["root", "some-top-menu-item", "some-extension-name/some-clickable-item"], + // Note: anonymous index "1" is used by the non-visible menu item. + ["root", "some-top-menu-item", "some-extension-name/2-separator"], + ["root", "some-top-menu-item", "some-extension-name/some-os-action-menu-item-id"], + ["root", "some-top-menu-item", "some-extension-name/some-submenu-with-explicit-children"], + ["root", "some-top-menu-item", "some-extension-name/some-submenu-with-explicit-children", "some-extension-name/some-submenu-with-explicit-children/some-explicit-child"], + ]); + }); + + it("when the extension-based clickable menu item is clicked, does so", () => { + builder.applicationMenu.click( + "root", "some-top-menu-item", "some-extension-name/some-clickable-item", + ); + + expect(onClickMock).toHaveBeenCalled(); + }); + + describe("when the extension is disabled", () => { + beforeEach(() => { + builder.extensions.disable(testExtensionOptions); + }); + + it("when related menu items no longer exist", () => { + const menuItemPathsForExtension = builder.applicationMenu.items.filter( + (x) => + x.join(".").startsWith("root.some-top-menu-item.some-extension-name"), + ); + + expect(menuItemPathsForExtension).toEqual([]); + }); + + it("when the extension is enabled again, also related menu items exist again", () => { + builder.extensions.enable(testExtensionOptions); + + const menuItemPathsForExtension = builder.applicationMenu.items.filter( + (x) => + x.join(".").startsWith("root.some-top-menu-item.some-extension-name"), + ); + + expect(menuItemPathsForExtension).toEqual([ + ["root", "some-top-menu-item", "some-extension-name/some-clickable-item"], + ["root", "some-top-menu-item", "some-extension-name/2-separator"], + ["root", "some-top-menu-item", "some-extension-name/some-os-action-menu-item-id"], + ["root", "some-top-menu-item", "some-extension-name/some-submenu-with-explicit-children"], + ["root", "some-top-menu-item", "some-extension-name/some-submenu-with-explicit-children", "some-extension-name/some-submenu-with-explicit-children/some-explicit-child"], + ]); + }); + }); + }); + + describe("when extension with unrecognizable application menu items is enabled", () => { + + beforeEach(() => { + const testExtensionOptions: FakeExtensionOptions = { + id: "some-test-extension", + name: "some-extension-name", + + mainOptions: { + appMenus: [ + { + id: "some-recognizable-item", + parentId: "some-top-menu-item", + click: noop, + }, + + { + id: "some-unrecognizable-item", + parentId: "some-top-menu-item", + // Note: there is no way to recognize this + // click: noop, + // role: "help" + // submenu: [] + // type: "separator" + }, + ], + }, + }; + + builder.extensions.enable(testExtensionOptions); + }); + + it("only recognizable menu items from extension exist", () => { + const menuItemPathsForExtension = builder.applicationMenu.items.filter( + (x) => + x.join(".").startsWith("root.some-top-menu-item.some-extension-name"), + ); + + expect(menuItemPathsForExtension).toEqual([ + ["root", "some-top-menu-item", "some-extension-name/some-recognizable-item"], + ]); + }); + + it("logs about the unrecognizable item", () => { + expect(logErrorMock).toHaveBeenCalledWith( + '[MENU]: Tried to register menu item "some-extension-name/some-unrecognizable-item" but it is not recognizable as any of ApplicationMenuItemTypes', + ); + }); + }); +}); + +const someTopMenuItemInjectable = getInjectable({ + id: "some-top-menu-item", + + instantiate: () => ({ + id: "some-top-menu-item", + parentId: "root" as const, + kind: "top-level-menu" as const, + label: "Some existing root menu item", + orderNumber: 42, + }), + + injectionToken: applicationMenuItemInjectionToken, +}); + +const someNonExtensionBasedMenuItemInjectable = getInjectable({ + id: "some-non-extension-based-menu-item", + + instantiate: () => ({ + id: "some-non-extension-based-menu-item", + parentId: "some-top-menu-item", + kind: "clickable-menu-item" as const, + label: "Some menu item", + onClick: () => {}, + orderNumber: 42, + }), + + injectionToken: applicationMenuItemInjectionToken, +}); diff --git a/src/features/application-menu/application-menu.test.ts b/src/features/application-menu/application-menu.test.ts new file mode 100644 index 0000000000..e0128b83fb --- /dev/null +++ b/src/features/application-menu/application-menu.test.ts @@ -0,0 +1,59 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import populateApplicationMenuInjectable from "./main/populate-application-menu.injectable"; +import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time"; +import { getCompositePaths } from "../../common/utils/composite/get-composite-paths/get-composite-paths"; +import platformInjectable, { allPlatforms } from "../../common/vars/platform.injectable"; + +describe.each(allPlatforms)("application-menu, given platform is '%s'", (platform) => { + let builder: ApplicationBuilder; + let populateApplicationMenuMock: jest.Mock; + + beforeEach(async () => { + useFakeTime(); + + populateApplicationMenuMock = jest.fn(); + + builder = getApplicationBuilder(); + + builder.beforeApplicationStart((mainDi) => { + mainDi.override(platformInjectable, () => platform); + + mainDi.override( + populateApplicationMenuInjectable, + () => populateApplicationMenuMock, + ); + }); + + await builder.startHidden(); + }); + + it("when insufficient time passes, does not populate menu items yet", () => { + advanceFakeTime(99); + + expect(populateApplicationMenuMock).not.toHaveBeenCalled(); + }); + + describe("given enough time passes", () => { + let applicationMenuPaths: string[][]; + + beforeEach(() => { + advanceFakeTime(100); + applicationMenuPaths = getCompositePaths( + populateApplicationMenuMock.mock.calls[0][0], + ); + }); + + it("populates application menu with at least something", () => { + expect(applicationMenuPaths.length).toBeGreaterThan(0); + }); + + it("populates application menu", () => { + expect(applicationMenuPaths.map(x => x.join(" -> "))).toMatchSnapshot(); + }); + }); +}); diff --git a/src/features/application-menu/handling-of-orphan-application-menu-items.test.ts b/src/features/application-menu/handling-of-orphan-application-menu-items.test.ts new file mode 100644 index 0000000000..7cae6d0079 --- /dev/null +++ b/src/features/application-menu/handling-of-orphan-application-menu-items.test.ts @@ -0,0 +1,81 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import populateApplicationMenuInjectable from "./main/populate-application-menu.injectable"; +import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time"; +import { getCompositePaths } from "../../common/utils/composite/get-composite-paths/get-composite-paths"; +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "./main/menu-items/application-menu-item-injection-token"; +import { runInAction } from "mobx"; +import logErrorInjectable from "../../common/log-error.injectable"; + +describe("handling-of-orphan-application-menu-items, given orphan menu item", () => { + let builder: ApplicationBuilder; + let populateApplicationMenuMock: jest.Mock; + let logErrorMock: jest.Mock; + + beforeEach(async () => { + useFakeTime(); + + populateApplicationMenuMock = jest.fn(); + logErrorMock = jest.fn(); + + builder = getApplicationBuilder(); + + builder.beforeApplicationStart((mainDi) => { + const someOrphanMenuItemInjectable = getInjectable({ + id: "some-orphan-menu-item", + instantiate: () => ({ + kind: "sub-menu" as const, + id: "some-item-id", + // Note: unknown id makes this item an orphan. + parentId: "some-unknown-parent-id", + orderNumber: 0, + label: "irrelevant", + }), + + injectionToken: applicationMenuItemInjectionToken, + }); + + runInAction(() => { + mainDi.register(someOrphanMenuItemInjectable); + }); + + mainDi.override(logErrorInjectable, () => logErrorMock); + + mainDi.override( + populateApplicationMenuInjectable, + () => populateApplicationMenuMock, + ); + }); + + await builder.startHidden(); + }); + + describe("given some time passes", () => { + let applicationMenuPaths: string[][]; + + beforeEach(() => { + advanceFakeTime(100); + + applicationMenuPaths = getCompositePaths( + populateApplicationMenuMock.mock.calls[0][0], + ); + }); + + it("keeps showing the other application menu items without throwing", () => { + expect(applicationMenuPaths.length).toBeGreaterThan(0); + }); + + it("does not show orphan application menu item", () => { + expect(applicationMenuPaths.find(x => x.join(".").endsWith("some-item-id"))); + }); + + it("logs about bad menu item", () => { + expect(logErrorMock).toHaveBeenCalledWith('[MENU]: cannot render menu item for missing parentIds: "some-unknown-parent-id"'); + }); + }); +}); diff --git a/src/features/application-menu/main/application-menu-item-composite.injectable.ts b/src/features/application-menu/main/application-menu-item-composite.injectable.ts new file mode 100644 index 0000000000..b9012fb67b --- /dev/null +++ b/src/features/application-menu/main/application-menu-item-composite.injectable.ts @@ -0,0 +1,64 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemsInjectable from "./application-menu-items.injectable"; +import type { Composite } from "../../../common/utils/composite/get-composite/get-composite"; +import { getCompositeFor } from "../../../common/utils/composite/get-composite/get-composite"; +import { computed } from "mobx"; +import { pipeline } from "@ogre-tools/fp"; +import type { ApplicationMenuItemTypes } from "./menu-items/application-menu-item-injection-token"; +import type { RootComposite } from "../../../common/utils/composite/interfaces"; +import type { Discriminable } from "../../../common/utils/composable-responsibilities/discriminable/discriminable"; +import { orderByOrderNumber } from "../../../common/utils/composable-responsibilities/orderable/orderable"; +import logErrorInjectable from "../../../common/log-error.injectable"; +import { isShown } from "../../../common/utils/composable-responsibilities/showable/showable"; + +export type MenuItemRoot = Discriminable<"root"> & RootComposite<"root">; + +const applicationMenuItemCompositeInjectable = getInjectable({ + id: "application-menu-item-composite", + + instantiate: (di) => { + const menuItems = di.inject(applicationMenuItemsInjectable); + const logError = di.inject(logErrorInjectable); + + return computed((): Composite => { + const items = menuItems.get(); + + return pipeline( + [ + { + parentId: undefined, + id: "root", + kind: "root", + } as const, + + ...items, + ], + + getCompositeFor({ + getId: (x) => x.id, + getParentId: (x) => x.parentId, + transformChildren: (children) => + pipeline( + children, + orderByOrderNumber, + (children) => children.filter(isShown), + ), + + handleMissingParentIds: ({ missingParentIds }) => { + logError( + `[MENU]: cannot render menu item for missing parentIds: "${missingParentIds.join( + '", "', + )}"`, + ); + }, + }), + ); + }); + }, +}); + +export default applicationMenuItemCompositeInjectable; diff --git a/src/features/application-menu/main/application-menu-item-registrator.injectable.ts b/src/features/application-menu/main/application-menu-item-registrator.injectable.ts new file mode 100644 index 0000000000..8c3d635db0 --- /dev/null +++ b/src/features/application-menu/main/application-menu-item-registrator.injectable.ts @@ -0,0 +1,158 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { Injectable } from "@ogre-tools/injectable"; +import { getInjectable } from "@ogre-tools/injectable"; +import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token"; +import type { LensExtension } from "../../../extensions/lens-extension"; +import type { LensMainExtension } from "../../../extensions/lens-main-extension"; +import type { + ApplicationMenuItemTypes, + ClickableMenuItem, + OsActionMenuItem, + Separator, +} from "./menu-items/application-menu-item-injection-token"; +import applicationMenuItemInjectionToken from "./menu-items/application-menu-item-injection-token"; +import type { MenuRegistration } from "./menu-registration"; +import logErrorInjectable from "../../../common/log-error.injectable"; + +const applicationMenuItemRegistratorInjectable = getInjectable({ + id: "application-menu-item-registrator", + + instantiate: (di) => { + const logError = di.inject(logErrorInjectable); + const toRecursedInjectables = toRecursedInjectablesFor(logError); + + return (ext: LensExtension) => { + const extension = ext as LensMainExtension; + + return extension.appMenus.flatMap( + toRecursedInjectables([extension.sanitizedExtensionId]), + ); + }; + }, + + injectionToken: extensionRegistratorInjectionToken, +}); + +export default applicationMenuItemRegistratorInjectable; + +const toRecursedInjectablesFor = (logError: (errorMessage: string) => void) => { + const toRecursedInjectables = (previousIdPath: string[]) => + ( + registration: MenuRegistration, + index: number, + // Todo: new version of injectable would require less type parameters with defaults. + ): Injectable< + ApplicationMenuItemTypes, + ApplicationMenuItemTypes, + void + >[] => { + const previousIdPathString = previousIdPath.join("/"); + const registrationId = registration.id || index.toString(); + const currentIdPath = [...previousIdPath, registrationId]; + const currentIdPathString = currentIdPath.join("/"); + const parentId = registration.parentId || previousIdPathString; + + const menuItem = getApplicationMenuItem({ + registration, + parentId, + currentIdPathString, + index, + }); + + if (!menuItem) { + logError(`[MENU]: Tried to register menu item "${currentIdPathString}" but it is not recognizable as any of ApplicationMenuItemTypes`); + + return []; + } + + return [ + getInjectable({ + id: `${currentIdPathString}/application-menu-item`, + + instantiate: () => menuItem, + + injectionToken: applicationMenuItemInjectionToken, + }), + + ...((registration.submenu as MenuRegistration[]) + ? (registration.submenu as MenuRegistration[]).flatMap( + toRecursedInjectables(currentIdPath), + ) + : []), + ]; + }; + + return toRecursedInjectables; +}; + +const getApplicationMenuItem = ({ + registration, + index, + currentIdPathString, + parentId, +}: { + registration: MenuRegistration; + index: number; + currentIdPathString: string; + parentId: string; +}): ApplicationMenuItemTypes | undefined => { + const orderNumber = 1000 + index * 10; + + if (registration.type === "separator") { + return { + kind: "separator" as const, + id: `${currentIdPathString}-separator`, + parentId, + orderNumber, + } as Separator; + } + + if (registration.submenu) { + return { + kind: "sub-menu" as const, + id: currentIdPathString, + parentId, + isShown: registration.visible ?? true, + orderNumber, + label: registration.label || "", + }; + } + + if (registration.click) { + return { + kind: "clickable-menu-item" as const, + id: currentIdPathString, + parentId, + // Todo: hide electron events from this abstraction. + onClick: registration.click, + label: registration.label, + isShown: registration.visible ?? true, + orderNumber, + + ...(registration.accelerator + ? { keyboardShortcut: registration.accelerator as string } + : {}), + } as ClickableMenuItem; + } + + if (registration.role) { + return { + kind: "os-action-menu-item" as const, + id: currentIdPathString, + parentId, + label: registration.label, + isShown: registration.visible ?? true, + orderNumber, + actionName: registration.role, + + ...(registration.accelerator + ? { keyboardShortcut: registration.accelerator as string } + : {}), + } as OsActionMenuItem; + } + + return undefined; +}; diff --git a/src/features/application-menu/main/application-menu-items.injectable.ts b/src/features/application-menu/main/application-menu-items.injectable.ts new file mode 100644 index 0000000000..7cb8cd3ef4 --- /dev/null +++ b/src/features/application-menu/main/application-menu-items.injectable.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MenuItemConstructorOptions } from "electron"; +import { computed } from "mobx"; +import applicationMenuItemInjectionToken from "./menu-items/application-menu-item-injection-token"; +import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx"; + +export interface MenuItemOpts extends MenuItemConstructorOptions { + submenu?: MenuItemConstructorOptions[]; +} + +const applicationMenuItemsInjectable = getInjectable({ + id: "application-menu-items", + + instantiate: (di) => { + const computedInjectMany = di.inject(computedInjectManyInjectable); + + return computed(() => + computedInjectMany(applicationMenuItemInjectionToken).get(), + ); + }, +}); + + +export default applicationMenuItemsInjectable; diff --git a/src/features/application-menu/main/application-menu-reactivity.injectable.ts b/src/features/application-menu/main/application-menu-reactivity.injectable.ts new file mode 100644 index 0000000000..67631322aa --- /dev/null +++ b/src/features/application-menu/main/application-menu-reactivity.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { autorun } from "mobx"; +import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable"; +import populateApplicationMenuInjectable from "./populate-application-menu.injectable"; +import applicationMenuItemCompositeInjectable from "./application-menu-item-composite.injectable"; + +const applicationMenuReactivityInjectable = getInjectable({ + id: "application-menu-reactivity", + + instantiate: (di) => { + const applicationMenuItemComposite = di.inject(applicationMenuItemCompositeInjectable); + const populateApplicationMenu = di.inject(populateApplicationMenuInjectable); + + return getStartableStoppable("application-menu-reactivity", () => + autorun(() => populateApplicationMenu(applicationMenuItemComposite.get()), { + delay: 100, + }), + ); + }, +}); + +export default applicationMenuReactivityInjectable; diff --git a/src/features/application-menu/main/menu-items/application-menu-item-injection-token.ts b/src/features/application-menu/main/menu-items/application-menu-item-injection-token.ts new file mode 100644 index 0000000000..b90e5e1612 --- /dev/null +++ b/src/features/application-menu/main/menu-items/application-menu-item-injection-token.ts @@ -0,0 +1,91 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { BrowserWindow, KeyboardEvent, MenuItemConstructorOptions, MenuItem as ElectronMenuItem } from "electron"; +import type { SetOptional } from "type-fest"; +import type { ChildOfParentComposite, ParentOfChildComposite } from "../../../../common/utils/composite/interfaces"; +import type { MaybeShowable } from "../../../../common/utils/composable-responsibilities/showable/showable"; +import type { Discriminable } from "../../../../common/utils/composable-responsibilities/discriminable/discriminable"; +import type { Orderable } from "../../../../common/utils/composable-responsibilities/orderable/orderable"; + +export interface MayHaveKeyboardShortcut { + keyboardShortcut?: string; +} + +export interface ElectronClickable { + // TODO: This leaky abstraction is exposed in Extension API, therefore cannot be updated + onClick: (menuItem: ElectronMenuItem, browserWindow: (BrowserWindow) | (undefined), event: KeyboardEvent) => void; +} + +export interface Labeled { + label: string; +} + +export interface MaybeLabeled extends SetOptional {} + +type ApplicationMenuItemType = + // Note: "kind" is being used for Discriminated unions of TypeScript to achieve type narrowing. + // See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions + & Discriminable + & ParentOfChildComposite + & ChildOfParentComposite + & MaybeShowable + & Orderable; + +export type TopLevelMenu = + & ApplicationMenuItemType<"top-level-menu"> + & { parentId: "root" } + & Labeled + & MayHaveElectronRole; + +interface MayHaveElectronRole { + role?: ElectronRoles; +} + +type ElectronRoles = Exclude; + +export type SubMenu = + & ApplicationMenuItemType<"sub-menu"> + & Labeled + & ChildOfParentComposite; + +export type ClickableMenuItem = + & ApplicationMenuItemType<"clickable-menu-item"> + & MenuItem + & Labeled + & ElectronClickable; + +export type OsActionMenuItem = + & ApplicationMenuItemType<"os-action-menu-item"> + & MenuItem + & MaybeLabeled + & TriggersElectronAction; + +type MenuItem = + & ChildOfParentComposite + & MayHaveKeyboardShortcut; + +interface TriggersElectronAction { + actionName: ElectronRoles; +} + +// Todo: SeparatorMenuItem +export type Separator = + & ApplicationMenuItemType<"separator"> + & ChildOfParentComposite; + +export type ApplicationMenuItemTypes = + | TopLevelMenu + | SubMenu + | OsActionMenuItem + | ClickableMenuItem + | Separator +; + +const applicationMenuItemInjectionToken = getInjectionToken({ + id: "application-menu-item-injection-token", +}); + +export default applicationMenuItemInjectionToken; diff --git a/src/features/application-menu/main/menu-items/edit/edit-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/edit/edit-menu-item.injectable.ts new file mode 100644 index 0000000000..f62a4221ac --- /dev/null +++ b/src/features/application-menu/main/menu-items/edit/edit-menu-item.injectable.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../application-menu-item-injection-token"; + +const editMenuItemInjectable = getInjectable({ + id: "edit-application-menu-item", + + instantiate: () => ({ + kind: "top-level-menu" as const, + id: "edit", + parentId: "root" as const, + orderNumber: 30, + label: "Edit", + }), + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default editMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/edit/operation-system-actions/operation-system-actions.injectable.ts b/src/features/application-menu/main/menu-items/edit/operation-system-actions/operation-system-actions.injectable.ts new file mode 100644 index 0000000000..aea229cc68 --- /dev/null +++ b/src/features/application-menu/main/menu-items/edit/operation-system-actions/operation-system-actions.injectable.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getApplicationMenuOperationSystemActionInjectable } from "../../get-application-menu-operation-system-action-injectable"; +import { getApplicationMenuSeparatorInjectable } from "../../get-application-menu-separator-injectable"; + +export const actionForUndo = getApplicationMenuOperationSystemActionInjectable({ + id: "undo", + parentId: "edit", + orderNumber: 10, + actionName: "undo", +}); + +export const actionForRedo = getApplicationMenuOperationSystemActionInjectable({ + id: "redo", + parentId: "edit", + orderNumber: 20, + actionName: "redo", +}); + +export const separator1 = getApplicationMenuSeparatorInjectable({ + id: "separator-1-in-edit", + parentId: "edit", + orderNumber: 30, +}); + +export const actionForCut = getApplicationMenuOperationSystemActionInjectable({ + id: "cut", + parentId: "edit", + orderNumber: 40, + actionName: "cut", +}); + +export const actionForCopy = getApplicationMenuOperationSystemActionInjectable({ + id: "copy", + parentId: "edit", + orderNumber: 50, + actionName: "copy", +}); + +export const actionForPaste = getApplicationMenuOperationSystemActionInjectable({ + id: "paste", + parentId: "edit", + orderNumber: 60, + actionName: "paste", +}); + +export const actionForDelete = getApplicationMenuOperationSystemActionInjectable({ + id: "delete", + parentId: "edit", + orderNumber: 70, + actionName: "delete", +}); + +export const separator2 = getApplicationMenuSeparatorInjectable({ + id: "separator-2-in-edit", + parentId: "edit", + orderNumber: 80, +}); + +export const actionForSelectAll = getApplicationMenuOperationSystemActionInjectable({ + id: "selectAll", + parentId: "edit", + orderNumber: 90, + actionName: "selectAll", +}); + diff --git a/src/features/application-menu/main/menu-items/file/add-cluster/add-cluster-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/file/add-cluster/add-cluster-menu-item.injectable.ts new file mode 100644 index 0000000000..78c7201ca6 --- /dev/null +++ b/src/features/application-menu/main/menu-items/file/add-cluster/add-cluster-menu-item.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import navigateToAddClusterInjectable from "../../../../../../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable"; + +const addClusterMenuItemInjectable = getInjectable({ + id: "add-cluster-application-menu-item", + + instantiate: (di) => { + const navigateToAddCluster = di.inject(navigateToAddClusterInjectable); + + return { + kind: "clickable-menu-item" as const, + parentId: "file", + id: "add-cluster", + orderNumber: 10, + label: "Add Cluster", + keyboardShortcut: "CmdOrCtrl+Shift+A", + + onClick: () => { + navigateToAddCluster(); + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default addClusterMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/file/close-window/close-window-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/file/close-window/close-window-menu-item.injectable.ts new file mode 100644 index 0000000000..745adb55df --- /dev/null +++ b/src/features/application-menu/main/menu-items/file/close-window/close-window-menu-item.injectable.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import isMacInjectable from "../../../../../../common/vars/is-mac.injectable"; + +const closeWindowMenuItemInjectable = getInjectable({ + id: "close-window-application-menu-item", + + instantiate: (di) => { + const isMac = di.inject(isMacInjectable); + + return { + id: "close-window", + kind: "os-action-menu-item" as const, + parentId: "file", + orderNumber: 60, + actionName: "close" as const, + label: "Close Window", + keyboardShortcut: "Shift+Cmd+W", + isShown: isMac, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default closeWindowMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/file/file-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/file/file-menu-item.injectable.ts new file mode 100644 index 0000000000..dce2bb5cef --- /dev/null +++ b/src/features/application-menu/main/menu-items/file/file-menu-item.injectable.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../application-menu-item-injection-token"; + +const fileMenuItemInjectable = getInjectable({ + id: "file-application-menu-item", + + instantiate: () => ({ + kind: "top-level-menu" as const, + id: "file", + parentId: "root" as const, + orderNumber: 20, + label: "File", + }), + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default fileMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/file/separators/separators.injectable.ts b/src/features/application-menu/main/menu-items/file/separators/separators.injectable.ts new file mode 100644 index 0000000000..a0f2cbe5d8 --- /dev/null +++ b/src/features/application-menu/main/menu-items/file/separators/separators.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { + getApplicationMenuSeparatorInjectable, +} from "../../get-application-menu-separator-injectable"; + +export const separator1 = getApplicationMenuSeparatorInjectable({ + id: "separator-1-for-file", + parentId: "file", + orderNumber: 20, + isShownOnlyOnMac: true, +}); diff --git a/src/features/application-menu/main/menu-items/get-application-menu-operation-system-action-injectable.ts b/src/features/application-menu/main/menu-items/get-application-menu-operation-system-action-injectable.ts new file mode 100644 index 0000000000..ebe6a6865e --- /dev/null +++ b/src/features/application-menu/main/menu-items/get-application-menu-operation-system-action-injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { OsActionMenuItem } from "./application-menu-item-injection-token"; +import applicationMenuItemInjectionToken from "./application-menu-item-injection-token"; + +const getApplicationMenuOperationSystemActionInjectable = ({ + id, + ...rest +}: Omit) => + getInjectable({ + id: `application-menu-operation-system-action/${id}`, + + instantiate: () => ({ + ...rest, + id, + kind: "os-action-menu-item" as const, + }), + + injectionToken: applicationMenuItemInjectionToken, + }); + +export { getApplicationMenuOperationSystemActionInjectable }; diff --git a/src/features/application-menu/main/menu-items/get-application-menu-separator-injectable.ts b/src/features/application-menu/main/menu-items/get-application-menu-separator-injectable.ts new file mode 100644 index 0000000000..d9eb1677cc --- /dev/null +++ b/src/features/application-menu/main/menu-items/get-application-menu-separator-injectable.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { Separator } from "./application-menu-item-injection-token"; +import applicationMenuItemInjectionToken from "./application-menu-item-injection-token"; +import isMacInjectable from "../../../../common/vars/is-mac.injectable"; + +const getApplicationMenuSeparatorInjectable = ({ + id, + isShownOnlyOnMac = false, + ...rest +}: { isShownOnlyOnMac?: boolean } & Omit< + Separator, + "kind" | "isShown" +>) => + getInjectable({ + id: `application-menu-separator/${id}`, + + instantiate: (di) => { + const isMac = di.inject(isMacInjectable); + const isShown = isShownOnlyOnMac ? isMac : true; + + return { + ...rest, + id, + kind: "separator" as const, + isShown, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, + }); + +export { getApplicationMenuSeparatorInjectable }; diff --git a/src/features/application-menu/main/menu-items/help/help-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/help/help-menu-item.injectable.ts new file mode 100644 index 0000000000..5b83115362 --- /dev/null +++ b/src/features/application-menu/main/menu-items/help/help-menu-item.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../application-menu-item-injection-token"; + +const helpMenuItemInjectable = getInjectable({ + id: "help-application-menu-item", + + instantiate: () => ({ + kind: "top-level-menu" as const, + id: "help", + parentId: "root" as const, + orderNumber: 50, + label: "Help", + role: "help" as const, + }), + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default helpMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/help/navigate-to-welcome/navigate-to-extensions-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/help/navigate-to-welcome/navigate-to-extensions-menu-item.injectable.ts new file mode 100644 index 0000000000..10100ecb4c --- /dev/null +++ b/src/features/application-menu/main/menu-items/help/navigate-to-welcome/navigate-to-extensions-menu-item.injectable.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import navigateToWelcomeInjectable from "../../../../../../common/front-end-routing/routes/welcome/navigate-to-welcome.injectable"; + +const navigateToWelcomeMenuItem = getInjectable({ + id: "navigate-to-welcome-menu-item", + + instantiate: (di) => { + const navigateToWelcome = di.inject(navigateToWelcomeInjectable); + + return { + kind: "clickable-menu-item" as const, + parentId: "help", + id: "navigate-to-welcome", + orderNumber: 10, + label: "Welcome", + + onClick: () => { + navigateToWelcome(); + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default navigateToWelcomeMenuItem; diff --git a/src/features/application-menu/main/menu-items/help/open-documentation/open-documentation-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/help/open-documentation/open-documentation-menu-item.injectable.ts new file mode 100644 index 0000000000..ca63120810 --- /dev/null +++ b/src/features/application-menu/main/menu-items/help/open-documentation/open-documentation-menu-item.injectable.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import { docsUrl } from "../../../../../../common/vars"; +import openLinkInBrowserInjectable from "../../../../../../common/utils/open-link-in-browser.injectable"; +import loggerInjectable from "../../../../../../common/logger.injectable"; + +const openDocumentationMenuItemInjectable = getInjectable({ + id: "open-documentation-menu-item", + + instantiate: (di) => { + const openLinkInBrowser = di.inject(openLinkInBrowserInjectable); + const logger = di.inject(loggerInjectable); + + return { + kind: "clickable-menu-item" as const, + parentId: "help", + id: "open-documentation", + orderNumber: 20, + label: "Documentation", + + // TODO: Convert to async/await + onClick: () => { + openLinkInBrowser(docsUrl).catch((error) => { + logger.error("[MENU]: failed to open browser", { error }); + }); + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default openDocumentationMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/help/open-support/open-support-item.injectable.ts b/src/features/application-menu/main/menu-items/help/open-support/open-support-item.injectable.ts new file mode 100644 index 0000000000..7a1db63078 --- /dev/null +++ b/src/features/application-menu/main/menu-items/help/open-support/open-support-item.injectable.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import { supportUrl } from "../../../../../../common/vars"; +import openLinkInBrowserInjectable from "../../../../../../common/utils/open-link-in-browser.injectable"; +import loggerInjectable from "../../../../../../common/logger.injectable"; + +const openSupportItemInjectable = getInjectable({ + id: "open-support-menu-item", + + instantiate: (di) => { + const openLinkInBrowser = di.inject(openLinkInBrowserInjectable); + const logger = di.inject(loggerInjectable); + + return { + kind: "clickable-menu-item" as const, + parentId: "help", + id: "open-support", + orderNumber: 30, + label: "Support", + + // TODO: Convert to async/await + onClick: () => { + openLinkInBrowser(supportUrl).catch((error) => { + logger.error("[MENU]: failed to open browser", { error }); + }); + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default openSupportItemInjectable; diff --git a/src/features/application-menu/main/menu-items/special-menu-for-mac-application/navigate-to-extensions/navigate-to-extensions-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/navigate-to-extensions/navigate-to-extensions-menu-item.injectable.ts new file mode 100644 index 0000000000..40aa366280 --- /dev/null +++ b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/navigate-to-extensions/navigate-to-extensions-menu-item.injectable.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import navigateToExtensionsInjectable from "../../../../../../common/front-end-routing/routes/extensions/navigate-to-extensions.injectable"; +import isMacInjectable from "../../../../../../common/vars/is-mac.injectable"; + +const navigateToExtensionsMenuItem = getInjectable({ + id: "navigate-to-extensions-menu-item", + + instantiate: (di) => { + const navigateToExtensions = di.inject(navigateToExtensionsInjectable); + const isMac = di.inject(isMacInjectable); + + return { + kind: "clickable-menu-item" as const, + parentId: isMac ? "mac" : "file", + id: "navigate-to-extensions", + orderNumber: isMac ? 50 : 40, + label: "Extensions", + keyboardShortcut: isMac ? "CmdOrCtrl+Shift+E" : "Ctrl+Shift+E", + + onClick: () => { + navigateToExtensions(); + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default navigateToExtensionsMenuItem; diff --git a/src/features/application-menu/main/menu-items/special-menu-for-mac-application/operation-system-actions/operation-system-actions.injectable.ts b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/operation-system-actions/operation-system-actions.injectable.ts new file mode 100644 index 0000000000..1c78d81031 --- /dev/null +++ b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/operation-system-actions/operation-system-actions.injectable.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { + getApplicationMenuOperationSystemActionInjectable, +} from "../../get-application-menu-operation-system-action-injectable"; + +export const actionForServices = getApplicationMenuOperationSystemActionInjectable({ + id: "services", + parentId: "mac", + orderNumber: 80, + actionName: "services", +}); + +export const actionForHide = getApplicationMenuOperationSystemActionInjectable({ + id: "hide", + parentId: "mac", + orderNumber: 100, + actionName: "hide", +}); + +export const actionForHideOthers = getApplicationMenuOperationSystemActionInjectable({ + id: "hide-others", + parentId: "mac", + orderNumber: 110, + actionName: "hideOthers", +}); + +export const actionForUnhide = getApplicationMenuOperationSystemActionInjectable({ + id: "unhide", + parentId: "mac", + orderNumber: 120, + actionName: "unhide", +}); diff --git a/src/features/application-menu/main/menu-items/special-menu-for-mac-application/primary-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/primary-menu-item.injectable.ts new file mode 100644 index 0000000000..b85df1d511 --- /dev/null +++ b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/primary-menu-item.injectable.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../application-menu-item-injection-token"; +import appNameInjectable from "../../../../../common/vars/app-name.injectable"; +import isMacInjectable from "../../../../../common/vars/is-mac.injectable"; + +const primaryMenuItemInjectable = getInjectable({ + id: "primary-application-menu-item", + + instantiate: (di) => { + const appName = di.inject(appNameInjectable); + const isMac = di.inject(isMacInjectable); + + return { + kind: "top-level-menu" as const, + parentId: "root" as const, + id: "mac", + orderNumber: 10, + label: appName, + isShown: isMac, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default primaryMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/special-menu-for-mac-application/quit-application/quit-application-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/quit-application/quit-application-menu-item.injectable.ts new file mode 100644 index 0000000000..71f7cde125 --- /dev/null +++ b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/quit-application/quit-application-menu-item.injectable.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import stopServicesAndExitAppInjectable from "../../../../../../main/stop-services-and-exit-app.injectable"; +import isMacInjectable from "../../../../../../common/vars/is-mac.injectable"; + +const quitApplicationMenuItemInjectable = getInjectable({ + id: "quit-application-menu-item", + + instantiate: (di) => { + const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable); + const isMac = di.inject(isMacInjectable); + + return { + kind: "clickable-menu-item" as const, + id: "quit", + label: "Quit", + + parentId: isMac ? "mac" : "file", + orderNumber: isMac ? 140 : 70, + keyboardShortcut: isMac ? "Cmd+Q" : "Alt+F4", + + onClick: () => { + stopServicesAndExitApp(); + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default quitApplicationMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/special-menu-for-mac-application/separators/separators.injectable.ts b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/separators/separators.injectable.ts new file mode 100644 index 0000000000..e878df9210 --- /dev/null +++ b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/separators/separators.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { + getApplicationMenuSeparatorInjectable, +} from "../../get-application-menu-separator-injectable"; + +export const separator1 = getApplicationMenuSeparatorInjectable({ + id: "separator-1", + parentId: "mac", + orderNumber: 30, +}); + +export const separator2 = getApplicationMenuSeparatorInjectable({ + id: "separator-2", + parentId: "mac", + orderNumber: 70, +}); + +export const separator3 = getApplicationMenuSeparatorInjectable({ + id: "separator-3", + parentId: "mac", + orderNumber: 90, +}); + +export const separator4 = getApplicationMenuSeparatorInjectable({ + id: "separator-4", + parentId: "mac", + orderNumber: 130, +}); diff --git a/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/about-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/about-menu-item.injectable.ts new file mode 100644 index 0000000000..6e269a9e3a --- /dev/null +++ b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/about-menu-item.injectable.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import productNameInjectable from "../../../../../../common/vars/product-name.injectable"; +import showAboutInjectable from "./show-about.injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import isMacInjectable from "../../../../../../common/vars/is-mac.injectable"; + +const aboutMenuItemInjectable = getInjectable({ + id: "about-menu-item", + + instantiate: (di) => { + const productName = di.inject(productNameInjectable); + const showAbout = di.inject(showAboutInjectable); + const isMac = di.inject(isMacInjectable); + + return { + kind: "clickable-menu-item" as const, + id: "about", + parentId: isMac ? "mac" : "help", + orderNumber: isMac ? 10 : 40, + label: `About ${productName}`, + + onClick() { + showAbout(); + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default aboutMenuItemInjectable; diff --git a/src/main/menu/show-about.injectable.ts b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts similarity index 63% rename from src/main/menu/show-about.injectable.ts rename to src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts index e19c2b0bfa..a91e2af337 100644 --- a/src/main/menu/show-about.injectable.ts +++ b/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts @@ -3,13 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import showMessagePopupInjectable from "../electron-app/features/show-message-popup.injectable"; -import isWindowsInjectable from "../../common/vars/is-windows.injectable"; -import appNameInjectable from "../../common/vars/app-name.injectable"; -import productNameInjectable from "../../common/vars/product-name.injectable"; -import buildVersionInjectable from "../vars/build-version/build-version.injectable"; -import extensionApiVersionInjectable from "../../common/vars/extension-api-version.injectable"; -import applicationCopyrightInjectable from "../../common/vars/application-copyright.injectable"; +import showMessagePopupInjectable from "../../../../../../main/electron-app/features/show-message-popup.injectable"; +import isWindowsInjectable from "../../../../../../common/vars/is-windows.injectable"; +import appNameInjectable from "../../../../../../common/vars/app-name.injectable"; +import productNameInjectable from "../../../../../../common/vars/product-name.injectable"; +import buildVersionInjectable from "../../../../../../main/vars/build-version/build-version.injectable"; +import extensionApiVersionInjectable from "../../../../../../common/vars/extension-api-version.injectable"; +import applicationCopyrightInjectable from "../../../../../../common/vars/application-copyright.injectable"; const showAboutInjectable = getInjectable({ id: "show-about", diff --git a/src/features/application-menu/main/menu-items/view/go-back/go-back-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/view/go-back/go-back-menu-item.injectable.ts new file mode 100644 index 0000000000..42860bdc5c --- /dev/null +++ b/src/features/application-menu/main/menu-items/view/go-back/go-back-menu-item.injectable.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import { webContents } from "electron"; + +const goBackMenuItemInjectable = getInjectable({ + id: "go-back-menu-item", + + instantiate: () => ({ + kind: "clickable-menu-item" as const, + parentId: "view", + id: "go-back", + orderNumber: 40, + label: "Back", + keyboardShortcut: "CmdOrCtrl+[", + + onClick: () => { + webContents + .getAllWebContents() + .filter((wc) => wc.getType() === "window") + .forEach((wc) => wc.goBack()); + }, + }), + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default goBackMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/view/go-forward/go-forward-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/view/go-forward/go-forward-menu-item.injectable.ts new file mode 100644 index 0000000000..e37b8d1cf7 --- /dev/null +++ b/src/features/application-menu/main/menu-items/view/go-forward/go-forward-menu-item.injectable.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import { webContents } from "electron"; + +const goForwardMenuItemInjectable = getInjectable({ + id: "go-forward-menu-item", + + instantiate: () => ({ + kind: "clickable-menu-item" as const, + parentId: "view", + id: "go-forward", + orderNumber: 50, + label: "Forward", + keyboardShortcut: "CmdOrCtrl+]", + + onClick: () => { + webContents + .getAllWebContents() + .filter((wc) => wc.getType() === "window") + .forEach((wc) => wc.goForward()); + }, + }), + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default goForwardMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/view/navigate-to-catalog/navigate-to-catalog-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/view/navigate-to-catalog/navigate-to-catalog-menu-item.injectable.ts new file mode 100644 index 0000000000..eb414fe1ec --- /dev/null +++ b/src/features/application-menu/main/menu-items/view/navigate-to-catalog/navigate-to-catalog-menu-item.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import navigateToCatalogInjectable from "../../../../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; + +const navigateToCatalogMenuItemInjectable = getInjectable({ + id: "navigate-to-catalog-menu-item", + + instantiate: (di) => { + const navigateToCatalog = di.inject(navigateToCatalogInjectable); + + return { + kind: "clickable-menu-item" as const, + parentId: "view", + id: "navigate-to-catalog", + orderNumber: 10, + label: "Catalog", + keyboardShortcut: "Shift+CmdOrCtrl+C", + + onClick: () => { + navigateToCatalog(); + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default navigateToCatalogMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/view/open-command-palette/open-command-palette-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/view/open-command-palette/open-command-palette-menu-item.injectable.ts new file mode 100644 index 0000000000..ac0bd77bfc --- /dev/null +++ b/src/features/application-menu/main/menu-items/view/open-command-palette/open-command-palette-menu-item.injectable.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import broadcastMessageInjectable from "../../../../../../common/ipc/broadcast-message.injectable"; + +const openCommandPaletteMenuItemInjectable = getInjectable({ + id: "open-command-palette-menu-item", + + instantiate: (di) => { + const broadcastMessage = di.inject(broadcastMessageInjectable); + + return { + kind: "clickable-menu-item" as const, + parentId: "view", + id: "open-command-palette", + orderNumber: 20, + label: "Command Palette...", + keyboardShortcut: "Shift+CmdOrCtrl+P", + + onClick(_m, _b, event) { + /** + * Don't broadcast unless it was triggered by menu iteration so that + * there aren't double events in renderer + * + * NOTE: this `?` is required because of a bug in playwright. https://github.com/microsoft/playwright/issues/10554 + */ + if (!event?.triggeredByAccelerator) { + broadcastMessage("command-palette:open"); + } + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default openCommandPaletteMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/view/operation-system-actions/operation-system-actions.injectable.ts b/src/features/application-menu/main/menu-items/view/operation-system-actions/operation-system-actions.injectable.ts new file mode 100644 index 0000000000..a7969d94f7 --- /dev/null +++ b/src/features/application-menu/main/menu-items/view/operation-system-actions/operation-system-actions.injectable.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { + getApplicationMenuOperationSystemActionInjectable, +} from "../../get-application-menu-operation-system-action-injectable"; + +export const actionForToggleDevTools = getApplicationMenuOperationSystemActionInjectable({ + id: "toggle-dev-tools", + parentId: "view", + orderNumber: 70, + actionName: "toggleDevTools", +}); + +export const actionForResetZoom = getApplicationMenuOperationSystemActionInjectable({ + id: "reset-zoom", + parentId: "view", + orderNumber: 90, + actionName: "resetZoom", +}); + +export const actionForZoomIn = getApplicationMenuOperationSystemActionInjectable({ + id: "zoom-in", + parentId: "view", + orderNumber: 100, + actionName: "zoomIn", +}); + +export const actionForZoomOut = getApplicationMenuOperationSystemActionInjectable({ + id: "zoom-out", + parentId: "view", + orderNumber: 110, + actionName: "zoomOut", +}); + +export const actionForToggleFullScreen = getApplicationMenuOperationSystemActionInjectable({ + id: "toggle-full-screen", + parentId: "view", + orderNumber: 130, + actionName: "togglefullscreen", +}); diff --git a/src/features/application-menu/main/menu-items/view/reload/reload-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/view/reload/reload-menu-item.injectable.ts new file mode 100644 index 0000000000..683daf666a --- /dev/null +++ b/src/features/application-menu/main/menu-items/view/reload/reload-menu-item.injectable.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../../application-menu-item-injection-token"; +import reloadCurrentApplicationWindowInjectable from "../../../../../../main/start-main-application/lens-window/reload-current-application-window.injectable"; + +const reloadMenuItemInjectable = getInjectable({ + id: "reload-menu-item", + + instantiate: (di) => { + const reloadApplicationWindow = di.inject( + reloadCurrentApplicationWindowInjectable, + ); + + return { + kind: "clickable-menu-item" as const, + parentId: "view", + id: "reload", + orderNumber: 60, + label: "Reload", + keyboardShortcut: "CmdOrCtrl+R", + + onClick: () => { + reloadApplicationWindow(); + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default reloadMenuItemInjectable; diff --git a/src/features/application-menu/main/menu-items/view/separators/separators.injectable.ts b/src/features/application-menu/main/menu-items/view/separators/separators.injectable.ts new file mode 100644 index 0000000000..00584ea384 --- /dev/null +++ b/src/features/application-menu/main/menu-items/view/separators/separators.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { + getApplicationMenuSeparatorInjectable, +} from "../../get-application-menu-separator-injectable"; + +export const separator1 = getApplicationMenuSeparatorInjectable({ + id: "separator-1-for-view", + parentId: "view", + orderNumber: 30, +}); + +export const separator2 = getApplicationMenuSeparatorInjectable({ + id: "separator-2-for-view", + parentId: "view", + orderNumber: 80, +}); + +export const separator3 = getApplicationMenuSeparatorInjectable({ + id: "separator-3-for-view", + parentId: "view", + orderNumber: 120, +}); diff --git a/src/features/application-menu/main/menu-items/view/view-menu-item.injectable.ts b/src/features/application-menu/main/menu-items/view/view-menu-item.injectable.ts new file mode 100644 index 0000000000..f45fbf7ebc --- /dev/null +++ b/src/features/application-menu/main/menu-items/view/view-menu-item.injectable.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationMenuItemInjectionToken from "../application-menu-item-injection-token"; + +const viewMenuItemInjectable = getInjectable({ + id: "view-application-menu-item", + + instantiate: () => ({ + kind: "top-level-menu" as const, + parentId: "root" as const, + id: "view", + orderNumber: 40, + label: "View", + }), + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default viewMenuItemInjectable; diff --git a/src/main/menu/menu-registration.ts b/src/features/application-menu/main/menu-registration.ts similarity index 100% rename from src/main/menu/menu-registration.ts rename to src/features/application-menu/main/menu-registration.ts diff --git a/src/main/menu/menu.ts b/src/features/application-menu/main/menu.ts similarity index 100% rename from src/main/menu/menu.ts rename to src/features/application-menu/main/menu.ts diff --git a/src/features/application-menu/main/populate-application-menu.global-override-for-injectable.ts b/src/features/application-menu/main/populate-application-menu.global-override-for-injectable.ts new file mode 100644 index 0000000000..acd4f5f14e --- /dev/null +++ b/src/features/application-menu/main/populate-application-menu.global-override-for-injectable.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import populateApplicationMenuInjectable from "./populate-application-menu.injectable"; +import { getGlobalOverride } from "../../../common/test-utils/get-global-override"; + +export default getGlobalOverride(populateApplicationMenuInjectable, () => () => {}); diff --git a/src/features/application-menu/main/populate-application-menu.injectable.ts b/src/features/application-menu/main/populate-application-menu.injectable.ts new file mode 100644 index 0000000000..8d744b4ded --- /dev/null +++ b/src/features/application-menu/main/populate-application-menu.injectable.ts @@ -0,0 +1,110 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Menu } from "electron"; +import type { MenuItemOpts } from "./application-menu-items.injectable"; +import type { Composite } from "../../../common/utils/composite/get-composite/get-composite"; +import type { ApplicationMenuItemTypes } from "./menu-items/application-menu-item-injection-token"; +import { pipeline } from "@ogre-tools/fp"; +import { map, sortBy } from "lodash/fp"; +import type { MenuItemRoot } from "./application-menu-item-composite.injectable"; +import { checkThatAllDiscriminablesAreExhausted } from "../../../common/utils/composable-responsibilities/discriminable/discriminable"; + +const populateApplicationMenuInjectable = getInjectable({ + id: "populate-application-menu", + + instantiate: + () => (composite: Composite) => { + const electronTemplate = getApplicationMenuTemplate(composite); + const menu = Menu.buildFromTemplate(electronTemplate); + + Menu.setApplicationMenu(menu); + }, + + causesSideEffects: true, +}); + +export default populateApplicationMenuInjectable; + +export const getApplicationMenuTemplate = (composite: Composite) => { + const topLevelMenus = composite.children.filter( + (x): x is Composite => x.value.kind !== "root", + ); + + return topLevelMenus.map(toHierarchicalElectronMenuItem); +}; + +const toHierarchicalElectronMenuItem = ( + composite: Composite, +): MenuItemOpts => { + const value = composite.value; + + switch (value.kind) { + case "top-level-menu": { + const { id } = composite; + const { label, role } = value; + + return { + ...(id ? { id } : {}), + ...(role ? { role } : {}), + label, + + submenu: pipeline( + composite.children, + sortBy((childComposite) => childComposite.value.orderNumber), + map(toHierarchicalElectronMenuItem), + ), + }; + } + + case "sub-menu": { + const { id } = composite; + const { label } = value; + + return { + ...(id ? { id } : {}), + label, + + submenu: pipeline( + composite.children, + sortBy((childComposite) => childComposite.value.orderNumber), + map(toHierarchicalElectronMenuItem), + ), + }; + } + + case "clickable-menu-item": { + const { id } = composite; + const { label, onClick, keyboardShortcut } = value; + + return { + ...(id ? { id } : {}), + ...(label ? { label } : {}), + ...(keyboardShortcut ? { accelerator: keyboardShortcut }: {}), + click: onClick, + }; + } + + case "os-action-menu-item": { + const { label, keyboardShortcut, actionName } = value; + + return { + ...(label ? { label } : {}), + ...(keyboardShortcut ? { accelerator: keyboardShortcut } : {}), + role: actionName, + }; + } + + case "separator": { + return { + type: "separator", + }; + } + + default: { + throw checkThatAllDiscriminablesAreExhausted(value); + } + } +}; diff --git a/src/main/menu/start-application-menu.injectable.ts b/src/features/application-menu/main/start-application-menu.injectable.ts similarity index 68% rename from src/main/menu/start-application-menu.injectable.ts rename to src/features/application-menu/main/start-application-menu.injectable.ts index 3365223c86..77fb31cb2a 100644 --- a/src/main/menu/start-application-menu.injectable.ts +++ b/src/features/application-menu/main/start-application-menu.injectable.ts @@ -3,15 +3,15 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationMenuInjectable from "./application-menu.injectable"; -import { onLoadOfApplicationInjectionToken } from "../start-main-application/runnable-tokens/on-load-of-application-injection-token"; +import applicationMenuReactivityInjectable from "./application-menu-reactivity.injectable"; +import { onLoadOfApplicationInjectionToken } from "../../../main/start-main-application/runnable-tokens/on-load-of-application-injection-token"; const startApplicationMenuInjectable = getInjectable({ id: "start-application-menu", instantiate: (di) => { const applicationMenu = di.inject( - applicationMenuInjectable, + applicationMenuReactivityInjectable, ); return { diff --git a/src/main/menu/stop-application-menu.injectable.ts b/src/features/application-menu/main/stop-application-menu.injectable.ts similarity index 68% rename from src/main/menu/stop-application-menu.injectable.ts rename to src/features/application-menu/main/stop-application-menu.injectable.ts index 73ed462242..63abccadf9 100644 --- a/src/main/menu/stop-application-menu.injectable.ts +++ b/src/features/application-menu/main/stop-application-menu.injectable.ts @@ -3,15 +3,15 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationMenuInjectable from "./application-menu.injectable"; -import { beforeQuitOfBackEndInjectionToken } from "../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token"; +import applicationMenuReactivityInjectable from "./application-menu-reactivity.injectable"; +import { beforeQuitOfBackEndInjectionToken } from "../../../main/start-main-application/runnable-tokens/before-quit-of-back-end-injection-token"; const stopApplicationMenuInjectable = getInjectable({ id: "stop-application-menu", instantiate: (di) => { const applicationMenu = di.inject( - applicationMenuInjectable, + applicationMenuReactivityInjectable, ); return { diff --git a/src/features/application-update/__snapshots__/installing-update.test.ts.snap b/src/features/application-update/__snapshots__/installing-update.test.ts.snap index 42b59bfab2..1d7c144ad5 100644 --- a/src/features/application-update/__snapshots__/installing-update.test.ts.snap +++ b/src/features/application-update/__snapshots__/installing-update.test.ts.snap @@ -12,43 +12,61 @@ exports[`installing update when started renders 1`] = `
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - - - arrow_back - - - - - arrow_forward - - - +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+ +
-
- - - home - - - - - arrow_back - - - - - arrow_forward - - - +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+ +
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
{ it("when checking for updates using application menu, sends event to analytics for being checked from application menu", async () => { analyticsListenerMock.mockClear(); - builder.applicationMenu.click("root.check-for-updates"); + builder.applicationMenu.click("root", "mac", "check-for-updates"); expect(analyticsListenerMock.mock.calls).toEqual([ [ diff --git a/src/features/application-update/child-features/application-update-using-application-menu/main/check-for-updates-menu-item.injectable.ts b/src/features/application-update/child-features/application-update-using-application-menu/main/check-for-updates-menu-item.injectable.ts new file mode 100644 index 0000000000..fe997e62fb --- /dev/null +++ b/src/features/application-update/child-features/application-update-using-application-menu/main/check-for-updates-menu-item.injectable.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; + +import applicationMenuItemInjectionToken from "../../../../application-menu/main/menu-items/application-menu-item-injection-token"; +import processCheckingForUpdatesInjectable from "../../../main/process-checking-for-updates.injectable"; +import showApplicationWindowInjectable from "../../../../../main/start-main-application/lens-window/show-application-window.injectable"; +import updatingIsEnabledInjectable from "../../../main/updating-is-enabled/updating-is-enabled.injectable"; +import isMacInjectable from "../../../../../common/vars/is-mac.injectable"; + +const checkForUpdatesMenuItemInjectable = getInjectable({ + id: "check-for-updates-menu-item", + + instantiate: (di) => { + const processCheckingForUpdates = di.inject( + processCheckingForUpdatesInjectable, + ); + + const showApplicationWindow = di.inject(showApplicationWindowInjectable); + + const updatingIsEnabled = di.inject(updatingIsEnabledInjectable); + const isMac = di.inject(isMacInjectable); + + return { + kind: "clickable-menu-item" as const, + id: "check-for-updates", + parentId: isMac ? "mac" : "help", + orderNumber: isMac ? 20 : 50, + label: "Check for updates", + isShown: updatingIsEnabled, + + onClick: () => { + // Todo: implement using async/await + processCheckingForUpdates("application-menu").then(() => + showApplicationWindow(), + ); + }, + }; + }, + + injectionToken: applicationMenuItemInjectionToken, +}); + +export default checkForUpdatesMenuItemInjectable; diff --git a/src/features/application-update/__snapshots__/installing-update-using-topbar-button.test.tsx.snap b/src/features/application-update/child-features/application-update-using-top-bar/__snapshots__/installing-update-using-topbar-button.test.tsx.snap similarity index 75% rename from src/features/application-update/__snapshots__/installing-update-using-topbar-button.test.tsx.snap rename to src/features/application-update/child-features/application-update-using-top-bar/__snapshots__/installing-update-using-topbar-button.test.tsx.snap index 4e33733f69..a092af4468 100644 --- a/src/features/application-update/__snapshots__/installing-update-using-topbar-button.test.tsx.snap +++ b/src/features/application-update/child-features/application-update-using-top-bar/__snapshots__/installing-update-using-topbar-button.test.tsx.snap @@ -12,61 +12,83 @@ exports[`encourage user to update when sufficient time passed since update was d
- - - home - - - - - arrow_back - - - - - arrow_forward - - - +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+ +
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
{ + const warningLevel = di.inject(updateWarningLevelInjectable); + + return { + id: "update-application", + isShown: computed(() => !!warningLevel.get()), + orderNumber: 50, + Component: UpdateButton, + }; + }, + + injectionToken: topBarItemOnRightSideInjectionToken, +}); + +export default updateApplicationTopBarItemInjectable; diff --git a/src/renderer/components/update-button/index.ts b/src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/index.ts similarity index 100% rename from src/renderer/components/update-button/index.ts rename to src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/index.ts diff --git a/src/renderer/components/update-button/styles.module.scss b/src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/styles.module.scss similarity index 100% rename from src/renderer/components/update-button/styles.module.scss rename to src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/styles.module.scss diff --git a/src/renderer/components/update-button/update-button.tsx b/src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/update-button.tsx similarity index 80% rename from src/renderer/components/update-button/update-button.tsx rename to src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/update-button.tsx index 029b58f3fe..317e64936b 100644 --- a/src/renderer/components/update-button/update-button.tsx +++ b/src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/update-button.tsx @@ -7,15 +7,15 @@ import styles from "./styles.module.scss"; import type { HTMLAttributes } from "react"; import React, { useState } from "react"; -import { Menu, MenuItem } from "../menu"; -import { cssNames } from "../../utils"; -import type { IconProps } from "../icon"; -import { Icon } from "../icon"; +import { Menu, MenuItem } from "../../../../../../../renderer/components/menu"; +import { cssNames } from "../../../../../../../renderer/utils"; +import type { IconProps } from "../../../../../../../renderer/components/icon"; +import { Icon } from "../../../../../../../renderer/components/icon"; import { withInjectables } from "@ogre-tools/injectable-react"; import { observer } from "mobx-react"; import type { IComputedValue } from "mobx"; -import restartAndInstallUpdateInjectable from "./restart-and-install-update.injectable"; -import updateWarningLevelInjectable from "./update-warning-level.injectable"; +import restartAndInstallUpdateInjectable from "../../../../../renderer/restart-and-install-update.injectable"; +import updateWarningLevelInjectable from "../update-warning-level.injectable"; interface UpdateButtonProps extends HTMLAttributes {} @@ -34,10 +34,6 @@ export const NonInjectedUpdateButton = observer(({ warningLevel, update, id }: U setOpened(!opened); }; - if (!level) { - return null; - } - return ( <>
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- - - home - - - - - arrow_back - - - - - arrow_forward - - - +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+ +
-
- - - home - - - - - arrow_back - - - - - arrow_forward - - - +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+ +
-
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
{ let builder: ApplicationBuilder; diff --git a/src/main/application-update/tray-icons/checking-for-updates-tray-icon.injectable.ts b/src/features/application-update/child-features/application-update-using-tray/main/tray-icons/checking-for-updates-tray-icon.injectable.ts similarity index 66% rename from src/main/application-update/tray-icons/checking-for-updates-tray-icon.injectable.ts rename to src/features/application-update/child-features/application-update-using-tray/main/tray-icons/checking-for-updates-tray-icon.injectable.ts index 5d22c98bba..c955199c21 100644 --- a/src/main/application-update/tray-icons/checking-for-updates-tray-icon.injectable.ts +++ b/src/features/application-update/child-features/application-update-using-tray/main/tray-icons/checking-for-updates-tray-icon.injectable.ts @@ -4,10 +4,10 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import getTrayIconPathInjectable from "../../tray/menu-icon/get-tray-icon-path.injectable"; -import { trayIconInjectionToken } from "../../tray/menu-icon/tray-icon-injection-token"; -import updatesAreBeingDiscoveredInjectable from "../../../common/application-update/updates-are-being-discovered/updates-are-being-discovered.injectable"; -import updateIsBeingDownloadedInjectable from "../../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable"; +import getTrayIconPathInjectable from "../../../../../../main/tray/menu-icon/get-tray-icon-path.injectable"; +import { trayIconInjectionToken } from "../../../../../../main/tray/menu-icon/tray-icon-injection-token"; +import updatesAreBeingDiscoveredInjectable from "../../../../common/updates-are-being-discovered/updates-are-being-discovered.injectable"; +import updateIsBeingDownloadedInjectable from "../../../../common/update-is-being-downloaded/update-is-being-downloaded.injectable"; const checkingForUpdatesTrayIconInjectable = getInjectable({ id: "checking-for-updates-tray-icon", diff --git a/src/main/application-update/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts b/src/features/application-update/child-features/application-update-using-tray/main/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts similarity index 79% rename from src/main/application-update/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts rename to src/features/application-update/child-features/application-update-using-tray/main/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts index 7f097c0cbb..19d0495c38 100644 --- a/src/main/application-update/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts +++ b/src/features/application-update/child-features/application-update-using-tray/main/tray-icons/update-is-ready-to-be-installed-tray-icon.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import getTrayIconPathInjectable from "../../tray/menu-icon/get-tray-icon-path.injectable"; -import { trayIconInjectionToken } from "../../tray/menu-icon/tray-icon-injection-token"; +import getTrayIconPathInjectable from "../../../../../../main/tray/menu-icon/get-tray-icon-path.injectable"; +import { trayIconInjectionToken } from "../../../../../../main/tray/menu-icon/tray-icon-injection-token"; import updateIsReadyToBeInstalledInjectable from "../update-is-ready-to-be-installed.injectable"; const updateIsReadyToBeInstalledTrayIconInjectable = getInjectable({ diff --git a/src/main/application-update/check-for-updates-tray-item.injectable.ts b/src/features/application-update/child-features/application-update-using-tray/main/tray-items/check-for-updates-tray-item.injectable.ts similarity index 66% rename from src/main/application-update/check-for-updates-tray-item.injectable.ts rename to src/features/application-update/child-features/application-update-using-tray/main/tray-items/check-for-updates-tray-item.injectable.ts index 82bdbfbbd4..e904abaa99 100644 --- a/src/main/application-update/check-for-updates-tray-item.injectable.ts +++ b/src/features/application-update/child-features/application-update-using-tray/main/tray-items/check-for-updates-tray-item.injectable.ts @@ -4,18 +4,18 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import updatingIsEnabledInjectable from "./updating-is-enabled.injectable"; -import { trayMenuItemInjectionToken } from "../tray/tray-menu-item/tray-menu-item-injection-token"; -import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable"; -import discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.injectable"; -import updateIsBeingDownloadedInjectable from "../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable"; -import updatesAreBeingDiscoveredInjectable from "../../common/application-update/updates-are-being-discovered/updates-are-being-discovered.injectable"; -import progressOfUpdateDownloadInjectable from "../../common/application-update/progress-of-update-download/progress-of-update-download.injectable"; +import updatingIsEnabledInjectable from "../../../../main/updating-is-enabled/updating-is-enabled.injectable"; +import { trayMenuItemInjectionToken } from "../../../../../../main/tray/tray-menu-item/tray-menu-item-injection-token"; +import showApplicationWindowInjectable from "../../../../../../main/start-main-application/lens-window/show-application-window.injectable"; +import discoveredUpdateVersionInjectable from "../../../../common/discovered-update-version/discovered-update-version.injectable"; +import updateIsBeingDownloadedInjectable from "../../../../common/update-is-being-downloaded/update-is-being-downloaded.injectable"; +import updatesAreBeingDiscoveredInjectable from "../../../../common/updates-are-being-discovered/updates-are-being-discovered.injectable"; +import progressOfUpdateDownloadInjectable from "../../../../common/progress-of-update-download/progress-of-update-download.injectable"; import assert from "assert"; -import processCheckingForUpdatesInjectable from "./check-for-updates/process-checking-for-updates.injectable"; -import { withErrorSuppression } from "../../common/utils/with-error-suppression/with-error-suppression"; +import processCheckingForUpdatesInjectable from "../../../../main/process-checking-for-updates.injectable"; +import { withErrorSuppression } from "../../../../../../common/utils/with-error-suppression/with-error-suppression"; import { pipeline } from "@ogre-tools/fp"; -import withErrorLoggingInjectable from "../../common/utils/with-error-logging/with-error-logging.injectable"; +import withErrorLoggingInjectable from "../../../../../../common/utils/with-error-logging/with-error-logging.injectable"; const checkForUpdatesTrayItemInjectable = getInjectable({ id: "check-for-updates-tray-item", diff --git a/src/main/application-update/install-application-update-tray-item.injectable.ts b/src/features/application-update/child-features/application-update-using-tray/main/tray-items/install-application-update-tray-item.injectable.ts similarity index 68% rename from src/main/application-update/install-application-update-tray-item.injectable.ts rename to src/features/application-update/child-features/application-update-using-tray/main/tray-items/install-application-update-tray-item.injectable.ts index ba71d5cfe7..86ae7d676f 100644 --- a/src/main/application-update/install-application-update-tray-item.injectable.ts +++ b/src/features/application-update/child-features/application-update-using-tray/main/tray-items/install-application-update-tray-item.injectable.ts @@ -4,13 +4,13 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import { trayMenuItemInjectionToken } from "../tray/tray-menu-item/tray-menu-item-injection-token"; -import discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.injectable"; -import { withErrorSuppression } from "../../common/utils/with-error-suppression/with-error-suppression"; +import { trayMenuItemInjectionToken } from "../../../../../../main/tray/tray-menu-item/tray-menu-item-injection-token"; +import discoveredUpdateVersionInjectable from "../../../../common/discovered-update-version/discovered-update-version.injectable"; +import { withErrorSuppression } from "../../../../../../common/utils/with-error-suppression/with-error-suppression"; import { pipeline } from "@ogre-tools/fp"; -import withErrorLoggingInjectable from "../../common/utils/with-error-logging/with-error-logging.injectable"; -import quitAndInstallUpdateInjectable from "./quit-and-install-update.injectable"; -import updateIsReadyToBeInstalledInjectable from "./update-is-ready-to-be-installed.injectable"; +import withErrorLoggingInjectable from "../../../../../../common/utils/with-error-logging/with-error-logging.injectable"; +import quitAndInstallUpdateInjectable from "../../../../main/quit-and-install-update.injectable"; +import updateIsReadyToBeInstalledInjectable from "../update-is-ready-to-be-installed.injectable"; const installApplicationUpdateTrayItemInjectable = getInjectable({ id: "install-update-tray-item", diff --git a/src/main/application-update/update-is-ready-to-be-installed.injectable.ts b/src/features/application-update/child-features/application-update-using-tray/main/update-is-ready-to-be-installed.injectable.ts similarity index 71% rename from src/main/application-update/update-is-ready-to-be-installed.injectable.ts rename to src/features/application-update/child-features/application-update-using-tray/main/update-is-ready-to-be-installed.injectable.ts index 157d1bbd6c..39e23f51e6 100644 --- a/src/main/application-update/update-is-ready-to-be-installed.injectable.ts +++ b/src/features/application-update/child-features/application-update-using-tray/main/update-is-ready-to-be-installed.injectable.ts @@ -4,8 +4,8 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.injectable"; -import updateIsBeingDownloadedInjectable from "../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable"; +import discoveredUpdateVersionInjectable from "../../../common/discovered-update-version/discovered-update-version.injectable"; +import updateIsBeingDownloadedInjectable from "../../../common/update-is-being-downloaded/update-is-being-downloaded.injectable"; const updateIsReadyToBeInstalledInjectable = getInjectable({ id: "update-is-ready-to-be-installed", diff --git a/src/features/application-update/__snapshots__/force-user-to-update-when-too-long-time-since-update-was-downloaded.test.ts.snap b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/__snapshots__/force-user-to-update-when-too-long-time-since-update-was-downloaded.test.ts.snap similarity index 74% rename from src/features/application-update/__snapshots__/force-user-to-update-when-too-long-time-since-update-was-downloaded.test.ts.snap rename to src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/__snapshots__/force-user-to-update-when-too-long-time-since-update-was-downloaded.test.ts.snap index 69c8ff0ac1..ec70ad00a5 100644 --- a/src/features/application-update/__snapshots__/force-user-to-update-when-too-long-time-since-update-was-downloaded.test.ts.snap +++ b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/__snapshots__/force-user-to-update-when-too-long-time-since-update-was-downloaded.test.ts.snap @@ -12,61 +12,83 @@ exports[`force user to update when too long since update was downloaded when app
- - - home - - - - - arrow_back - - - - - arrow_forward - - - +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+ +
-
- - - home - - - - - arrow_back - - - - - arrow_forward - - - +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+ +
-
- - - home - - - - - arrow_back - - - - - arrow_forward - - - +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+ +
-
({ + id: "force-update-modal", + Component: () => null, + shouldRender: computed(() => false), + }), +); diff --git a/src/renderer/application-update/force-update-modal/force-update-modal-root-frame-component.injectable.ts b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/force-update-modal-root-frame-component.injectable.ts similarity index 83% rename from src/renderer/application-update/force-update-modal/force-update-modal-root-frame-component.injectable.ts rename to src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/force-update-modal-root-frame-component.injectable.ts index 9681aa72e0..9109f4bfe3 100644 --- a/src/renderer/application-update/force-update-modal/force-update-modal-root-frame-component.injectable.ts +++ b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/force-update-modal-root-frame-component.injectable.ts @@ -4,10 +4,10 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import { rootFrameChildComponentInjectionToken } from "../../frames/root-frame/root-frame-child-component-injection-token"; +import { rootFrameChildComponentInjectionToken } from "../../../../../../renderer/frames/root-frame/root-frame-child-component-injection-token"; import { ForceUpdateModal } from "./force-update-modal"; import timeSinceUpdateWasDownloadedInjectable from "./time-since-update-was-downloaded.injectable"; -import updateDownloadedDateTimeInjectable from "../../../common/application-update/update-downloaded-date-time/update-downloaded-date-time.injectable"; +import updateDownloadedDateTimeInjectable from "../../../../common/update-downloaded-date-time/update-downloaded-date-time.injectable"; import timeAfterUpdateMustBeInstalledInjectable from "./time-after-update-must-be-installed.injectable"; const forceUpdateModalRootFrameComponentInjectable = getInjectable({ diff --git a/src/renderer/application-update/force-update-modal/force-update-modal.module.scss b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/force-update-modal.module.scss similarity index 100% rename from src/renderer/application-update/force-update-modal/force-update-modal.module.scss rename to src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/force-update-modal.module.scss diff --git a/src/renderer/application-update/force-update-modal/force-update-modal.tsx b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/force-update-modal.tsx similarity index 84% rename from src/renderer/application-update/force-update-modal/force-update-modal.tsx rename to src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/force-update-modal.tsx index 22c322be82..83daa606e3 100644 --- a/src/renderer/application-update/force-update-modal/force-update-modal.tsx +++ b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/force-update-modal.tsx @@ -4,13 +4,13 @@ */ import { withInjectables } from "@ogre-tools/injectable-react"; import React from "react"; -import restartAndInstallUpdateInjectable from "../../components/update-button/restart-and-install-update.injectable"; -import { Countdown } from "../../components/countdown/countdown"; +import restartAndInstallUpdateInjectable from "../../../../renderer/restart-and-install-update.injectable"; +import { Countdown } from "../../../../../../renderer/components/countdown/countdown"; import type { IComputedValue } from "mobx"; import { observer } from "mobx-react"; import installUpdateCountdownInjectable from "./install-update-countdown.injectable"; -import { Dialog } from "../../components/dialog"; -import { Button } from "../../components/button"; +import { Dialog } from "../../../../../../renderer/components/dialog"; +import { Button } from "../../../../../../renderer/components/button"; import styles from "./force-update-modal.module.scss"; interface Dependencies { diff --git a/src/renderer/application-update/force-update-modal/install-update-countdown.injectable.ts b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/install-update-countdown.injectable.ts similarity index 78% rename from src/renderer/application-update/force-update-modal/install-update-countdown.injectable.ts rename to src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/install-update-countdown.injectable.ts index eb1ee53371..f8e99b9ac1 100644 --- a/src/renderer/application-update/force-update-modal/install-update-countdown.injectable.ts +++ b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/install-update-countdown.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import countdownStateInjectable from "../../components/countdown/countdown-state.injectable"; +import countdownStateInjectable from "../../../../../../renderer/components/countdown/countdown-state.injectable"; import secondsAfterInstallStartsInjectable from "./seconds-after-install-starts.injectable"; -import restartAndInstallUpdateInjectable from "../../components/update-button/restart-and-install-update.injectable"; +import restartAndInstallUpdateInjectable from "../../../../renderer/restart-and-install-update.injectable"; const installUpdateCountdownInjectable = getInjectable({ id: "install-update-countdown", diff --git a/src/renderer/application-update/force-update-modal/seconds-after-install-starts.injectable.ts b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/seconds-after-install-starts.injectable.ts similarity index 100% rename from src/renderer/application-update/force-update-modal/seconds-after-install-starts.injectable.ts rename to src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/seconds-after-install-starts.injectable.ts diff --git a/src/renderer/application-update/force-update-modal/time-after-update-must-be-installed.injectable.ts b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/time-after-update-must-be-installed.injectable.ts similarity index 100% rename from src/renderer/application-update/force-update-modal/time-after-update-must-be-installed.injectable.ts rename to src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/time-after-update-must-be-installed.injectable.ts diff --git a/src/renderer/application-update/force-update-modal/time-since-update-was-downloaded.injectable.ts b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/time-since-update-was-downloaded.injectable.ts similarity index 78% rename from src/renderer/application-update/force-update-modal/time-since-update-was-downloaded.injectable.ts rename to src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/time-since-update-was-downloaded.injectable.ts index e89657a72c..8ac86c80ca 100644 --- a/src/renderer/application-update/force-update-modal/time-since-update-was-downloaded.injectable.ts +++ b/src/features/application-update/child-features/force-user-to-update-when-too-long-time-since-update-was-downloaded/renderer/force-update-modal/time-since-update-was-downloaded.injectable.ts @@ -6,8 +6,8 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { computed } from "mobx"; import moment from "moment"; -import updateDownloadedDateTimeInjectable from "../../../common/application-update/update-downloaded-date-time/update-downloaded-date-time.injectable"; -import { reactiveNow } from "../../../common/utils/reactive-now/reactive-now"; +import updateDownloadedDateTimeInjectable from "../../../../common/update-downloaded-date-time/update-downloaded-date-time.injectable"; +import { reactiveNow } from "../../../../../../common/utils/reactive-now/reactive-now"; const timeSinceUpdateWasDownloadedInjectable = getInjectable({ id: "time-since-update-was-downloaded", diff --git a/src/features/application-update/__snapshots__/periodical-checking-of-updates.test.ts.snap b/src/features/application-update/child-features/periodical-checking-of-updates/__snapshots__/periodical-checking-of-updates.test.ts.snap similarity index 78% rename from src/features/application-update/__snapshots__/periodical-checking-of-updates.test.ts.snap rename to src/features/application-update/child-features/periodical-checking-of-updates/__snapshots__/periodical-checking-of-updates.test.ts.snap index 3a9779a33a..36e52e2d5f 100644 --- a/src/features/application-update/__snapshots__/periodical-checking-of-updates.test.ts.snap +++ b/src/features/application-update/child-features/periodical-checking-of-updates/__snapshots__/periodical-checking-of-updates.test.ts.snap @@ -12,43 +12,61 @@ exports[`periodical checking of updates given updater is enabled and configurati
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
({ + start: async () => {}, + stop: async () => {}, + started: false, +})); diff --git a/src/main/application-update/periodical-check-for-updates/periodical-check-for-updates.injectable.ts b/src/features/application-update/child-features/periodical-checking-of-updates/main/periodical-check-for-updates.injectable.ts similarity index 74% rename from src/main/application-update/periodical-check-for-updates/periodical-check-for-updates.injectable.ts rename to src/features/application-update/child-features/periodical-checking-of-updates/main/periodical-check-for-updates.injectable.ts index b6b5f7b852..472cf31a06 100644 --- a/src/main/application-update/periodical-check-for-updates/periodical-check-for-updates.injectable.ts +++ b/src/features/application-update/child-features/periodical-checking-of-updates/main/periodical-check-for-updates.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable"; -import processCheckingForUpdatesInjectable from "../check-for-updates/process-checking-for-updates.injectable"; -import withOrphanPromiseInjectable from "../../../common/utils/with-orphan-promise/with-orphan-promise.injectable"; +import { getStartableStoppable } from "../../../../../common/utils/get-startable-stoppable"; +import processCheckingForUpdatesInjectable from "../../../main/process-checking-for-updates.injectable"; +import withOrphanPromiseInjectable from "../../../../../common/utils/with-orphan-promise/with-orphan-promise.injectable"; const periodicalCheckForUpdatesInjectable = getInjectable({ id: "periodical-check-for-updates", diff --git a/src/main/application-update/periodical-check-for-updates/start-checking-for-updates.injectable.ts b/src/features/application-update/child-features/periodical-checking-of-updates/main/start-checking-for-updates.injectable.ts similarity index 77% rename from src/main/application-update/periodical-check-for-updates/start-checking-for-updates.injectable.ts rename to src/features/application-update/child-features/periodical-checking-of-updates/main/start-checking-for-updates.injectable.ts index 15088b3173..0292d148df 100644 --- a/src/main/application-update/periodical-check-for-updates/start-checking-for-updates.injectable.ts +++ b/src/features/application-update/child-features/periodical-checking-of-updates/main/start-checking-for-updates.injectable.ts @@ -4,8 +4,8 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable"; -import updatingIsEnabledInjectable from "../updating-is-enabled.injectable"; -import { afterApplicationIsLoadedInjectionToken } from "../../start-main-application/runnable-tokens/after-application-is-loaded-injection-token"; +import updatingIsEnabledInjectable from "../../../main/updating-is-enabled/updating-is-enabled.injectable"; +import { afterApplicationIsLoadedInjectionToken } from "../../../../../main/start-main-application/runnable-tokens/after-application-is-loaded-injection-token"; const startCheckingForUpdatesInjectable = getInjectable({ id: "start-checking-for-updates", diff --git a/src/main/application-update/periodical-check-for-updates/stop-checking-for-updates.injectable.ts b/src/features/application-update/child-features/periodical-checking-of-updates/main/stop-checking-for-updates.injectable.ts similarity index 84% rename from src/main/application-update/periodical-check-for-updates/stop-checking-for-updates.injectable.ts rename to src/features/application-update/child-features/periodical-checking-of-updates/main/stop-checking-for-updates.injectable.ts index 15e71cc817..ff7607e7db 100644 --- a/src/main/application-update/periodical-check-for-updates/stop-checking-for-updates.injectable.ts +++ b/src/features/application-update/child-features/periodical-checking-of-updates/main/stop-checking-for-updates.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable"; -import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token"; +import { beforeQuitOfBackEndInjectionToken } from "../../../../../main/start-main-application/runnable-tokens/before-quit-of-back-end-injection-token"; const stopCheckingForUpdatesInjectable = getInjectable({ id: "stop-checking-for-updates", diff --git a/src/features/application-update/periodical-checking-of-updates.test.ts b/src/features/application-update/child-features/periodical-checking-of-updates/periodical-checking-of-updates.test.ts similarity index 80% rename from src/features/application-update/periodical-checking-of-updates.test.ts rename to src/features/application-update/child-features/periodical-checking-of-updates/periodical-checking-of-updates.test.ts index e5c7b1c17c..e38ebdcbfc 100644 --- a/src/features/application-update/periodical-checking-of-updates.test.ts +++ b/src/features/application-update/child-features/periodical-checking-of-updates/periodical-checking-of-updates.test.ts @@ -2,14 +2,14 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; -import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import type { ApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder"; import type { RenderResult } from "@testing-library/react"; -import electronUpdaterIsActiveInjectable from "../../main/electron-app/features/electron-updater-is-active.injectable"; -import publishIsConfiguredInjectable from "../../main/application-update/publish-is-configured.injectable"; -import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable"; -import periodicalCheckForUpdatesInjectable from "../../main/application-update/periodical-check-for-updates/periodical-check-for-updates.injectable"; -import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time"; +import electronUpdaterIsActiveInjectable from "../../../../main/electron-app/features/electron-updater-is-active.injectable"; +import publishIsConfiguredInjectable from "../../main/updating-is-enabled/publish-is-configured/publish-is-configured.injectable"; +import processCheckingForUpdatesInjectable from "../../main/process-checking-for-updates.injectable"; +import periodicalCheckForUpdatesInjectable from "./main/periodical-check-for-updates.injectable"; +import { advanceFakeTime, useFakeTime } from "../../../../common/test-utils/use-fake-time"; const ENOUGH_TIME = 1000 * 60 * 60 * 2; diff --git a/src/features/application-update/child-features/preferences/renderer/update-channel/update-channel-preference-block.injectable.ts b/src/features/application-update/child-features/preferences/renderer/update-channel/update-channel-preference-block.injectable.ts new file mode 100644 index 0000000000..aa4fe3f5fb --- /dev/null +++ b/src/features/application-update/child-features/preferences/renderer/update-channel/update-channel-preference-block.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { preferenceItemInjectionToken } from "../../../../../preferences/renderer/preference-items/preference-item-injection-token"; +import { UpdateChannel } from "./update-channel"; + +const updateChannelPreferenceBlockInjectable = getInjectable({ + id: "update-channel-preference-item", + + instantiate: () => ({ + kind: "block" as const, + id: "update-channel", + parentId: "application-page", + orderNumber: 50, + Component: UpdateChannel, + }), + + injectionToken: preferenceItemInjectionToken, +}); + +export default updateChannelPreferenceBlockInjectable; diff --git a/src/features/application-update/child-features/preferences/renderer/update-channel/update-channel.tsx b/src/features/application-update/child-features/preferences/renderer/update-channel/update-channel.tsx new file mode 100644 index 0000000000..339d54beb8 --- /dev/null +++ b/src/features/application-update/child-features/preferences/renderer/update-channel/update-channel.tsx @@ -0,0 +1,51 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import React from "react"; +import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import { Select } from "../../../../../../renderer/components/select"; +import { updateChannels } from "../../../../common/update-channels"; +import type { SelectedUpdateChannel } from "../../../../common/selected-update-channel/selected-update-channel.injectable"; +import selectedUpdateChannelInjectable from "../../../../common/selected-update-channel/selected-update-channel.injectable"; +import { pipeline } from "@ogre-tools/fp"; +import { map, toPairs } from "lodash/fp"; +import { observer } from "mobx-react"; + +interface Dependencies { + selectedUpdateChannel: SelectedUpdateChannel; +} + +const updateChannelOptions = pipeline( + toPairs(updateChannels), + + map(([, channel]) => ({ + value: channel.id, + label: channel.label, + })), +); + + +const NonInjectedUpdateChannel = observer(({ selectedUpdateChannel }: Dependencies) => ( +
+ + - -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -452,30 +289,256 @@ exports[`add custom helm repository in preferences when navigating to preference
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -723,231 +807,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -1313,231 +1460,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -2014,231 +2224,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -2612,231 +2885,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -3313,231 +3649,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -4196,231 +4595,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -4897,231 +5359,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -5780,231 +6305,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -6482,231 +7070,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -7080,231 +7731,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -7388,30 +7855,256 @@ exports[`add custom helm repository in preferences when navigating to preference
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -7667,231 +8381,61 @@ exports[`add custom helm repository in preferences when navigating to preference class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -7975,30 +8505,256 @@ exports[`add custom helm repository in preferences when navigating to preference
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -144,231 +165,61 @@ exports[`add helm repository from list in preferences when navigating to prefere class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -452,30 +289,256 @@ exports[`add helm repository from list in preferences when navigating to prefere
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -723,231 +807,61 @@ exports[`add helm repository from list in preferences when navigating to prefere class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some already active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some already active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -1313,231 +1460,61 @@ exports[`add helm repository from list in preferences when navigating to prefere class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some already active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some already active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -1956,231 +2166,61 @@ exports[`add helm repository from list in preferences when navigating to prefere class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some already active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some already active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -2546,231 +2819,61 @@ exports[`add helm repository from list in preferences when navigating to prefere class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some already active repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ Some already active repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -3136,231 +3472,61 @@ exports[`add helm repository from list in preferences when navigating to prefere class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -3444,30 +3596,256 @@ exports[`add helm repository from list in preferences when navigating to prefere
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -3715,231 +4114,61 @@ exports[`add helm repository from list in preferences when navigating to prefere class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some already active repository -
-
- some-url -
-
- + +
- delete - - -
- Remove +
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
-
-
- Some to be added repository +
+
+
+ Some already active repository +
+
+ some-url +
-
- some-other-url + + delete + + +
+ Remove
- - - delete - -
- Remove +
+
+ Some to be added repository +
+
+ some-other-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -4339,231 +4801,61 @@ exports[`add helm repository from list in preferences when navigating to prefere class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some already active repository -
-
- some-url -
-
- + +
- delete - - -
- Remove +
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
-
-
- Some to be added repository +
+
+
+ Some already active repository +
+
+ some-url +
-
- some-other-url + + delete + + +
+ Remove
- - - delete - -
- Remove +
+
+ Some to be added repository +
+
+ some-other-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -5026,231 +5551,61 @@ exports[`add helm repository from list in preferences when navigating to prefere class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- Some already active repository -
-
- some-url -
-
- + +
- delete - - -
- Remove +
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
-
-
- Some to be added repository +
+
+
+ Some already active repository +
+
+ some-url +
-
- some-other-url + + delete + + +
+ Remove
- - - delete - -
- Remove +
+
+ Some to be added repository +
+
+ some-other-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -5650,231 +6238,61 @@ exports[`add helm repository from list in preferences when navigating to prefere class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -5958,30 +6362,256 @@ exports[`add helm repository from list in preferences when navigating to prefere
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -144,231 +165,61 @@ exports[`listing active helm repositories in preferences when navigating to pref class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -452,30 +289,256 @@ exports[`listing active helm repositories in preferences when navigating to pref
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -723,231 +807,61 @@ exports[`listing active helm repositories in preferences when navigating to pref class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -1031,30 +931,256 @@ exports[`listing active helm repositories in preferences when navigating to pref
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -1302,230 +1449,272 @@ exports[`listing active helm repositories in preferences when navigating to pref class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download + +
+ +
+
+
+
+
+
+ Download mirror +
+ +
+ +
+
+
+ - + +
-
+
+
+
+
+
+
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
-
-
+
+
+
+

- Directory for binaries + Kubeconfig Syncs +

+
+ +
+
+ Synced Items
- -
-
-
- The directory to download binaries into. -
-
-
-
- Path to kubectl binary - -
-
- -
-
-
-
-
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
- Error updating Helm repositories: Some error + No files and folders have been synced yet
-
-
+ +
+
+
+
+

+ Helm Charts +

+
+
+
+ Error updating Helm repositories: Some error +
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -1773,231 +1983,61 @@ exports[`listing active helm repositories in preferences when navigating to pref class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -2081,30 +2107,256 @@ exports[`listing active helm repositories in preferences when navigating to pref
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -2352,230 +2625,272 @@ exports[`listing active helm repositories in preferences when navigating to pref class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download + +
+ +
+
+
+
+
+
+ Download mirror +
+ +
+ +
+
+
+ - + +
-
+
+
+
+
+
+
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
-
-
+
+
+
+

- Directory for binaries + Kubeconfig Syncs +

+
+ +
+
+ Synced Items
- -
-
-
- The directory to download binaries into. -
-
-
-
- Path to kubectl binary - -
-
- -
-
-
-
-
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
- Error when adding default Helm repository: Some error + No files and folders have been synced yet
-
-
+ +
+
+
+
+

+ Helm Charts +

+
+
+
+ Error when adding default Helm repository: Some error +
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -2823,231 +3159,61 @@ exports[`listing active helm repositories in preferences when navigating to pref class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -3131,30 +3283,256 @@ exports[`listing active helm repositories in preferences when navigating to pref
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -3402,231 +3801,61 @@ exports[`listing active helm repositories in preferences when navigating to pref class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- some-repository -
-
- some-repository-url -
-
- + +
- delete - - -
- Remove +
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
-
-
- some-other-repository +
+
+
+ some-repository +
+
+ some-repository-url +
-
- some-other-repository-url + + delete + + +
+ Remove
- - - delete - -
- Remove +
+
+ some-other-repository +
+
+ some-other-repository-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -4026,230 +4488,272 @@ exports[`listing active helm repositories in preferences when navigating to pref class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download + +
+ +
+
+
+
+
+
+ Download mirror +
+ +
+ +
+
+
+ - + +
-
+
+
+
+
+
+
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
-
-
+
+
+
+

- Directory for binaries + Kubeconfig Syncs +

+
+ +
+
+ Synced Items
- -
-
-
- The directory to download binaries into. -
-
-
-
- Path to kubectl binary - -
-
- -
-
-
-
-
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
- Error getting Helm configuration: Tried to get Helm repositories, but HELM_REPOSITORY_CACHE was not present in \`$ helm env\`. + No files and folders have been synced yet
-
-
+ +
+
+
+
+

+ Helm Charts +

+
+
+
+ Error getting Helm configuration: Tried to get Helm repositories, but HELM_REPOSITORY_CACHE was not present in \`$ helm env\`. +
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -4497,230 +5022,272 @@ exports[`listing active helm repositories in preferences when navigating to pref class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download + +
+ +
+
+
+
+
+
+ Download mirror +
+ +
+ +
+
+
+ - + +
-
+
+
+
+
+
+
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
-
-
+
+
+
+

- Directory for binaries + Kubeconfig Syncs +

+
+ +
+
+ Synced Items
- -
-
-
- The directory to download binaries into. -
-
-
-
- Path to kubectl binary - -
-
- -
-
-
-
-
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
- Error getting Helm configuration: Tried to get Helm repositories, but HELM_REPOSITORY_CONFIG was not present in \`$ helm env\`. + No files and folders have been synced yet
-
-
+ +
+
+
+
+

+ Helm Charts +

+
+
+
+ Error getting Helm configuration: Tried to get Helm repositories, but HELM_REPOSITORY_CONFIG was not present in \`$ helm env\`. +
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -4968,230 +5556,272 @@ exports[`listing active helm repositories in preferences when navigating to pref class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download + +
+ +
+
+
+
+
+
+ Download mirror +
+ +
+ +
+
+
+ - + +
-
+
+
+
+
+
+
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
-
-
+
+
+
+

- Directory for binaries + Kubeconfig Syncs +

+
+ +
+
+ Synced Items
- -
-
-
- The directory to download binaries into. -
-
-
-
- Path to kubectl binary - -
-
- -
-
-
-
-
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
- Error getting Helm configuration: some-error + No files and folders have been synced yet
-
-
+ +
+
+
+
+

+ Helm Charts +

+
+
+
+ Error getting Helm configuration: some-error +
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -144,231 +165,61 @@ exports[`remove helm repository from list of active repositories in preferences class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -452,30 +289,256 @@ exports[`remove helm repository from list of active repositories in preferences
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -723,231 +807,61 @@ exports[`remove helm repository from list of active repositories in preferences class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- some-active-repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ some-active-repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -1313,231 +1460,61 @@ exports[`remove helm repository from list of active repositories in preferences class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
-
+
+
+
+
- Add Custom Helm Repo - -
+ Directory for binaries + +
+
+ +
+
+
+ The directory to download binaries into. +
+
+
+
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+
+
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
-
-
- some-active-repository -
-
- some-url -
-
- + +
- delete - - -
+
+ Repositories +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+
+ some-active-repository +
+
+ some-url +
+
+ + + delete + + +
+ Remove +
+
-
-
-
+ +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -1903,231 +2113,61 @@ exports[`remove helm repository from list of active repositories in preferences class="content" >
-
-

+
+
- Kubernetes -

-
- Kubectl binary download - -
- -
-
-
- Download mirror - -
-
- - -
+
-
- Download mirror for kubectl -
-
- -
+ Kubectl binary download +
-
- - -
-
-
-
-
-
- Directory for binaries - + +
- -
-
+ class="size-xl" + />
- The directory to download binaries into. -
- -
-
- Path to kubectl binary - -
-
- -
-
-
- -
-
-

- Kubeconfig Syncs -

-
- -
-
- Synced Items - -
-
-
- No files and folders have been synced yet -
-
-
-
-
-

- Helm Charts -

-
-
-
+
+ Download mirror + +
+
- Repositories + Download mirror for kubectl
- @@ -2211,30 +2237,256 @@ exports[`remove helm repository from list of active repositories in preferences
- -
-
+
+
+
+
+
+ Directory for binaries + +
+
+
-
-
+
+ The directory to download binaries into. +
+
-
- +
+
+
+
+ Path to kubectl binary + +
+
+ +
+
+
+
+ +
+
+
+
+

+ Kubeconfig Syncs +

+
+ +
+
+ Synced Items + +
+
+
+ No files and folders have been synced yet +
+
+
+
+
+
+
+

+ Helm Charts +

+
+
+
+
+ + +
+
+
+ Repositories +
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
({ + kind: "block" as const, + id: "helm-charts", + parentId: "kubernetes-page", + orderNumber: 30, + Component: HelmCharts, + }), + + injectionToken: preferenceItemInjectionToken, +}); + +export default helmChartsPreferenceBlockInjectable; diff --git a/src/renderer/components/+preferences/kubernetes/helm-charts/helm-charts.module.scss b/src/features/helm-charts/child-features/preferences/renderer/helm-charts.module.scss similarity index 100% rename from src/renderer/components/+preferences/kubernetes/helm-charts/helm-charts.module.scss rename to src/features/helm-charts/child-features/preferences/renderer/helm-charts.module.scss diff --git a/src/renderer/components/+preferences/kubernetes/helm-charts/helm-charts.tsx b/src/features/helm-charts/child-features/preferences/renderer/helm-charts.tsx similarity index 67% rename from src/renderer/components/+preferences/kubernetes/helm-charts/helm-charts.tsx rename to src/features/helm-charts/child-features/preferences/renderer/helm-charts.tsx index 691d618061..3fd3c6718b 100644 --- a/src/renderer/components/+preferences/kubernetes/helm-charts/helm-charts.tsx +++ b/src/features/helm-charts/child-features/preferences/renderer/helm-charts.tsx @@ -14,7 +14,7 @@ import type { HelmRepositoriesErrorState } from "./helm-repositories-error-state import helmRepositoriesErrorStateInjectable from "./helm-repositories-error-state.injectable"; import type { IObservableValue } from "mobx"; import { observer } from "mobx-react"; -import { Notice } from "../../../+extensions/notice"; +import { Notice } from "../../../../../renderer/components/+extensions/notice"; interface Dependencies { helmRepositoriesErrorState: IObservableValue; @@ -25,27 +25,31 @@ const NonInjectedHelmCharts = observer( const state = helmRepositoriesErrorState.get(); return ( -
- {!state.controlsAreShown && ( - -
{state.errorMessage}
-
- )} +
+

Helm Charts

- {state.controlsAreShown && ( -
-
- +
+ {!state.controlsAreShown && ( + +
{state.errorMessage}
+
+ )} - + {state.controlsAreShown && ( +
+
+ + + +
+ + + +
- - - - -
- )} -
+ )} +
+
); }, ); diff --git a/src/renderer/components/+preferences/kubernetes/helm-charts/helm-repositories-error-state.injectable.ts b/src/features/helm-charts/child-features/preferences/renderer/helm-repositories-error-state.injectable.ts similarity index 100% rename from src/renderer/components/+preferences/kubernetes/helm-charts/helm-repositories-error-state.injectable.ts rename to src/features/helm-charts/child-features/preferences/renderer/helm-repositories-error-state.injectable.ts diff --git a/src/renderer/components/+preferences/kubernetes/helm-charts/helm-repositories.tsx b/src/features/helm-charts/child-features/preferences/renderer/helm-repositories.tsx similarity index 92% rename from src/renderer/components/+preferences/kubernetes/helm-charts/helm-repositories.tsx rename to src/features/helm-charts/child-features/preferences/renderer/helm-repositories.tsx index d48f648e89..fd3cd333d8 100644 --- a/src/renderer/components/+preferences/kubernetes/helm-charts/helm-repositories.tsx +++ b/src/features/helm-charts/child-features/preferences/renderer/helm-repositories.tsx @@ -11,9 +11,9 @@ import { observer } from "mobx-react"; import activeHelmRepositoriesInjectable from "./active-helm-repositories.injectable"; import type { IAsyncComputed } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react"; -import { Spinner } from "../../../spinner"; +import { Spinner } from "../../../../../renderer/components/spinner"; import type { HelmRepo } from "../../../../../common/helm/helm-repo"; -import { RemovableItem } from "../../removable-item"; +import { RemovableItem } from "../../../../preferences/renderer/removable-item/removable-item"; import removeHelmRepositoryInjectable from "./remove-helm-repository.injectable"; interface Dependencies { diff --git a/src/renderer/components/+preferences/kubernetes/helm-charts/remove-helm-repository.injectable.ts b/src/features/helm-charts/child-features/preferences/renderer/remove-helm-repository.injectable.ts similarity index 100% rename from src/renderer/components/+preferences/kubernetes/helm-charts/remove-helm-repository.injectable.ts rename to src/features/helm-charts/child-features/preferences/renderer/remove-helm-repository.injectable.ts diff --git a/src/features/helm-charts/listing-active-helm-repositories-in-preferences.test.ts b/src/features/helm-charts/listing-active-helm-repositories-in-preferences.test.ts index 30e5bac5eb..99336697ff 100644 --- a/src/features/helm-charts/listing-active-helm-repositories-in-preferences.test.ts +++ b/src/features/helm-charts/listing-active-helm-repositories-in-preferences.test.ts @@ -14,7 +14,7 @@ import execFileInjectable, { type ExecFile } from "../../common/fs/exec-file.inj import helmBinaryPathInjectable from "../../main/helm/helm-binary-path.injectable"; import loggerInjectable from "../../common/logger.injectable"; import type { Logger } from "../../common/logger"; -import callForPublicHelmRepositoriesInjectable from "../../renderer/components/+preferences/kubernetes/helm-charts/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable"; +import callForPublicHelmRepositoriesInjectable from "./child-features/preferences/renderer/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable"; import showErrorNotificationInjectable from "../../renderer/components/notifications/show-error-notification.injectable"; describe("listing active helm repositories in preferences", () => { diff --git a/src/features/helm-charts/remove-helm-repository-from-list-of-active-repository-in-preferences.test.ts b/src/features/helm-charts/remove-helm-repository-from-list-of-active-repository-in-preferences.test.ts index 8d029d3412..87a83c8eb3 100644 --- a/src/features/helm-charts/remove-helm-repository-from-list-of-active-repository-in-preferences.test.ts +++ b/src/features/helm-charts/remove-helm-repository-from-list-of-active-repository-in-preferences.test.ts @@ -13,7 +13,7 @@ import execFileInjectable from "../../common/fs/exec-file.injectable"; import helmBinaryPathInjectable from "../../main/helm/helm-binary-path.injectable"; import getActiveHelmRepositoriesInjectable from "../../main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable"; import type { HelmRepo } from "../../common/helm/helm-repo"; -import callForPublicHelmRepositoriesInjectable from "../../renderer/components/+preferences/kubernetes/helm-charts/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable"; +import callForPublicHelmRepositoriesInjectable from "./child-features/preferences/renderer/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable"; import type { AsyncResult } from "../../common/utils/async-result"; describe("remove helm repository from list of active repositories in preferences", () => { diff --git a/src/features/preferences/__snapshots__/closing-preferences.test.tsx.snap b/src/features/preferences/__snapshots__/closing-preferences.test.tsx.snap index 720d17ab66..f87feb798c 100644 --- a/src/features/preferences/__snapshots__/closing-preferences.test.tsx.snap +++ b/src/features/preferences/__snapshots__/closing-preferences.test.tsx.snap @@ -11,44 +11,62 @@ exports[`preferences - closing-preferences given accessing preferences directly
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -155,400 +176,429 @@ exports[`preferences - closing-preferences given accessing preferences directly class="content" >

Application

-
-
- Theme - -
-
- -
+ Theme + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
-
-
-
+
+
+
-
- Extension Install Registry - -
-
- -
+ Extension Install Registry + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
-

- This setting is to change the registry URL for installing extensions by name. - If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your - - .npmrc - - file or in the input below. -

-
+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+ +
+
+
+
+
+ Start-up + +
-
-
-
-
-
+
+
+
-
- Start-up - -
- - -
-
-
- Update Channel - -
-
- -
+ Update Channel + +
+
+ +
- Stable -
-
- -
-
-
- - +
+ +
-
-
-
-
+
+
+
-
- Locale Timezone - -
-
- -
+ Locale Timezone + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
- + +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- Preferences -
- - - - - -
@@ -804,7 +876,13 @@ exports[`preferences - closing-preferences given accessing preferences directly
- Some test page +
+
+ irrelevant +
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
-
+
Some front page
@@ -1038,50 +1136,70 @@ exports[`preferences - closing-preferences given accessing preferences directly
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
-
+
Some front page
@@ -1159,44 +1277,62 @@ exports[`preferences - closing-preferences given already in a page and then navi
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -1303,400 +1442,429 @@ exports[`preferences - closing-preferences given already in a page and then navi class="content" >

Application

-
-
- Theme - -
-
- -
+ Theme + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
-
-
-
+
+
+
-
- Extension Install Registry - -
-
- -
+ Extension Install Registry + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
-

- This setting is to change the registry URL for installing extensions by name. - If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your - - .npmrc - - file or in the input below. -

-
+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+ +
+
+
+
+
+ Start-up + +
-
-
-
-
-
+
+
+
-
- Start-up - -
- - -
-
-
- Update Channel - -
-
- -
+ Update Channel + +
+
+ +
- Stable -
-
- -
-
-
- - +
+ +
-
-
-
-
+
+
+
-
- Locale Timezone - -
-
- -
+ Locale Timezone + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
- + +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- Preferences -
- - - - - -
@@ -1952,7 +2142,13 @@ exports[`preferences - closing-preferences given already in a page and then navi
- Some test page +
+
+ irrelevant +
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- -
-
- Some test page -
-
-
-
-
- - - close - - -
- -
-
-
-
+ Some content
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
- -
-
- Some test page -
-
-
-
-
- - - close - - -
- -
-
-
-
+ Some content
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -168,400 +189,429 @@ exports[`preferences: extension adding preference tabs given in preferences, whe class="content" >

Application

-
-
- Theme - -
-
- -
+ Theme + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
-
-
-
+
+
+
-
- Extension Install Registry - -
-
- -
+ Extension Install Registry + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
-

- This setting is to change the registry URL for installing extensions by name. - If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your - - .npmrc - - file or in the input below. -

-
+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+ +
+
+
+
+
+ Start-up + +
-
-
-
-
-
+
+
+
-
- Start-up - -
- - -
-
-
- Update Channel - -
-
- -
+ Update Channel + +
+
+ +
- Stable -
-
- -
-
-
- - +
+ +
-
-
-
-
+
+
+
-
- Locale Timezone - -
-
- -
+ Locale Timezone + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
- + +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+ +
+
+
+

+ Application +

+
+
+
+ Theme + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+ Extension Install Registry + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+

+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+
+
+
+
+
+
+ Start-up + +
+ +
+
+
+
+
+
+ Update Channel + +
+
+ + +
+
+
+ Stable +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+ Locale Timezone + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + + close + + +
+ +
+
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+`; + +exports[`preferences - hiding-of-empty-branches, given in preferences page given tab group and empty tabs when an item appears for one of the tabs renders 1`] = ` +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+ +
+
+
+

+ Application +

+
+
+
+ Theme + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+ Extension Install Registry + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+

+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+
+
+
+
+
+
+ Start-up + +
+ +
+
+
+
+
+
+ Update Channel + +
+
+ + +
+
+
+ Stable +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+ Locale Timezone + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + + close + + +
+ +
+
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+`; + +exports[`preferences - hiding-of-empty-branches, given in preferences page given tab group and empty tabs when an item appears for one of the tabs when an item appears for the remaining tab renders 1`] = ` +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+ +
+
+
+

+ Application +

+
+
+
+ Theme + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+ Extension Install Registry + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+

+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+
+
+
+
+
+
+ Start-up + +
+ +
+
+
+
+
+
+ Update Channel + +
+
+ + +
+
+
+ Stable +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+ Locale Timezone + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + + close + + +
+ +
+
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+`; diff --git a/src/features/preferences/__snapshots__/navigation-to-application-preferences.test.ts.snap b/src/features/preferences/__snapshots__/navigation-to-application-preferences.test.ts.snap index 8f3df16d7c..fc951ae75a 100644 --- a/src/features/preferences/__snapshots__/navigation-to-application-preferences.test.ts.snap +++ b/src/features/preferences/__snapshots__/navigation-to-application-preferences.test.ts.snap @@ -11,44 +11,62 @@ exports[`preferences - navigation to application preferences given in some child
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -143,76 +164,81 @@ exports[`preferences - navigation to application preferences given in some child class="content" >
-
-

- Proxy -

-
- HTTP Proxy - -
-
-
+
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -458,400 +505,429 @@ exports[`preferences - navigation to application preferences given in some child class="content" >

Application

-
-
- Theme - -
-
- -
+ Theme + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
-
-
-
+
+
+
-
- Extension Install Registry - -
-
- -
+ Extension Install Registry + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
-

- This setting is to change the registry URL for installing extensions by name. - If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your - - .npmrc - - file or in the input below. -

-
+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+ +
+
+
+
+
+ Start-up + +
-
-
-
-
-
+
+
+
-
- Start-up - -
- - -
-
-
- Update Channel - -
-
- -
+ Update Channel + +
+
+ +
- Stable -
-
- -
-
-
- - +
+ +
-
-
-
-
+
+
+
-
- Locale Timezone - -
-
- -
+ Locale Timezone + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
- + +
`; + +exports[`preferences - navigation to application preferences given in some child page of preferences, when rendered when navigating to preferences without specifying the tab renders 1`] = ` + +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+ +
+
+
+

+ Application +

+
+
+
+ Theme + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+ Extension Install Registry + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+

+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+
+
+
+
+
+
+ Start-up + +
+ +
+
+
+
+
+
+ Update Channel + +
+
+ + +
+
+
+ Stable +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+ Locale Timezone + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + + close + + +
+ +
+
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+ +`; diff --git a/src/features/preferences/__snapshots__/navigation-to-editor-preferences.test.ts.snap b/src/features/preferences/__snapshots__/navigation-to-editor-preferences.test.ts.snap index ccb22a9388..a21e6662c3 100644 --- a/src/features/preferences/__snapshots__/navigation-to-editor-preferences.test.ts.snap +++ b/src/features/preferences/__snapshots__/navigation-to-editor-preferences.test.ts.snap @@ -11,44 +11,62 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -143,400 +164,429 @@ exports[`preferences - navigation to editor preferences given in preferences, wh class="content" >

Application

-
-
- Theme - -
-
- -
+ Theme + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
-
-
-
+
+
+
-
- Extension Install Registry - -
-
- -
+ Extension Install Registry + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
-

- This setting is to change the registry URL for installing extensions by name. - If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your - - .npmrc - - file or in the input below. -

-
+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+ +
+
+
+
+
+ Start-up + +
-
-
-
-
-
+
+
+
-
- Start-up - -
- - -
-
-
- Update Channel - -
-
- -
+ Update Channel + +
+
+ +
- Stable -
-
- -
-
-
- - +
+ +
-
-
-
-
+
+
+
-
- Locale Timezone - -
-
- -
+ Locale Timezone + +
+
+ +
- Select... -
-
- -
-
-
- - +
+ +
-
- + +
- - - home - - - + home + + +
+
+
- - arrow_back - - - + arrow_back + + +
+
+
- - arrow_forward - - + + arrow_forward + + +
+
-
@@ -782,284 +853,317 @@ exports[`preferences - navigation to editor preferences given in preferences, wh class="content" >

- Editor configuration + Editor

- Minimap - -
-
-
+
+ Minimap + +
+
+
+ +
+
+
+ Position +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ Line numbers + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+ Tab size + +
+
+
+
+
+
+
+
+
+
+ Font size +
-
- Position -
+ +
- - -
-
-
- Select... -
-
- -
-
-
- - -
-
-
+ class="input-info flex gaps" + />
-
-
-
-
- Line numbers - -
-
- - +
+
+
+
+
-
-
- Select... -
-
- -
-
-
- - -
+ Font family +
-
- -
-
- Tab size - -
-
-
-
-
-
-
- Font size - -
-
- +
- -
-
-
-
-
- Font family - -
-
- -
-
-
+
+ +