mirror of
https://github.com/ApfelTeeSaft/LiveSplitOne.git
synced 2026-08-26 19:23:40 +00:00
Implement Hotkey Settings
This commit is contained in:
+1
-1
Submodule livesplit-core updated: 789e2d007f...422dbb949a
@@ -892,6 +892,7 @@ export type SettingsDescriptionValueJson =
|
||||
{ ColumnStartWith: ColumnStartWith } |
|
||||
{ ColumnUpdateWith: ColumnUpdateWith } |
|
||||
{ ColumnUpdateTrigger: ColumnUpdateTrigger } |
|
||||
{ Hotkey: string } |
|
||||
{ CustomCombobox: CustomCombobox };
|
||||
|
||||
/**
|
||||
@@ -2848,6 +2849,112 @@ export class GraphComponentState extends GraphComponentStateRefMut {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration to use for a Hotkey System. It describes with keys to use
|
||||
* as hotkeys for the different actions.
|
||||
*/
|
||||
export class HotkeyConfigRef {
|
||||
ptr: number;
|
||||
/**
|
||||
* Encodes generic description of the settings available for the hotkey
|
||||
* configuration and their current values as JSON.
|
||||
*/
|
||||
settingsDescriptionAsJson(): any {
|
||||
if (this.ptr == 0) {
|
||||
throw "this is disposed";
|
||||
}
|
||||
const result = instance().exports.HotkeyConfig_settings_description_as_json(this.ptr);
|
||||
return JSON.parse(decodeString(result));
|
||||
}
|
||||
/**
|
||||
* Encodes the hotkey configuration as JSON.
|
||||
*/
|
||||
asJson(): any {
|
||||
if (this.ptr == 0) {
|
||||
throw "this is disposed";
|
||||
}
|
||||
const result = instance().exports.HotkeyConfig_as_json(this.ptr);
|
||||
return JSON.parse(decodeString(result));
|
||||
}
|
||||
/**
|
||||
* This constructor is an implementation detail. Do not use this.
|
||||
*/
|
||||
constructor(ptr: number) {
|
||||
this.ptr = ptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration to use for a Hotkey System. It describes with keys to use
|
||||
* as hotkeys for the different actions.
|
||||
*/
|
||||
export class HotkeyConfigRefMut extends HotkeyConfigRef {
|
||||
/**
|
||||
* Sets a setting's value by its index to the given value.
|
||||
*
|
||||
* false is returned if a hotkey is already in use by a different action.
|
||||
*
|
||||
* This panics if the type of the value to be set is not compatible with the
|
||||
* type of the setting's value. A panic can also occur if the index of the
|
||||
* setting provided is out of bounds.
|
||||
*/
|
||||
setValue(index: number, value: SettingValue): boolean {
|
||||
if (this.ptr == 0) {
|
||||
throw "this is disposed";
|
||||
}
|
||||
if (value.ptr == 0) {
|
||||
throw "value is disposed";
|
||||
}
|
||||
const result = instance().exports.HotkeyConfig_set_value(this.ptr, index, value.ptr) != 0;
|
||||
value.ptr = 0;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration to use for a Hotkey System. It describes with keys to use
|
||||
* as hotkeys for the different actions.
|
||||
*/
|
||||
export class HotkeyConfig extends HotkeyConfigRefMut {
|
||||
/**
|
||||
* Allows for scoped usage of the object. The object is guaranteed to get
|
||||
* disposed once this function returns. You are free to dispose the object
|
||||
* early yourself anywhere within the scope. The scope's return value gets
|
||||
* carried to the outside of this function.
|
||||
*/
|
||||
with<T>(closure: (obj: HotkeyConfig) => T): T {
|
||||
try {
|
||||
return closure(this);
|
||||
} finally {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Disposes the object, allowing it to clean up all of its memory. You need
|
||||
* to call this for every object that you don't use anymore and hasn't
|
||||
* already been disposed.
|
||||
*/
|
||||
dispose() {
|
||||
if (this.ptr != 0) {
|
||||
instance().exports.HotkeyConfig_drop(this.ptr);
|
||||
this.ptr = 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parses a hotkey configuration from the given JSON description. null is
|
||||
* returned if it couldn't be parsed.
|
||||
*/
|
||||
static parseJson(settings: any): HotkeyConfig | null {
|
||||
const settings_allocated = allocString(JSON.stringify(settings));
|
||||
const result = new HotkeyConfig(instance().exports.HotkeyConfig_parse_json(settings_allocated.ptr));
|
||||
dealloc(settings_allocated);
|
||||
if (result.ptr == 0) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* With a Hotkey System the runner can use hotkeys on their keyboard to control
|
||||
* the Timer. The hotkeys are global, so the application doesn't need to be in
|
||||
@@ -2877,6 +2984,16 @@ export class HotkeySystemRef {
|
||||
}
|
||||
instance().exports.HotkeySystem_activate(this.ptr);
|
||||
}
|
||||
/**
|
||||
* Returns the hotkey configuration currently in use by the Hotkey System.
|
||||
*/
|
||||
config(): HotkeyConfig {
|
||||
if (this.ptr == 0) {
|
||||
throw "this is disposed";
|
||||
}
|
||||
const result = new HotkeyConfig(instance().exports.HotkeySystem_config(this.ptr));
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* This constructor is an implementation detail. Do not use this.
|
||||
*/
|
||||
@@ -2893,6 +3010,23 @@ export class HotkeySystemRef {
|
||||
* System temporarily. By default the Hotkey System is activated.
|
||||
*/
|
||||
export class HotkeySystemRefMut extends HotkeySystemRef {
|
||||
/**
|
||||
* Applies a new hotkey configuration to the Hotkey System. Each hotkey is
|
||||
* changed to the one specified in the configuration. This operation may fail
|
||||
* if you provide a hotkey configuration where a hotkey is used for multiple
|
||||
* operations. Returns false if the operation failed.
|
||||
*/
|
||||
setConfig(config: HotkeyConfig): boolean {
|
||||
if (this.ptr == 0) {
|
||||
throw "this is disposed";
|
||||
}
|
||||
if (config.ptr == 0) {
|
||||
throw "config is disposed";
|
||||
}
|
||||
const result = instance().exports.HotkeySystem_set_config(this.ptr, config.ptr) != 0;
|
||||
config.ptr = 0;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2941,6 +3075,25 @@ export class HotkeySystem extends HotkeySystemRefMut {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Creates a new Hotkey System for a Timer with a custom configuration for the
|
||||
* hotkeys.
|
||||
*/
|
||||
static withConfig(sharedTimer: SharedTimer, config: HotkeyConfig): HotkeySystem | null {
|
||||
if (sharedTimer.ptr == 0) {
|
||||
throw "sharedTimer is disposed";
|
||||
}
|
||||
if (config.ptr == 0) {
|
||||
throw "config is disposed";
|
||||
}
|
||||
const result = new HotkeySystem(instance().exports.HotkeySystem_with_config(sharedTimer.ptr, config.ptr));
|
||||
sharedTimer.ptr = 0;
|
||||
config.ptr = 0;
|
||||
if (result.ptr == 0) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.hotkey-button {
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
font-size: 16px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.hotkey-button:focus {
|
||||
color: red;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import * as React from "react";
|
||||
import { Option } from "../util/OptionUtil";
|
||||
|
||||
import "./HotkeyButton.css";
|
||||
|
||||
export interface Props {
|
||||
value: string,
|
||||
setValue: (value: string) => void,
|
||||
}
|
||||
|
||||
export interface State {
|
||||
listener: Option<EventListenerObject>,
|
||||
}
|
||||
|
||||
export class HotkeyButton extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
listener: null,
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<button
|
||||
className="hotkey-button"
|
||||
onFocus={() => {
|
||||
const listener = {
|
||||
handleEvent: (ev: KeyboardEvent) => this.props.setValue(ev.code),
|
||||
};
|
||||
|
||||
window.addEventListener("keypress", listener);
|
||||
|
||||
this.setState({
|
||||
...this.state,
|
||||
listener,
|
||||
});
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (this.state.listener != null) {
|
||||
window.removeEventListener("keypress", this.state.listener);
|
||||
|
||||
this.setState({
|
||||
...this.state,
|
||||
listener: null,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{this.props.value}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
+115
-40
@@ -4,23 +4,36 @@ import AutoRefreshLayout from "../layout/AutoRefreshLayout";
|
||||
import {
|
||||
HotkeySystem, Layout, LayoutEditor, Run, RunEditor,
|
||||
Segment, SharedTimer, Timer, TimerPhase, TimingMethod,
|
||||
TimeSpan, TimerRef, TimerRefMut,
|
||||
TimeSpan, TimerRef, TimerRefMut, HotkeyConfig,
|
||||
} from "../livesplit";
|
||||
import { exportFile, openFileAsArrayBuffer, openFileAsString } from "../util/FileUtil";
|
||||
import { Option, assertNull, expect, maybeDispose, maybeDisposeAndThen, map } from "../util/OptionUtil";
|
||||
import { Option, assertNull, expect, maybeDispose, maybeDisposeAndThen, map, panic } from "../util/OptionUtil";
|
||||
import * as SplitsIO from "../util/SplitsIO";
|
||||
import { LayoutEditor as LayoutEditorComponent } from "./LayoutEditor";
|
||||
import { RunEditor as RunEditorComponent } from "./RunEditor";
|
||||
import { SettingsEditor as SettingsEditorComponent } from "./SettingsEditor";
|
||||
import { Route, SideBarContent } from "./SideBarContent";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
enum MenuKind {
|
||||
Timer,
|
||||
RunEditor,
|
||||
LayoutEditor,
|
||||
SettingsEditor,
|
||||
}
|
||||
|
||||
type Menu =
|
||||
{ kind: MenuKind.Timer } |
|
||||
{ kind: MenuKind.RunEditor, editor: RunEditor } |
|
||||
{ kind: MenuKind.LayoutEditor, editor: LayoutEditor } |
|
||||
{ kind: MenuKind.SettingsEditor, config: HotkeyConfig };
|
||||
|
||||
export interface State {
|
||||
hotkeySystem: Option<HotkeySystem>,
|
||||
hotkeySystem: HotkeySystem,
|
||||
timer: SharedTimer,
|
||||
layout: Layout,
|
||||
sidebarOpen: boolean,
|
||||
runEditor: Option<RunEditor>,
|
||||
layoutEditor: Option<LayoutEditor>,
|
||||
menu: Menu,
|
||||
}
|
||||
|
||||
export class LiveSplit extends React.Component<{}, State> {
|
||||
@@ -41,7 +54,22 @@ export class LiveSplit extends React.Component<{}, State> {
|
||||
"The Default Run should be a valid Run",
|
||||
).intoShared();
|
||||
|
||||
const hotkeySystem = HotkeySystem.new(timer.share());
|
||||
let hotkeySystem = null;
|
||||
const settings = localStorage.getItem("settings");
|
||||
try {
|
||||
if (settings) {
|
||||
const config = HotkeyConfig.parseJson(JSON.parse(settings).hotkeys);
|
||||
if (config != null) {
|
||||
hotkeySystem = HotkeySystem.withConfig(timer.share(), config);
|
||||
}
|
||||
}
|
||||
} catch (_) { /* Looks like local storage has no valid data */ }
|
||||
if (hotkeySystem == null) {
|
||||
hotkeySystem = expect(
|
||||
HotkeySystem.new(timer.share()),
|
||||
"Couldn't initialize the hotkeys",
|
||||
);
|
||||
}
|
||||
|
||||
if (window.location.hash.indexOf("#/splits-io/") === 0) {
|
||||
const loadingRun = Run.new();
|
||||
@@ -78,8 +106,7 @@ export class LiveSplit extends React.Component<{}, State> {
|
||||
|
||||
this.state = {
|
||||
layout,
|
||||
layoutEditor: null,
|
||||
runEditor: null,
|
||||
menu: { kind: MenuKind.Timer },
|
||||
sidebarOpen: false,
|
||||
timer,
|
||||
hotkeySystem,
|
||||
@@ -117,19 +144,24 @@ export class LiveSplit extends React.Component<{}, State> {
|
||||
|
||||
public render() {
|
||||
const [route, content] = ((): [Route, JSX.Element] => {
|
||||
if (this.state.runEditor) {
|
||||
if (this.state.menu.kind === MenuKind.RunEditor) {
|
||||
return [
|
||||
"run-editor",
|
||||
<RunEditorComponent editor={this.state.runEditor} />,
|
||||
<RunEditorComponent editor={this.state.menu.editor} />,
|
||||
];
|
||||
} else if (this.state.layoutEditor) {
|
||||
} else if (this.state.menu.kind === MenuKind.LayoutEditor) {
|
||||
return [
|
||||
"layout-editor",
|
||||
<LayoutEditorComponent
|
||||
editor={this.state.layoutEditor}
|
||||
editor={this.state.menu.editor}
|
||||
timer={this.state.timer}
|
||||
/>,
|
||||
];
|
||||
} else if (this.state.menu.kind === MenuKind.SettingsEditor) {
|
||||
return [
|
||||
"settings-editor",
|
||||
<SettingsEditorComponent hotkeyConfig={this.state.menu.config} />,
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
"main",
|
||||
@@ -316,13 +348,14 @@ export class LiveSplit extends React.Component<{}, State> {
|
||||
});
|
||||
|
||||
if (run != null) {
|
||||
if (this.state.hotkeySystem != null) {
|
||||
this.state.hotkeySystem.deactivate();
|
||||
}
|
||||
const editor = RunEditor.new(run);
|
||||
this.state.hotkeySystem.deactivate();
|
||||
const editor = expect(
|
||||
RunEditor.new(run),
|
||||
"The Run Editor should always be able to be opened.",
|
||||
);
|
||||
this.setState({
|
||||
...this.state,
|
||||
runEditor: editor,
|
||||
menu: { kind: MenuKind.RunEditor, editor },
|
||||
sidebarOpen: false,
|
||||
});
|
||||
} else {
|
||||
@@ -331,61 +364,62 @@ export class LiveSplit extends React.Component<{}, State> {
|
||||
}
|
||||
|
||||
public closeRunEditor(save: boolean) {
|
||||
const runEditor = expect(
|
||||
this.state.runEditor,
|
||||
"No Run Editor to close",
|
||||
);
|
||||
if (this.state.menu.kind !== MenuKind.RunEditor) {
|
||||
panic("No Run Editor to close");
|
||||
return;
|
||||
}
|
||||
const runEditor = this.state.menu.editor;
|
||||
const run = runEditor.close();
|
||||
if (save) {
|
||||
assertNull(
|
||||
this.writeWith((t) => t.setRun(run)),
|
||||
"The Run Editor should always return a valid Run",
|
||||
"The Run Editor should always return a valid Run.",
|
||||
);
|
||||
this.setState({
|
||||
...this.state,
|
||||
runEditor: null,
|
||||
menu: { kind: MenuKind.Timer },
|
||||
sidebarOpen: false,
|
||||
});
|
||||
} else {
|
||||
run.dispose();
|
||||
this.setState({
|
||||
...this.state,
|
||||
runEditor: null,
|
||||
menu: { kind: MenuKind.Timer },
|
||||
sidebarOpen: false,
|
||||
});
|
||||
}
|
||||
this.state.layout.remount();
|
||||
if (this.state.hotkeySystem != null) {
|
||||
this.state.hotkeySystem.activate();
|
||||
}
|
||||
this.state.hotkeySystem.activate();
|
||||
}
|
||||
|
||||
public openLayoutEditor() {
|
||||
if (this.state.hotkeySystem != null) {
|
||||
this.state.hotkeySystem.deactivate();
|
||||
}
|
||||
this.state.hotkeySystem.deactivate();
|
||||
|
||||
const layout = this.state.layout.clone();
|
||||
const editor = LayoutEditor.new(layout);
|
||||
const editor = expect(
|
||||
LayoutEditor.new(layout),
|
||||
"The Layout Editor should always be able to be opened.",
|
||||
);
|
||||
this.setState({
|
||||
...this.state,
|
||||
layoutEditor: editor,
|
||||
menu: { kind: MenuKind.LayoutEditor, editor },
|
||||
sidebarOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
public closeLayoutEditor(save: boolean) {
|
||||
const layoutEditor = expect(
|
||||
this.state.layoutEditor,
|
||||
"No Layout Editor to close",
|
||||
);
|
||||
if (this.state.menu.kind !== MenuKind.LayoutEditor) {
|
||||
panic("No Layout Editor to close.");
|
||||
return;
|
||||
}
|
||||
const layoutEditor = this.state.menu.editor;
|
||||
const layout = layoutEditor.close();
|
||||
if (save) {
|
||||
this.state.layout.dispose();
|
||||
this.setState({
|
||||
...this.state,
|
||||
layout,
|
||||
layoutEditor: null,
|
||||
menu: { kind: MenuKind.Timer },
|
||||
sidebarOpen: false,
|
||||
});
|
||||
layout.remount();
|
||||
@@ -393,14 +427,55 @@ export class LiveSplit extends React.Component<{}, State> {
|
||||
layout.dispose();
|
||||
this.setState({
|
||||
...this.state,
|
||||
layoutEditor: null,
|
||||
menu: { kind: MenuKind.Timer },
|
||||
sidebarOpen: false,
|
||||
});
|
||||
this.state.layout.remount();
|
||||
}
|
||||
if (this.state.hotkeySystem != null) {
|
||||
this.state.hotkeySystem.activate();
|
||||
this.state.hotkeySystem.activate();
|
||||
}
|
||||
|
||||
public openSettingsEditor() {
|
||||
this.state.hotkeySystem.deactivate();
|
||||
this.setState({
|
||||
...this.state,
|
||||
menu: {
|
||||
kind: MenuKind.SettingsEditor,
|
||||
config: this.state.hotkeySystem.config(),
|
||||
},
|
||||
sidebarOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
public closeSettingsEditor(save: boolean) {
|
||||
const menu = this.state.menu;
|
||||
|
||||
if (menu.kind !== MenuKind.SettingsEditor) {
|
||||
panic("No Settings Editor to close.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (save) {
|
||||
try {
|
||||
const hotkeys = menu.config.asJson();
|
||||
const settings = { hotkeys };
|
||||
localStorage.setItem("settings", JSON.stringify(settings));
|
||||
} catch (_) {
|
||||
toast.error("Failed to save the settings.");
|
||||
}
|
||||
this.state.hotkeySystem.setConfig(menu.config);
|
||||
} else {
|
||||
menu.config.dispose();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
...this.state,
|
||||
menu: { kind: MenuKind.Timer },
|
||||
sidebarOpen: false,
|
||||
});
|
||||
|
||||
this.state.layout.remount();
|
||||
this.state.hotkeySystem.activate();
|
||||
}
|
||||
|
||||
public connectToServerOrDisconnect() {
|
||||
|
||||
@@ -3,6 +3,9 @@ import { Color, SettingsDescriptionJson, SettingsDescriptionValueJson } from "..
|
||||
import { assertNever, expect, Option } from "../util/OptionUtil";
|
||||
import ColorPicker from "./ColorPicker";
|
||||
|
||||
import "./Settings.css";
|
||||
import { HotkeyButton } from "./HotkeyButton";
|
||||
|
||||
export interface Props<T> {
|
||||
setValue: (index: number, value: T) => void,
|
||||
state: SettingsDescriptionJson,
|
||||
@@ -705,6 +708,18 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
>
|
||||
{value.CustomCombobox.list.map((v) => <option value={v}>{v}</option>)}
|
||||
</select>;
|
||||
} else if ("Hotkey" in value) {
|
||||
component = (
|
||||
<HotkeyButton
|
||||
value={value.Hotkey}
|
||||
setValue={(value) => {
|
||||
this.props.setValue(
|
||||
valueIndex,
|
||||
factory.fromString(value),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
assertNever(value);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.settings-editor {
|
||||
padding: 10px 10px 10px 10px;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import * as React from "react";
|
||||
|
||||
import "./SettingsEditor.css";
|
||||
import { SettingsComponent } from "./Settings";
|
||||
import { SettingsDescriptionJson, SettingValue, HotkeyConfig } from "../livesplit";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export interface Props {
|
||||
hotkeyConfig: HotkeyConfig,
|
||||
}
|
||||
|
||||
export interface State {
|
||||
settings: SettingsDescriptionJson,
|
||||
}
|
||||
|
||||
export class SettingsEditor extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
settings: props.hotkeyConfig.settingsDescriptionAsJson(),
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="settings-editor">
|
||||
<SettingsComponent
|
||||
factory={SettingValue}
|
||||
state={this.state.settings}
|
||||
setValue={(index, value) => {
|
||||
if (!this.props.hotkeyConfig.setValue(index, value)) {
|
||||
toast.error("The hotkey is already in use.");
|
||||
return;
|
||||
}
|
||||
this.update();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private update() {
|
||||
this.setState({
|
||||
...this.state,
|
||||
settings: this.props.hotkeyConfig.settingsDescriptionAsJson(),
|
||||
});
|
||||
}
|
||||
}
|
||||
+48
-23
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import { SharedTimerRef, TimingMethod } from "../livesplit";
|
||||
import { Option } from "../util/OptionUtil";
|
||||
|
||||
export type Route = "main" | "run-editor" | "layout-editor";
|
||||
export type Route = "main" | "run-editor" | "layout-editor" | "settings-editor";
|
||||
|
||||
export interface SidebarCallbacks {
|
||||
closeRunEditor(save: boolean): void,
|
||||
@@ -21,6 +21,8 @@ export interface SidebarCallbacks {
|
||||
switchToNextComparison(): void,
|
||||
setCurrentTimingMethod(timingMethod: TimingMethod): void,
|
||||
connectToServerOrDisconnect(): void,
|
||||
openSettingsEditor(): void,
|
||||
closeSettingsEditor(save: boolean): void,
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
@@ -101,6 +103,26 @@ export class SideBarContent extends React.Component<Props, State> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case "settings-editor": {
|
||||
return (
|
||||
<div className="sidebar-buttons">
|
||||
<div className="small">
|
||||
<button
|
||||
className="toggle-left"
|
||||
onClick={(_) => this.props.callbacks.closeSettingsEditor(true)}
|
||||
>
|
||||
<i className="fa fa-check" aria-hidden="true" /> OK
|
||||
</button>
|
||||
<button
|
||||
className="toggle-right"
|
||||
onClick={(_) => this.props.callbacks.closeSettingsEditor(false)}
|
||||
>
|
||||
<i className="fa fa-times" aria-hidden="true" /> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case "main": {
|
||||
return (
|
||||
<div className="sidebar-buttons">
|
||||
@@ -137,28 +159,6 @@ export class SideBarContent extends React.Component<Props, State> {
|
||||
<i className="fa fa-upload" aria-hidden="true" /> Export Layout
|
||||
</button>
|
||||
<hr />
|
||||
<button onClick={(_) => this.props.callbacks.connectToServerOrDisconnect()}>
|
||||
{
|
||||
(() => {
|
||||
switch (this.props.connectionState) {
|
||||
case WebSocket.OPEN:
|
||||
return <div>
|
||||
<i className="fa fa-power-off" aria-hidden="true" /> Disconnect
|
||||
</div>;
|
||||
case WebSocket.CLOSED:
|
||||
return <div>
|
||||
<i className="fa fa-desktop" aria-hidden="true" /> Connect to Server
|
||||
</div>;
|
||||
case WebSocket.CONNECTING:
|
||||
return <div>Connecting...</div>;
|
||||
case WebSocket.CLOSING:
|
||||
return <div>Disconnecting...</div>;
|
||||
default: throw new Error("Unknown WebSocket State");
|
||||
}
|
||||
})()
|
||||
}
|
||||
</button>
|
||||
<hr />
|
||||
<h2>Compare Against</h2>
|
||||
<div className="choose-comparison">
|
||||
<button onClick={(_) => this.props.callbacks.switchToPreviousComparison()}>
|
||||
@@ -195,6 +195,31 @@ export class SideBarContent extends React.Component<Props, State> {
|
||||
Game Time
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
<button onClick={(_) => this.props.callbacks.connectToServerOrDisconnect()}>
|
||||
{
|
||||
(() => {
|
||||
switch (this.props.connectionState) {
|
||||
case WebSocket.OPEN:
|
||||
return <div>
|
||||
<i className="fa fa-power-off" aria-hidden="true" /> Disconnect
|
||||
</div>;
|
||||
case WebSocket.CLOSED:
|
||||
return <div>
|
||||
<i className="fa fa-desktop" aria-hidden="true" /> Connect to Server
|
||||
</div>;
|
||||
case WebSocket.CONNECTING:
|
||||
return <div>Connecting...</div>;
|
||||
case WebSocket.CLOSING:
|
||||
return <div>Disconnecting...</div>;
|
||||
default: throw new Error("Unknown WebSocket State");
|
||||
}
|
||||
})()
|
||||
}
|
||||
</button>
|
||||
<button onClick={() => this.props.callbacks.openSettingsEditor()}>
|
||||
<i className="fa fa-cog" aria-hidden="true" /> Settings
|
||||
</button>
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ export function expect<T>(obj: Option<T>, message: string): T {
|
||||
if (obj != null) {
|
||||
return obj;
|
||||
}
|
||||
toast.error(`Bug: ${message}`);
|
||||
throw new Error(message);
|
||||
return panic(message);
|
||||
}
|
||||
|
||||
interface Disposable {
|
||||
@@ -18,11 +17,16 @@ interface MaybeDisposable {
|
||||
dispose?(): void,
|
||||
}
|
||||
|
||||
export function panic(message: string): never {
|
||||
toast.error(`Bug: ${message}`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
export function assertNever(x: never): never { return x; }
|
||||
|
||||
export function assert(condition: boolean, message: string) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
panic(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +35,7 @@ export function assertNull(obj: Option<MaybeDisposable>, message: string) {
|
||||
if (obj.dispose) {
|
||||
obj.dispose();
|
||||
}
|
||||
throw new Error(message);
|
||||
panic(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user