mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
feat: Introduce API for changing the status bar colour
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
f6977428da
commit
06a0dce612
@ -3,8 +3,12 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { StatusBarStatus } from "../../renderer/components/status-bar/current-status.injectable";
|
||||
import type { SetStatusBarStatus } from "../../renderer/components/status-bar/set-status-bar-status.injectable";
|
||||
import setStatusBarStatusInjectable from "../../renderer/components/status-bar/set-status-bar-status.injectable";
|
||||
import activeThemeInjectable from "../../renderer/themes/active.injectable";
|
||||
import type { LensTheme } from "../../renderer/themes/lens-theme";
|
||||
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||
import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
|
||||
export const activeTheme = asLegacyGlobalForExtensionApi(activeThemeInjectable);
|
||||
@ -16,4 +20,10 @@ export function getActiveTheme() {
|
||||
return activeTheme.get();
|
||||
}
|
||||
|
||||
export type { LensTheme };
|
||||
export const setStatusBarStatus = asLegacyGlobalFunctionForExtensionApi(setStatusBarStatusInjectable);
|
||||
|
||||
export type {
|
||||
LensTheme,
|
||||
StatusBarStatus,
|
||||
SetStatusBarStatus,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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 { observable } from "mobx";
|
||||
|
||||
export type StatusBarStatus = "default" | "warning" | "error";
|
||||
|
||||
const statusBarCurrentStatusInjectable = getInjectable({
|
||||
id: "status-bar-current-status",
|
||||
instantiate: () => observable.box<StatusBarStatus>("default"),
|
||||
});
|
||||
|
||||
export default statusBarCurrentStatusInjectable;
|
||||
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 { action } from "mobx";
|
||||
import type { StatusBarStatus } from "./current-status.injectable";
|
||||
import statusBarCurrentStatusInjectable from "./current-status.injectable";
|
||||
|
||||
export type SetStatusBarStatus = (newStatus: StatusBarStatus) => void;
|
||||
|
||||
const setStatusBarStatusInjectable = getInjectable({
|
||||
id: "set-status-bar-status",
|
||||
instantiate: (di): SetStatusBarStatus => {
|
||||
const status = di.inject(statusBarCurrentStatusInjectable);
|
||||
|
||||
return action((newStatus) => status.set(newStatus));
|
||||
},
|
||||
});
|
||||
|
||||
export default setStatusBarStatusInjectable;
|
||||
@ -4,7 +4,6 @@
|
||||
*/
|
||||
import type { Injectable } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { computed } from "mobx";
|
||||
import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token";
|
||||
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||
@ -39,7 +38,6 @@ const toItemInjectableFor = (extension: LensRendererExtension, getRandomId: () =
|
||||
const id = `${getRandomId()}-status-bar-item-for-extension-${extension.sanitizedExtensionId}`;
|
||||
let component: React.ComponentType;
|
||||
let position: "left" | "right";
|
||||
const visible: IComputedValue<boolean> | undefined = registration?.visible;
|
||||
|
||||
if (registration?.item) {
|
||||
const { item } = registration;
|
||||
@ -77,7 +75,7 @@ const toItemInjectableFor = (extension: LensRendererExtension, getRandomId: () =
|
||||
origin: extension.sanitizedExtensionId,
|
||||
component,
|
||||
position,
|
||||
visible: visible ?? computed(() => true),
|
||||
visible: registration?.visible ?? computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: statusBarItemInjectionToken,
|
||||
|
||||
@ -3,14 +3,12 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { computed } from "mobx";
|
||||
import type { StatusBarItemProps } from "./status-bar-registration";
|
||||
import type { StatusBarItem } from "./status-bar-item-injection-token";
|
||||
import { statusBarItemInjectionToken } from "./status-bar-item-injection-token";
|
||||
import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
|
||||
interface StatusItem {
|
||||
export interface StatusItem {
|
||||
origin?: string;
|
||||
component: React.ComponentType<StatusBarItemProps>;
|
||||
}
|
||||
@ -20,50 +18,38 @@ export interface StatusBarItems {
|
||||
left: StatusItem[];
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
registrations: IComputedValue<StatusBarItem[]>;
|
||||
}
|
||||
|
||||
function getStatusBarItems({ registrations }: Dependencies): IComputedValue<StatusBarItems> {
|
||||
return computed(() => {
|
||||
const res: StatusBarItems = {
|
||||
left: [],
|
||||
right: [],
|
||||
};
|
||||
|
||||
for (const registration of registrations.get()) {
|
||||
const { position = "right", component, visible, origin } = registration;
|
||||
|
||||
if (!visible.get()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res[position].push({
|
||||
origin,
|
||||
component,
|
||||
});
|
||||
}
|
||||
|
||||
// This is done so that the first ones registered are closest to the corner
|
||||
res.right.reverse();
|
||||
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
const statusBarItemsInjectable = getInjectable({
|
||||
id: "status-bar-items",
|
||||
|
||||
instantiate: (di) => {
|
||||
const computedInjectMany = di.inject(
|
||||
computedInjectManyInjectable,
|
||||
);
|
||||
const computedInjectMany = di.inject(computedInjectManyInjectable);
|
||||
const registrations = computedInjectMany(statusBarItemInjectionToken);
|
||||
|
||||
return getStatusBarItems({
|
||||
registrations: computedInjectMany(statusBarItemInjectionToken),
|
||||
return computed(() => {
|
||||
const res: StatusBarItems = {
|
||||
left: [],
|
||||
right: [],
|
||||
};
|
||||
|
||||
for (const registration of registrations.get()) {
|
||||
const { position = "right", component, visible, origin } = registration;
|
||||
|
||||
if (!visible.get()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res[position].push({
|
||||
origin,
|
||||
component,
|
||||
});
|
||||
}
|
||||
|
||||
// This is done so that the first ones registered are closest to the corner
|
||||
res.right.reverse();
|
||||
|
||||
return res;
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default statusBarItemsInjectable;
|
||||
|
||||
@ -8,12 +8,23 @@
|
||||
color: white;
|
||||
|
||||
grid-area: status-bar;
|
||||
background-color: var(--blue);
|
||||
height: var(--status-bar-height);
|
||||
font-size: var(--font-size-small);
|
||||
|
||||
display: inline-grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
&.status-default {
|
||||
background-color: var(--colorInfo);
|
||||
}
|
||||
|
||||
&.status-warning {
|
||||
background-color: var(--colorWarning);
|
||||
}
|
||||
|
||||
&.status-error {
|
||||
background-color: var(--colorError);
|
||||
}
|
||||
}
|
||||
|
||||
.leftSide {
|
||||
|
||||
@ -5,47 +5,45 @@
|
||||
|
||||
import React from "react";
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import type { IObservableArray } from "mobx";
|
||||
import { computed, observable } from "mobx";
|
||||
import type { StatusBarItems } from "./status-bar-items.injectable";
|
||||
import statusBarItemsInjectable from "./status-bar-items.injectable";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import type { ApplicationBuilder } from "../test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../test-utils/get-application-builder";
|
||||
import setStatusBarStatusInjectable from "./set-status-bar-status.injectable";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import getRandomIdInjectable from "../../../common/utils/get-random-id.injectable";
|
||||
|
||||
describe("<StatusBar />", () => {
|
||||
let statusBarItems: IObservableArray<any>;
|
||||
let builder: ApplicationBuilder;
|
||||
let result: RenderResult;
|
||||
|
||||
beforeEach(async () => {
|
||||
statusBarItems = observable.array([]);
|
||||
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
builder.beforeWindowStart(({ windowDi }) => {
|
||||
windowDi.unoverride(getRandomIdInjectable);
|
||||
windowDi.permitSideEffects(getRandomIdInjectable);
|
||||
windowDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
windowDi.unoverride(getRandomIdInjectable);
|
||||
});
|
||||
|
||||
builder.extensions.enable({
|
||||
id: "some-id",
|
||||
name: "some-name",
|
||||
result = await builder.render();
|
||||
});
|
||||
|
||||
rendererOptions: {
|
||||
statusBarItems,
|
||||
},
|
||||
describe("when an extension is enabled with no status items", () => {
|
||||
beforeEach(() => {
|
||||
builder.extensions.enable({
|
||||
id: "some-id",
|
||||
name: "some-name",
|
||||
|
||||
rendererOptions: {
|
||||
statusBarItems: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders w/o errors", async () => {
|
||||
const { container } = await builder.render();
|
||||
|
||||
expect(container).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it.each([
|
||||
describe.each([
|
||||
undefined,
|
||||
"hello",
|
||||
6,
|
||||
@ -53,73 +51,130 @@ describe("<StatusBar />", () => {
|
||||
[],
|
||||
[{}],
|
||||
{},
|
||||
])("renders w/o errors when registrations are not type compliant (%p)", async val => {
|
||||
statusBarItems.replace([val]);
|
||||
])("when an extension is enabled with an invalid data type, (%p)", (value) => {
|
||||
beforeEach(() => {
|
||||
builder.extensions.enable({
|
||||
id: "some-id",
|
||||
name: "some-name",
|
||||
|
||||
await expect(builder.render()).resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders items [{item: React.ReactNode}] (4.0.0-rc.1)", async () => {
|
||||
const testId = "testId";
|
||||
const text = "heee";
|
||||
|
||||
builder.beforeWindowStart(({ windowDi }) => {
|
||||
windowDi.override(statusBarItemsInjectable, () => computed(() => ({
|
||||
right: [ { origin: testId, component: () => <span data-testid={testId} >{text}</span> }],
|
||||
left: [],
|
||||
}) as StatusBarItems));
|
||||
rendererOptions: {
|
||||
statusBarItems: [value as any],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const { getByTestId } = await builder.render();
|
||||
|
||||
expect(getByTestId(testId)).toHaveTextContent(text);
|
||||
it("renders", () => {
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders items [{item: () => React.ReactNode}] (4.0.0-rc.1+)", async () => {
|
||||
const testId = "testId";
|
||||
const text = "heee";
|
||||
describe("when an extension is enabled using a deprecated registration of a plain ReactNode", () => {
|
||||
beforeEach(() => {
|
||||
builder.extensions.enable({
|
||||
id: "some-id",
|
||||
name: "some-name",
|
||||
|
||||
statusBarItems.replace([{
|
||||
item: () => <span data-testid={testId} >{text}</span>,
|
||||
}]);
|
||||
rendererOptions: {
|
||||
statusBarItems: [{
|
||||
item: "heeeeeeee",
|
||||
}],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const { getByTestId } = await builder.render();
|
||||
|
||||
expect(getByTestId(testId)).toHaveTextContent(text);
|
||||
it("renders the provided ReactNode", () => {
|
||||
expect(result.baseElement).toHaveTextContent("heeeeeeee");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when an extension is enabled using a deprecated registration of a function returning a ReactNode", () => {
|
||||
beforeEach(() => {
|
||||
builder.extensions.enable({
|
||||
id: "some-id",
|
||||
name: "some-name",
|
||||
|
||||
it("sort positioned items properly", async () => {
|
||||
statusBarItems.replace([
|
||||
{
|
||||
components: {
|
||||
Item: () => <div data-testid="sortedElem">right1</div>,
|
||||
rendererOptions: {
|
||||
statusBarItems: [{
|
||||
item: () => "heeeeeeee",
|
||||
}],
|
||||
},
|
||||
},
|
||||
{
|
||||
components: {
|
||||
Item: () => <div data-testid="sortedElem">right2</div>,
|
||||
position: "right",
|
||||
},
|
||||
},
|
||||
{
|
||||
components: {
|
||||
Item: () => <div data-testid="sortedElem">left1</div>,
|
||||
position: "left",
|
||||
},
|
||||
},
|
||||
{
|
||||
components: {
|
||||
Item: () => <div data-testid="sortedElem">left2</div>,
|
||||
position: "left",
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
const { getAllByTestId } = await builder.render();
|
||||
const elems = getAllByTestId("sortedElem");
|
||||
const positions = elems.map(elem => elem.textContent);
|
||||
it("renders the provided ReactNode", () => {
|
||||
expect(result.baseElement).toHaveTextContent("heeeeeeee");
|
||||
});
|
||||
});
|
||||
|
||||
expect(positions).toEqual(["left1", "left2", "right2", "right1"]);
|
||||
describe("when an extension is enabled specifying the side the elements should be on", () => {
|
||||
beforeEach(() => {
|
||||
builder.extensions.enable({
|
||||
id: "some-id",
|
||||
name: "some-name",
|
||||
|
||||
rendererOptions: {
|
||||
statusBarItems: [
|
||||
{
|
||||
components: {
|
||||
Item: () => <div data-testid="sortedElem">right1</div>,
|
||||
},
|
||||
},
|
||||
{
|
||||
components: {
|
||||
Item: () => <div data-testid="sortedElem">right2</div>,
|
||||
position: "right",
|
||||
},
|
||||
},
|
||||
{
|
||||
components: {
|
||||
Item: () => <div data-testid="sortedElem">left1</div>,
|
||||
position: "left",
|
||||
},
|
||||
},
|
||||
{
|
||||
components: {
|
||||
Item: () => <div data-testid="sortedElem">left2</div>,
|
||||
position: "left",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("sort positioned items properly", async () => {
|
||||
const elems = result.getAllByTestId("sortedElem");
|
||||
const positions = elems.map(elem => elem.textContent);
|
||||
|
||||
expect(positions).toEqual(["left1", "left2", "right2", "right1"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("has the default status by default", () => {
|
||||
expect([...result.getByTestId("status-bar").classList]).toContain("status-default");
|
||||
});
|
||||
|
||||
describe.each([
|
||||
"warning" as const,
|
||||
"error" as const,
|
||||
])("when StatusBar's status is set to %p", (value) => {
|
||||
beforeEach(() => {
|
||||
const di = builder.applicationWindow.only.di;
|
||||
const setStatusBarStatus = di.inject(setStatusBarStatusInjectable);
|
||||
|
||||
setStatusBarStatus(value);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`has the ${value} status by default`, () => {
|
||||
expect([...result.getByTestId("status-bar").classList]).toContain(`status-${value}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -10,19 +10,26 @@ import { observer } from "mobx-react";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { StatusBarItems } from "./status-bar-items.injectable";
|
||||
import statusBarItemsInjectable from "./status-bar-items.injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { IComputedValue, IObservableValue } from "mobx";
|
||||
import type { StatusBarStatus } from "./current-status.injectable";
|
||||
import statusBarCurrentStatusInjectable from "./current-status.injectable";
|
||||
import { cssNames } from "@k8slens/utilities";
|
||||
|
||||
export interface StatusBarProps {}
|
||||
|
||||
interface Dependencies {
|
||||
items: IComputedValue<StatusBarItems>;
|
||||
status: IObservableValue<StatusBarStatus>;
|
||||
}
|
||||
|
||||
const NonInjectedStatusBar = observer(({ items }: Dependencies & StatusBarProps) => {
|
||||
const NonInjectedStatusBar = observer(({
|
||||
items,
|
||||
status,
|
||||
}: Dependencies & StatusBarProps) => {
|
||||
const { left, right } = items.get();
|
||||
|
||||
return (
|
||||
<div className={styles.StatusBar} data-testid="status-bar">
|
||||
<div className={cssNames(styles.StatusBar, styles[`status-${status.get()}`])} data-testid="status-bar">
|
||||
<div className={styles.leftSide} data-testid="status-bar-left">
|
||||
{left.map((Item, index) => (
|
||||
<div
|
||||
@ -50,7 +57,8 @@ const NonInjectedStatusBar = observer(({ items }: Dependencies & StatusBarProps)
|
||||
|
||||
export const StatusBar = withInjectables<Dependencies, StatusBarProps>(NonInjectedStatusBar, {
|
||||
getProps: (di, props) => ({
|
||||
items: di.inject(statusBarItemsInjectable),
|
||||
...props,
|
||||
items: di.inject(statusBarItemsInjectable),
|
||||
status: di.inject(statusBarCurrentStatusInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user