Merge remote-tracking branch 'origin/master' into eliminate-gst-from-app-paths

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen committed 2022-01-05 15:04:43 +02:00
commit 4224937260
52 files changed
+1168 -409

No files matched your search

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
.closeButton {
width: 35px;
height: 35px;
display: grid;
place-items: center;
cursor: pointer;
border: 2px solid var(--textColorDimmed);
border-radius: 50%;
&:hover {
background-color: #72767d25;
}
&:active {
transform: translateY(1px);
}
}
.icon {
color: var(--textColorAccent);
opacity: 0.6;
}
.esc {
text-align: center;
margin-top: var(--margin);
font-weight: bold;
user-select: none;
color: var(--textColorDimmed);
pointer-events: none;
}
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import styles from "./close-button.module.scss";
import React, { HTMLAttributes } from "react";
import { Icon } from "../icon";
interface Props extends HTMLAttributes<HTMLDivElement> {
}
export function CloseButton(props: Props) {
return (
<div {...props}>
<div className={styles.closeButton} role="button" aria-label="Close">
<Icon material="close" className={styles.icon}/>
</div>
<div className={styles.esc} aria-hidden="true">
ESC
</div>
</div>
);
}
@@ -138,41 +138,7 @@
}
> .toolsRegion {
.fixedTools {
position: fixed;
top: 60px;
.closeBtn {
width: 35px;
height: 35px;
display: grid;
place-items: center;
border: 2px solid var(--textColorDimmed);
border-radius: 50%;
cursor: pointer;
&:hover {
background-color: #72767d4d;
}
&:active {
transform: translateY(1px);
}
.Icon {
color: var(--textColorTertiary);
}
}
.esc {
text-align: center;
margin-top: 4px;
font-weight: 600;
font-size: 14px;
color: var(--textColorDimmed);
pointer-events: none;
}
}
width: 45px;
}
}
@@ -25,8 +25,8 @@ import React from "react";
import { observer } from "mobx-react";
import { cssNames, IClassName } from "../../utils";
import { navigation } from "../../navigation";
import { Icon } from "../icon";
import { catalogURL } from "../../../common/routes";
import { CloseButton } from "./close-button";
export interface SettingLayoutProps extends React.DOMAttributes<any> {
className?: IClassName;
@@ -104,13 +104,8 @@ export class SettingLayout extends React.Component<SettingLayoutProps> {
<div className="toolsRegion">
{
this.props.provideBackButtonNavigation && (
<div className="fixedTools">
<div className="closeBtn" role="button" aria-label="Close" onClick={back}>
<Icon material="close" />
</div>
<div className="esc" aria-hidden="true">
ESC
</div>
<div className="fixed top-[60px]">
<CloseButton onClick={back}/>
</div>
)
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed } from "mobx";
import rendererExtensionsInjectable from "../../../../../extensions/renderer-extensions.injectable";
const topBarItemsInjectable = getInjectable({
instantiate: (di) => {
const extensions = di.inject(rendererExtensionsInjectable);
return computed(() =>
extensions.get().flatMap((extension) => extension.topBarItems),
);
},
lifecycle: lifecycleEnum.singleton,
});
export default topBarItemsInjectable;
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
interface TopBarComponents {
Item: React.ComponentType;
}
export interface TopBarRegistration {
components: TopBarComponents;
}
@@ -20,23 +20,32 @@
*/
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import { TopBar } from "../topbar";
import { TopBarRegistry } from "../../../../extensions/registries";
import { TopBar } from "./top-bar";
import { IpcMainWindowEvents } from "../../../../main/window-manager";
import { broadcastMessage } from "../../../../common/ipc";
import * as vars from "../../../../common/vars";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import { DiRender, renderFor } from "../../test-utils/renderFor";
import directoryForUserDataInjectable
from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import mockFs from "mock-fs";
const mockConfig = vars as { isWindows: boolean, isLinux: boolean };
const mockConfig = vars as { isWindows: boolean; isLinux: boolean };
jest.mock("../../../../common/ipc");
jest.mock("../../../../common/vars", () => {
const SemVer = require("semver").SemVer;
const versionStub = new SemVer("1.0.0");
return {
__esModule: true,
isWindows: null,
isLinux: null,
appSemVer: versionStub,
};
});
@@ -57,20 +66,30 @@ jest.mock("@electron/remote", () => {
};
});
describe("<Tobar/> in Windows and Linux", () => {
beforeEach(() => {
TopBarRegistry.createInstance();
describe("<TopBar/> in Windows and Linux", () => {
let render: DiRender;
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs();
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
await di.runSetups();
render = renderFor(di);
});
afterEach(() => {
TopBarRegistry.resetInstance();
mockFs.restore();
});
it("shows window controls on Windows", () => {
mockConfig.isWindows = true;
mockConfig.isLinux = false;
const { getByTestId } = render(<TopBar/>);
const { getByTestId } = render(<TopBar />);
expect(getByTestId("window-menu")).toBeInTheDocument();
expect(getByTestId("window-minimize")).toBeInTheDocument();
@@ -82,7 +101,7 @@ describe("<Tobar/> in Windows and Linux", () => {
mockConfig.isWindows = false;
mockConfig.isLinux = true;
const { getByTestId } = render(<TopBar/>);
const { getByTestId } = render(<TopBar />);
expect(getByTestId("window-menu")).toBeInTheDocument();
expect(getByTestId("window-minimize")).toBeInTheDocument();
@@ -93,7 +112,7 @@ describe("<Tobar/> in Windows and Linux", () => {
it("triggers ipc events on click", () => {
mockConfig.isWindows = true;
const { getByTestId } = render(<TopBar/>);
const { getByTestId } = render(<TopBar />);
const menu = getByTestId("window-menu");
const minimize = getByTestId("window-minimize");
@@ -20,14 +20,26 @@
*/
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import { TopBar } from "../topbar";
import { TopBarRegistry } from "../../../../extensions/registries";
import { TopBar } from "./top-bar";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
import { DiRender, renderFor } from "../../test-utils/renderFor";
import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable";
import { computed } from "mobx";
import directoryForUserDataInjectable
from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import mockFs from "mock-fs";
jest.mock("../../../../common/vars", () => {
const SemVer = require("semver").SemVer;
const versionStub = new SemVer("1.0.0");
return {
isMac: true,
appSemVer: versionStub,
};
});
@@ -47,9 +59,6 @@ jest.mock(
},
),
},
app: {
getPath: () => "tmp",
},
}),
);
@@ -76,12 +85,23 @@ jest.mock("@electron/remote", () => {
});
describe("<TopBar/>", () => {
beforeEach(() => {
TopBarRegistry.createInstance();
let di: ConfigurableDependencyInjectionContainer;
let render: DiRender;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs();
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
await di.runSetups();
render = renderFor(di);
});
afterEach(() => {
TopBarRegistry.resetInstance();
mockFs.restore();
});
it("renders w/o errors", () => {
@@ -129,13 +149,13 @@ describe("<TopBar/>", () => {
const testId = "testId";
const text = "an item";
TopBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
di.override(topBarItemsInjectable, () => computed(() => [
{
components: {
Item: () => <span data-testid={testId}>{text}</span>,
},
},
]);
]));
const { getByTestId } = render(<TopBar/>);
@@ -19,22 +19,28 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import styles from "./topbar.module.scss";
import styles from "./top-bar.module.scss";
import React, { useEffect, useMemo, useRef } from "react";
import { observer } from "mobx-react";
import { TopBarRegistry } from "../../../extensions/registries";
import { Icon } from "../icon";
import type { IComputedValue } from "mobx";
import { Icon } from "../../icon";
import { webContents, getCurrentWindow } from "@electron/remote";
import { observable } from "mobx";
import { broadcastMessage, ipcRendererOn } from "../../../common/ipc";
import { watchHistoryState } from "../../remote-helpers/history-updater";
import { isActiveRoute, navigate } from "../../navigation";
import { catalogRoute, catalogURL } from "../../../common/routes";
import { IpcMainWindowEvents } from "../../../main/window-manager";
import { isLinux, isWindows } from "../../../common/vars";
import { cssNames } from "../../utils";
import { broadcastMessage, ipcRendererOn } from "../../../../common/ipc";
import { watchHistoryState } from "../../../remote-helpers/history-updater";
import { isActiveRoute, navigate } from "../../../navigation";
import { catalogRoute, catalogURL } from "../../../../common/routes";
import { IpcMainWindowEvents } from "../../../../main/window-manager";
import { isLinux, isWindows } from "../../../../common/vars";
import { cssNames } from "../../../utils";
import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { TopBarRegistration } from "./top-bar-registration";
interface Props extends React.HTMLAttributes<any> {
interface Props extends React.HTMLAttributes<any> {}
interface Dependencies {
items: IComputedValue<TopBarRegistration[]>;
}
const prevEnabled = observable.box(false);
@@ -48,34 +54,10 @@ ipcRendererOn("history:can-go-forward", (event, state: boolean) => {
nextEnabled.set(state);
});
export const TopBar = observer(({ children, ...rest }: Props) => {
const NonInjectedTopBar = (({ items, children, ...rest }: Props & Dependencies) => {
const elem = useRef<HTMLDivElement>();
const window = useMemo(() => getCurrentWindow(), []);
const renderRegisteredItems = () => {
const items = TopBarRegistry.getInstance().getItems();
if (!Array.isArray(items)) {
return null;
}
return (
<div>
{items.map((registration, index) => {
if (!registration?.components?.Item) {
return null;
}
return (
<div key={index}>
<registration.components.Item />
</div>
);
})}
</div>
);
};
const openContextMenu = () => {
broadcastMessage(IpcMainWindowEvents.OPEN_CONTEXT_MENU);
};
@@ -156,7 +138,7 @@ export const TopBar = observer(({ children, ...rest }: Props) => {
/>
</div>
<div className={styles.controls}>
{renderRegisteredItems()}
{renderRegisteredItems(items.get())}
{children}
{(isWindows || isLinux) && (
<div className={cssNames(styles.windowButtons, { [styles.linuxButtons]: isLinux })}>
@@ -174,3 +156,29 @@ export const TopBar = observer(({ children, ...rest }: Props) => {
</div>
);
});
const renderRegisteredItems = (items: TopBarRegistration[]) => (
<div>
{items.map((registration, index) => {
if (!registration?.components?.Item) {
return null;
}
return (
<div key={index}>
<registration.components.Item />
</div>
);
})}
</div>
);
export const TopBar = withInjectables(observer(NonInjectedTopBar), {
getProps: (di, props) => ({
items: di.inject(topBarItemsInjectable),
...props,
}),
});