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-11 13:56:23 +02:00
commit 8d87bd0985
80 files changed
+1822 -1271

No files matched your search

@@ -21,13 +21,17 @@
import "@testing-library/jest-dom/extend-expect";
import { HotbarRemoveCommand } from "../hotbar-remove-command";
import { render, fireEvent } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import React from "react";
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import { DiRender, renderFor } from "../../test-utils/renderFor";
import hotbarManagerInjectable from "../../../../common/hotbar-store.injectable";
import { ThemeStore } from "../../../theme.store";
import { ConfirmDialog } from "../../confirm-dialog";
import type { HotbarStore } from "../../../../common/hotbar-store";
import { UserStore } from "../../../../common/user-store";
import { Notifications } from "../../notifications";
import mockFs from "mock-fs";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
const mockHotbars: { [id: string]: any } = {
@@ -38,50 +42,69 @@ const mockHotbars: { [id: string]: any } = {
},
};
jest.mock("../../../../common/hotbar-store", () => ({
HotbarStore: {
getInstance: () => ({
hotbars: [mockHotbars["1"]],
getById: (id: string) => mockHotbars[id],
remove: () => {},
hotbarIndex: () => 0,
}),
},
}));
describe("<HotbarRemoveCommand />", () => {
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
let di: DependencyInjectionContainer;
let render: DiRender;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs();
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
await di.runSetups();
render = renderFor(di);
UserStore.createInstance();
ThemeStore.createInstance();
});
afterEach(() => {
UserStore.resetInstance();
ThemeStore.resetInstance();
mockFs.restore();
ThemeStore.resetInstance();
UserStore.resetInstance();
});
it("renders w/o errors", () => {
const { container } = render(<HotbarRemoveCommand/>);
it("renders w/o errors", async () => {
di.override(hotbarManagerInjectable, () => ({
hotbars: [mockHotbars["1"]],
getById: (id: string) => mockHotbars[id],
remove: () => {
},
hotbarIndex: () => 0,
getDisplayLabel: () => "1: Default",
}) as any as HotbarStore);
await di.runSetups();
const { container } = render(<HotbarRemoveCommand />);
expect(container).toBeInstanceOf(HTMLElement);
});
it("displays error notification if user tries to remove last hotbar", () => {
const spy = jest.spyOn(Notifications, "error");
const { getByText } = render(<HotbarRemoveCommand/>);
it("calls remove if you click on the entry", async () => {
const removeMock = jest.fn();
di.override(hotbarManagerInjectable, () => ({
hotbars: [mockHotbars["1"]],
getById: (id: string) => mockHotbars[id],
remove: removeMock,
hotbarIndex: () => 0,
getDisplayLabel: () => "1: Default",
}) as any as HotbarStore);
await di.runSetups();
const { getByText } = render(
<>
<HotbarRemoveCommand />
<ConfirmDialog />
</>,
);
fireEvent.click(getByText("1: Default"));
fireEvent.click(getByText("Remove Hotbar"));
expect(spy).toHaveBeenCalled();
spy.mockRestore();
expect(removeMock).toHaveBeenCalled();
});
});
@@ -21,44 +21,53 @@
import React from "react";
import { observer } from "mobx-react";
import { HotbarStore } from "../../../common/hotbar-store";
import { CommandOverlay } from "../command-palette";
import { Input, InputValidator } from "../input";
import type { CreateHotbarData, CreateHotbarOptions } from "../../../common/hotbar-types";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable";
export const uniqueHotbarName: InputValidator = {
condition: ({ required }) => required,
message: () => "Hotbar with this name already exists",
validate: value => !HotbarStore.getInstance().getByName(value),
};
interface Dependencies {
closeCommandOverlay: () => void;
addHotbar: (data: CreateHotbarData, { setActive }?: CreateHotbarOptions) => void;
uniqueHotbarName: InputValidator;
}
@observer
export class HotbarAddCommand extends React.Component {
onSubmit = (name: string) => {
const NonInjectedHotbarAddCommand = observer(({ closeCommandOverlay, addHotbar, uniqueHotbarName }: Dependencies) => {
const onSubmit = (name: string) => {
if (!name.trim()) {
return;
}
HotbarStore.getInstance().add({ name }, { setActive: true });
CommandOverlay.close();
addHotbar({ name }, { setActive: true });
closeCommandOverlay();
};
render() {
return (
<>
<Input
placeholder="Hotbar name"
autoFocus={true}
theme="round-black"
data-test-id="command-palette-hotbar-add-name"
validators={uniqueHotbarName}
onSubmit={this.onSubmit}
dirty={true}
showValidationLine={true}
/>
<small className="hint">
Please provide a new hotbar name (Press &quot;Enter&quot; to confirm or &quot;Escape&quot; to cancel)
</small>
</>
);
}
}
return (
<>
<Input
placeholder="Hotbar name"
autoFocus={true}
theme="round-black"
data-test-id="command-palette-hotbar-add-name"
validators={uniqueHotbarName}
onSubmit={onSubmit}
dirty={true}
showValidationLine={true}
/>
<small className="hint">
Please provide a new hotbar name (Press &quot;Enter&quot; to confirm or &quot;Escape&quot; to cancel)
</small>
</>
);
});
export const HotbarAddCommand = withInjectables<Dependencies>(NonInjectedHotbarAddCommand, {
getProps: (di, props) => ({
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
addHotbar: di.inject(hotbarManagerInjectable).add,
uniqueHotbarName: di.inject(uniqueHotbarNameInjectable),
...props,
}),
});
@@ -1,36 +0,0 @@
/**
* 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 { HotbarStore } from "../../../common/hotbar-store";
function hotbarIndex(id: string) {
return HotbarStore.getInstance().hotbarIndex(id) + 1;
}
export function hotbarDisplayLabel(id: string) : string {
const hotbar = HotbarStore.getInstance().getById(id);
return `${hotbarIndex(id)}: ${hotbar.name}`;
}
export function hotbarDisplayIndex(id: string) : string {
return hotbarIndex(id).toString();
}
@@ -22,51 +22,44 @@
import React from "react";
import { observer } from "mobx-react";
import { Select } from "../select";
import { computed, makeObservable } from "mobx";
import { HotbarStore } from "../../../common/hotbar-store";
import { hotbarDisplayLabel } from "./hotbar-display-label";
import { CommandOverlay } from "../command-palette";
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
import { ConfirmDialog } from "../confirm-dialog";
import { Notifications } from "../notifications";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import type { Hotbar } from "../../../common/hotbar-types";
@observer
export class HotbarRemoveCommand extends React.Component {
constructor(props: {}) {
super(props);
makeObservable(this);
}
interface Dependencies {
closeCommandOverlay: () => void;
hotbarManager: {
hotbars: Hotbar[];
getById: (id: string) => Hotbar | undefined;
remove: (hotbar: Hotbar) => void;
getDisplayLabel: (hotbar: Hotbar) => string;
};
}
@computed get options() {
return HotbarStore.getInstance().hotbars.map((hotbar) => {
return { value: hotbar.id, label: hotbarDisplayLabel(hotbar.id) };
});
}
const NonInjectedHotbarRemoveCommand = observer(({ closeCommandOverlay, hotbarManager }: Dependencies) => {
const options = hotbarManager.hotbars.map(hotbar => ({
value: hotbar.id,
label: hotbarManager.getDisplayLabel(hotbar),
}));
onChange(id: string): void {
const hotbarStore = HotbarStore.getInstance();
const hotbar = hotbarStore.getById(id);
CommandOverlay.close();
const onChange = (id: string): void => {
const hotbar = hotbarManager.getById(id);
if (!hotbar) {
return;
}
if (hotbarStore.hotbars.length === 1) {
Notifications.error("Can't remove the last hotbar");
return;
}
closeCommandOverlay();
// TODO: make confirm dialog injectable
ConfirmDialog.open({
okButtonProps: {
label: `Remove Hotbar`,
label: "Remove Hotbar",
primary: false,
accent: true,
},
ok: () => {
hotbarStore.remove(hotbar);
},
ok: () => hotbarManager.remove(hotbar),
message: (
<div className="confirm flex column gaps">
<p>
@@ -75,19 +68,26 @@ export class HotbarRemoveCommand extends React.Component {
</div>
),
});
}
};
render() {
return (
<Select
menuPortalTarget={null}
onChange={(v) => this.onChange(v.value)}
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
menuIsOpen={true}
options={this.options}
autoFocus={true}
escapeClearsValue={false}
placeholder="Remove hotbar" />
);
}
}
return (
<Select
menuPortalTarget={null}
onChange={(v) => onChange(v.value)}
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
menuIsOpen={true}
options={options}
autoFocus={true}
escapeClearsValue={false}
placeholder="Remove hotbar"
/>
);
});
export const HotbarRemoveCommand = withInjectables<Dependencies>(NonInjectedHotbarRemoveCommand, {
getProps: (di, props) => ({
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
hotbarManager: di.inject(hotbarManagerInjectable),
...props,
}),
});
@@ -19,81 +19,61 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import React, { useState } from "react";
import { observer } from "mobx-react";
import { Select } from "../select";
import { action, computed, makeObservable, observable } from "mobx";
import { HotbarStore } from "../../../common/hotbar-store";
import { hotbarDisplayLabel } from "./hotbar-display-label";
import { Input } from "../input";
import { uniqueHotbarName } from "./hotbar-add-command";
import { CommandOverlay } from "../command-palette";
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
import { Input, InputValidator } from "../input";
import type { Hotbar } from "../../../common/hotbar-types";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable";
@observer
export class HotbarRenameCommand extends React.Component {
@observable hotbarId = "";
@observable hotbarName = "";
constructor(props: {}) {
super(props);
makeObservable(this);
}
@computed get options() {
return HotbarStore.getInstance().hotbars.map((hotbar) => {
return { value: hotbar.id, label: hotbarDisplayLabel(hotbar.id) };
});
}
@action onSelect = (id: string) => {
this.hotbarId = id;
this.hotbarName = HotbarStore.getInstance().getById(this.hotbarId).name;
interface Dependencies {
closeCommandOverlay: () => void;
hotbarManager: {
hotbars: Hotbar[];
getById: (id: string) => Hotbar | undefined;
setHotbarName: (id: string, name: string) => void;
getDisplayLabel: (hotbar: Hotbar) => string;
};
uniqueHotbarName: InputValidator;
}
onSubmit = (name: string) => {
const NonInjectedHotbarRenameCommand = observer(({ closeCommandOverlay, hotbarManager, uniqueHotbarName }: Dependencies) => {
const [hotbarId, setHotbarId] = useState("");
const [hotbarName, setHotbarName] = useState("");
const options = hotbarManager.hotbars.map(hotbar => ({
value: hotbar.id,
label: hotbarManager.getDisplayLabel(hotbar),
}));
const onSelect = (id: string) => {
setHotbarId(id);
setHotbarName(hotbarManager.getById(id).name);
};
const onSubmit = (name: string) => {
if (!name.trim()) {
return;
}
const hotbarStore = HotbarStore.getInstance();
const hotbar = HotbarStore.getInstance().getById(this.hotbarId);
if (!hotbar) {
return;
}
hotbarStore.setHotbarName(this.hotbarId, name);
CommandOverlay.close();
hotbarManager.setHotbarName(hotbarId, name);
closeCommandOverlay();
};
renderHotbarList() {
return (
<>
<Select
menuPortalTarget={null}
onChange={(v) => this.onSelect(v.value)}
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
menuIsOpen={true}
options={this.options}
autoFocus={true}
escapeClearsValue={false}
placeholder="Rename hotbar"/>
</>
);
}
renderNameInput() {
if (hotbarId) {
return (
<>
<Input
trim={true}
value={this.hotbarName}
onChange={v => this.hotbarName = v}
value={hotbarName}
onChange={setHotbarName}
placeholder="New hotbar name"
autoFocus={true}
theme="round-black"
validators={uniqueHotbarName}
onSubmit={this.onSubmit}
onSubmit={onSubmit}
showValidationLine={true}
/>
<small className="hint">
@@ -103,12 +83,25 @@ export class HotbarRenameCommand extends React.Component {
);
}
render() {
return (
<Select
menuPortalTarget={null}
onChange={(v) => onSelect(v.value)}
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
menuIsOpen={true}
options={options}
autoFocus={true}
escapeClearsValue={false}
placeholder="Rename hotbar"
/>
);
});
return (
<>
{!this.hotbarId ? this.renderHotbarList() : this.renderNameInput()}
</>
);
}
}
export const HotbarRenameCommand = withInjectables<Dependencies>(NonInjectedHotbarRenameCommand, {
getProps: (di, props) => ({
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
hotbarManager: di.inject(hotbarManagerInjectable),
uniqueHotbarName: di.inject(uniqueHotbarNameInjectable),
...props,
}),
});
@@ -23,38 +23,52 @@ import "./hotbar-selector.scss";
import React from "react";
import { Icon } from "../icon";
import { Badge } from "../badge";
import { HotbarStore } from "../../../common/hotbar-store";
import { CommandOverlay } from "../command-palette";
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
import { HotbarSwitchCommand } from "./hotbar-switch-command";
import { hotbarDisplayIndex } from "./hotbar-display-label";
import { TooltipPosition } from "../tooltip";
import { observer } from "mobx-react";
import type { Hotbar } from "../../../common/hotbar-types";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
interface Props {
export interface HotbarSelectorProps {
hotbar: Hotbar;
}
export const HotbarSelector = observer(({ hotbar }: Props) => {
const store = HotbarStore.getInstance();
interface Dependencies {
hotbarManager: {
switchToPrevious: () => void;
switchToNext: () => void;
getActive: () => Hotbar;
getDisplayIndex: (hotbar: Hotbar) => string;
};
openCommandOverlay: (component: React.ReactElement) => void;
}
return (
<div className="HotbarSelector flex align-center">
<Icon material="play_arrow" className="previous box" onClick={() => store.switchToPrevious()} />
<div className="box grow flex align-center">
<Badge
id="hotbarIndex"
small
label={hotbarDisplayIndex(store.activeHotbarId)}
onClick={() => CommandOverlay.open(<HotbarSwitchCommand />)}
tooltip={{
preferredPositions: [TooltipPosition.TOP, TooltipPosition.TOP_LEFT],
children: hotbar.name,
}}
className="SelectorIndex"
/>
</div>
<Icon material="play_arrow" className="next box" onClick={() => store.switchToNext()} />
const NonInjectedHotbarSelector = observer(({ hotbar, hotbarManager, openCommandOverlay }: HotbarSelectorProps & Dependencies) => (
<div className="HotbarSelector flex align-center">
<Icon material="play_arrow" className="previous box" onClick={() => hotbarManager.switchToPrevious()} />
<div className="box grow flex align-center">
<Badge
id="hotbarIndex"
small
label={hotbarManager.getDisplayIndex(hotbarManager.getActive())}
onClick={() => openCommandOverlay(<HotbarSwitchCommand />)}
tooltip={{
preferredPositions: [TooltipPosition.TOP, TooltipPosition.TOP_LEFT],
children: hotbar.name,
}}
className="SelectorIndex"
/>
</div>
);
<Icon material="play_arrow" className="next box" onClick={() => hotbarManager.switchToNext()} />
</div>
));
export const HotbarSelector = withInjectables<Dependencies, HotbarSelectorProps>(NonInjectedHotbarSelector, {
getProps: (di, props) => ({
hotbarManager: di.inject(hotbarManagerInjectable),
openCommandOverlay: di.inject(commandOverlayInjectable).open,
...props,
}),
});
@@ -22,73 +22,82 @@
import React from "react";
import { observer } from "mobx-react";
import { Select } from "../select";
import { computed, makeObservable } from "mobx";
import { HotbarStore } from "../../../common/hotbar-store";
import { CommandOverlay } from "../command-palette";
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
import type { CommandOverlay } from "../command-palette";
import { HotbarAddCommand } from "./hotbar-add-command";
import { HotbarRemoveCommand } from "./hotbar-remove-command";
import { hotbarDisplayLabel } from "./hotbar-display-label";
import { HotbarRenameCommand } from "./hotbar-rename-command";
import type { Hotbar } from "../../../common/hotbar-types";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
@observer
export class HotbarSwitchCommand extends React.Component {
private static addActionId = "__add__";
private static removeActionId = "__remove__";
private static renameActionId = "__rename__";
const addActionId = "__add__";
const removeActionId = "__remove__";
const renameActionId = "__rename__";
constructor(props: {}) {
super(props);
makeObservable(this);
}
@computed get options() {
const hotbarStore = HotbarStore.getInstance();
const options = hotbarStore.hotbars.map((hotbar) => {
return { value: hotbar.id, label: hotbarDisplayLabel(hotbar.id) };
});
options.push({ value: HotbarSwitchCommand.addActionId, label: "Add hotbar ..." });
if (hotbarStore.hotbars.length > 1) {
options.push({ value: HotbarSwitchCommand.removeActionId, label: "Remove hotbar ..." });
}
options.push({ value: HotbarSwitchCommand.renameActionId, label: "Rename hotbar ..." });
return options;
}
onChange(idOrAction: string): void {
switch (idOrAction) {
case HotbarSwitchCommand.addActionId:
CommandOverlay.open(<HotbarAddCommand />);
return;
case HotbarSwitchCommand.removeActionId:
CommandOverlay.open(<HotbarRemoveCommand />);
return;
case HotbarSwitchCommand.renameActionId:
CommandOverlay.open(<HotbarRenameCommand />);
return;
default:
HotbarStore.getInstance().activeHotbarId = idOrAction;
CommandOverlay.close();
}
}
render() {
return (
<Select
menuPortalTarget={null}
onChange={(v) => this.onChange(v.value)}
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
menuIsOpen={true}
options={this.options}
autoFocus={true}
escapeClearsValue={false}
placeholder="Switch to hotbar" />
);
}
interface HotbarManager {
hotbars: Hotbar[];
setActiveHotbar: (id: string) => void;
getDisplayLabel: (hotbar: Hotbar) => string;
}
interface Dependencies {
hotbarManager: HotbarManager
commandOverlay: CommandOverlay;
}
function getHotbarSwitchOptions(hotbarManager: HotbarManager) {
const options = hotbarManager.hotbars.map(hotbar => ({
value: hotbar.id,
label: hotbarManager.getDisplayLabel(hotbar),
}));
options.push({ value: addActionId, label: "Add hotbar ..." });
if (hotbarManager.hotbars.length > 1) {
options.push({ value: removeActionId, label: "Remove hotbar ..." });
}
options.push({ value: renameActionId, label: "Rename hotbar ..." });
return options;
}
const NonInjectedHotbarSwitchCommand = observer(({ hotbarManager, commandOverlay }: Dependencies) => {
const options = getHotbarSwitchOptions(hotbarManager);
const onChange = (idOrAction: string): void => {
switch (idOrAction) {
case addActionId:
return commandOverlay.open(<HotbarAddCommand />);
case removeActionId:
return commandOverlay.open(<HotbarRemoveCommand />);
case renameActionId:
return commandOverlay.open(<HotbarRenameCommand />);
default:
hotbarManager.setActiveHotbar(idOrAction);
commandOverlay.close();
}
};
return (
<Select
menuPortalTarget={null}
onChange={(v) => onChange(v.value)}
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
menuIsOpen={true}
options={options}
autoFocus={true}
escapeClearsValue={false}
placeholder="Switch to hotbar"
/>
);
});
export const HotbarSwitchCommand = withInjectables<Dependencies>(NonInjectedHotbarSwitchCommand, {
getProps: (di, props) => ({
hotbarManager: di.inject(hotbarManagerInjectable),
commandOverlay: di.inject(commandOverlayInjectable),
...props,
}),
});