From 84c42503dd9eb0adee16ba648e54a23c7c9db19f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 May 2024 19:07:07 +0000 Subject: [PATCH 01/30] Bump @typescript-eslint/parser from 7.7.0 to 7.10.0 (#890) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 7.7.0 to 7.10.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.10.0/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 50 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index e25aef1b..202195e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "@types/node": "^20.12.7", "@types/selenium-webdriver": "^4.1.21", "@typescript-eslint/eslint-plugin": "^7.7.0", - "@typescript-eslint/parser": "^7.8.0", + "@typescript-eslint/parser": "^7.10.0", "chromedriver": "^125.0.2", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.7.3", @@ -3248,15 +3248,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz", - "integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.10.0.tgz", + "integrity": "sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.8.0", - "@typescript-eslint/types": "7.8.0", - "@typescript-eslint/typescript-estree": "7.8.0", - "@typescript-eslint/visitor-keys": "7.8.0", + "@typescript-eslint/scope-manager": "7.10.0", + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/typescript-estree": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0", "debug": "^4.3.4" }, "engines": { @@ -3276,13 +3276,13 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz", - "integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz", + "integrity": "sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.8.0", - "@typescript-eslint/visitor-keys": "7.8.0" + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -3293,9 +3293,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz", - "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.10.0.tgz", + "integrity": "sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -3306,13 +3306,13 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz", - "integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz", + "integrity": "sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.8.0", - "@typescript-eslint/visitor-keys": "7.8.0", + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3334,12 +3334,12 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz", - "integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz", + "integrity": "sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/types": "7.10.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { diff --git a/package.json b/package.json index c4344962..32dc4fc7 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@types/node": "^20.12.7", "@types/selenium-webdriver": "^4.1.21", "@typescript-eslint/eslint-plugin": "^7.7.0", - "@typescript-eslint/parser": "^7.8.0", + "@typescript-eslint/parser": "^7.10.0", "chromedriver": "^125.0.2", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.7.3", From 392d669ed082e4a5557e8359243a2ffbe54ec83d Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Mon, 27 May 2024 19:16:55 +0200 Subject: [PATCH 02/30] Clean up and move server connection to settings (#895) This moves the server connection button to the settings and cleans the entire functionality up in various ways. The server connection no longer gets closed by navigating around the application. Additionally the server URL is now being remembered between sessions. This also improves the design of the toast messages. --- src/api/LiveSplitServer.ts | 96 ++++++++++++++++++++++ src/css/About.scss | 22 ++--- src/css/LiveSplitServerButton.scss | 6 ++ src/css/Tooltip.scss | 1 + src/css/main.scss | 15 ++-- src/index.tsx | 4 +- src/storage/index.tsx | 1 + src/ui/About.tsx | 4 +- src/ui/HotkeyButton.tsx | 4 +- src/ui/LayoutView.tsx | 6 ++ src/ui/LiveSplit.tsx | 78 ++++++++++++++++-- src/ui/RunEditor.tsx | 4 +- src/ui/Settings.tsx | 48 ++++++++++- src/ui/SettingsEditor.tsx | 49 +++++++++++ src/ui/TimerView.tsx | 126 +---------------------------- src/util/FrameRate.ts | 54 +++++++++---- webpack.config.js | 2 +- 17 files changed, 349 insertions(+), 171 deletions(-) create mode 100644 src/api/LiveSplitServer.ts create mode 100644 src/css/LiveSplitServerButton.scss diff --git a/src/api/LiveSplitServer.ts b/src/api/LiveSplitServer.ts new file mode 100644 index 00000000..e167e8ca --- /dev/null +++ b/src/api/LiveSplitServer.ts @@ -0,0 +1,96 @@ +import { toast } from "react-toastify"; + +interface Callbacks { + start(): void, + split(): void, + splitOrStart(): void, + reset(): void, + togglePauseOrStart(): void, + undoSplit(): void, + skipSplit(): void, + initializeGameTime(): void, + setGameTime(time: string): void, + setLoadingTimes(time: string): void, + pauseGameTime(): void, + resumeGameTime(): void, + forceUpdate(): void, + onServerConnectionClosed(): void; +} + +export class LiveSplitServer { + private connection: WebSocket; + private wasIntendingToDisconnect = false; + + constructor( + url: string, + private callbacks: Callbacks, + ) { + try { + this.connection = new WebSocket(url); + } catch (e: any) { + toast.error(`Failed to connect to the server: ${e.message}`); + throw e; + } + + callbacks.forceUpdate(); + + let wasConnected = false; + + this.connection.onopen = (_) => { + wasConnected = true; + toast.info("Connected to the server."); + callbacks.forceUpdate(); + }; + + this.connection.onerror = (e) => { + toast.error(e); + }; + + this.connection.onmessage = (e) => { + if (typeof e.data === "string") { + const [command, ...args] = e.data.split(" "); + switch (command) { + case "start": callbacks.start(); break; + case "split": callbacks.split(); break; + case "splitorstart": callbacks.splitOrStart(); break; + case "reset": callbacks.reset(); break; + case "togglepause": callbacks.togglePauseOrStart(); break; + case "undo": callbacks.undoSplit(); break; + case "skip": callbacks.skipSplit(); break; + case "initgametime": callbacks.initializeGameTime(); break; + case "setgametime": callbacks.setGameTime(args[0]); break; + case "setloadingtimes": callbacks.setLoadingTimes(args[0]); break; + case "pausegametime": callbacks.pauseGameTime(); break; + case "resumegametime": callbacks.resumeGameTime(); break; + } + } + }; + + this.connection.onclose = (ev) => { + const reason = ev.reason ? `: ${ev.reason}` : "."; + if (wasConnected) { + if (this.wasIntendingToDisconnect) { + toast.info("Closed the connection to the server."); + } else { + toast.error(`Lost the connection to the server${reason}`); + } + } else { + toast.error(`Failed to connect to the server${reason}`); + } + callbacks.onServerConnectionClosed(); + callbacks.forceUpdate(); + }; + } + + close(): void { + if (this.connection.readyState === WebSocket.OPEN) { + this.wasIntendingToDisconnect = true; + this.connection.close(); + this.callbacks.forceUpdate(); + } + } + + getConnectionState(): number { + return this.connection.readyState; + } +} diff --git a/src/css/About.scss b/src/css/About.scss index 4cc1b649..16e90d9c 100644 --- a/src/css/About.scss +++ b/src/css/About.scss @@ -4,7 +4,7 @@ $text-width: 400px; $icon-size: 40px; $title-font-size: 40px; -$build-version-font-size: 11px; +$build-version-font-size: 12px; $link-color: #56b0ff; .about { @@ -13,47 +13,47 @@ $link-color: #56b0ff; padding: $ui-large-margin; border: 1px solid $border-color; width: fit-content; - + .livesplit-title { display: flex; align-items: center; - + .livesplit-icon { height: $icon-size; margin-right: $ui-margin; - + img { height: 100%; } } - + .title-text { font-weight: bold; font-size: $title-font-size; } } - + .build-version { font-size: $build-version-font-size; } - + .contributors-header { margin-bottom: 0; } - + .contributor { margin-block-start: 0.5em; margin-block-end: 0.5em; - + &:last-child { margin-block-end: 0; } } - + a { color: $link-color; } - + p { max-width: $text-width; diff --git a/src/css/LiveSplitServerButton.scss b/src/css/LiveSplitServerButton.scss new file mode 100644 index 00000000..c653a427 --- /dev/null +++ b/src/css/LiveSplitServerButton.scss @@ -0,0 +1,6 @@ +.livesplit-server-button { + margin: 0px; + font-size: 16px; + height: 22px; + // TODO: This is the same as the hotkey button. +} diff --git a/src/css/Tooltip.scss b/src/css/Tooltip.scss index 2406ce03..f2401774 100644 --- a/src/css/Tooltip.scss +++ b/src/css/Tooltip.scss @@ -23,6 +23,7 @@ transition: opacity 0.25s; transition-delay: 0.25s; text-wrap: initial; + font-weight: initial; @include mobile { left: 0; diff --git a/src/css/main.scss b/src/css/main.scss index e388339a..6f6f96c8 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -22,16 +22,21 @@ body { background: $main-background-color; } -.toast-class { - border-radius: 2px !important; - min-height: 48px !important; +.Toastify { + .toast-class { + font-family: "fira", sans-serif; + background: $sidebar-background-color !important; + border: 2px solid $border-color !important; + border-radius: 10px !important; + min-height: 48px !important; + } } .toast-body { - margin: 10px 5px 10px 5px !important; + margin: 5px 5px 5px 5px !important; } -.toast-class > button { +.toast-class>button { margin-bottom: initial; margin-top: initial; } diff --git a/src/index.tsx b/src/index.tsx index 8799ee66..2155dc76 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -96,9 +96,7 @@ try { position={toast.POSITION.BOTTOM_RIGHT} toastClassName="toast-class" bodyClassName="toast-body" - style={{ - textShadow: "none", - }} + theme="dark" /> , document.getElementById("base"), diff --git a/src/storage/index.tsx b/src/storage/index.tsx index 0e96eaf7..ba226f7d 100644 --- a/src/storage/index.tsx +++ b/src/storage/index.tsx @@ -254,5 +254,6 @@ export async function loadGeneralSettings(): Promise { showManualGameTime: generalSettings.showManualGameTime ?? false, speedrunComIntegration: generalSettings.speedrunComIntegration ?? true, splitsIoIntegration: generalSettings.splitsIoIntegration ?? true, + serverUrl: generalSettings.serverUrl, }; } diff --git a/src/ui/About.tsx b/src/ui/About.tsx index a347cd74..4df57eb6 100644 --- a/src/ui/About.tsx +++ b/src/ui/About.tsx @@ -32,7 +32,7 @@ export class About extends React.Component {

- Version: {COMMIT_HASH} ({BUILD_DATE}) + Version: {BUILD_DATE}

LiveSplit One is a multiplatform version of LiveSplit, the sleek, @@ -42,7 +42,7 @@ export class About extends React.Component { View Source Code on GitHub

-

Contributors

+

Contributors

{ CONTRIBUTORS_LIST.map((contributor) => (

diff --git a/src/ui/HotkeyButton.tsx b/src/ui/HotkeyButton.tsx index dc1d40fc..6edf9d2d 100644 --- a/src/ui/HotkeyButton.tsx +++ b/src/ui/HotkeyButton.tsx @@ -45,10 +45,11 @@ export default class HotkeyButton extends React.Component { return (

{ map(this.props.value, () => ( @@ -102,6 +103,7 @@ export default class HotkeyButton extends React.Component { } text += ev.code; this.props.setValue(text); + ev.preventDefault(); } }; diff --git a/src/ui/LayoutView.tsx b/src/ui/LayoutView.tsx index c82f51d8..e6202ddb 100644 --- a/src/ui/LayoutView.tsx +++ b/src/ui/LayoutView.tsx @@ -4,6 +4,8 @@ import { TimerView } from "./TimerView"; import { UrlCache } from "../util/UrlCache"; import { WebRenderer } from "../livesplit-core/livesplit_core"; import { GeneralSettings } from "./SettingsEditor"; +import { LiveSplitServer } from "../api/LiveSplitServer"; +import { Option } from "../util/OptionUtil"; export interface Props { isDesktop: boolean, @@ -17,6 +19,7 @@ export interface Props { sidebarOpen: boolean, timer: SharedTimer, renderer: WebRenderer, + serverConnection: Option, callbacks: Callbacks, } @@ -35,6 +38,8 @@ interface Callbacks { openTimerView(): void, renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element, saveLayout(): void, + onServerConnectionOpened(serverConnection: LiveSplitServer): void, + onServerConnectionClosed(): void, } export class LayoutView extends React.Component { @@ -51,6 +56,7 @@ export class LayoutView extends React.Component { sidebarOpen={this.props.sidebarOpen} timer={this.props.timer} renderer={this.props.renderer} + serverConnection={this.props.serverConnection} callbacks={this.props.callbacks} />; const sidebarContent = this.renderSidebarContent(); diff --git a/src/ui/LiveSplit.tsx b/src/ui/LiveSplit.tsx index eb7f5e67..6087e379 100644 --- a/src/ui/LiveSplit.tsx +++ b/src/ui/LiveSplit.tsx @@ -4,6 +4,7 @@ import { HotkeySystem, Layout, LayoutEditor, Run, RunEditor, Segment, SharedTimer, Timer, TimerRef, TimerRefMut, HotkeyConfig, LayoutState, LayoutStateJson, + TimeSpan, } from "../livesplit-core"; import { convertFileToArrayBuffer, convertFileToString, exportFile, openFileAsString } from "../util/FileUtil"; import { Option, assertNull, expect, maybeDisposeAndThen, panic } from "../util/OptionUtil"; @@ -20,6 +21,7 @@ import * as Storage from "../storage"; import { UrlCache } from "../util/UrlCache"; import { WebRenderer } from "../livesplit-core/livesplit_core"; import variables from "../css/variables.scss"; +import { LiveSplitServer } from "../api/LiveSplitServer"; import "react-toastify/dist/ReactToastify.css"; import "../css/LiveSplit.scss"; @@ -77,6 +79,7 @@ export interface State { timer: SharedTimer, renderer: WebRenderer, generalSettings: GeneralSettings, + serverConnection: Option, } export let hotkeySystem: Option = null; @@ -187,18 +190,18 @@ export class LiveSplit extends React.Component { openedSplitsKey: props.splitsKey, renderer, generalSettings: props.generalSettings, + serverConnection: null, }; this.mediaQueryChanged = this.mediaQueryChanged.bind(this); } - private notifyAboutUpdate(this: void) - { + private notifyAboutUpdate(this: void) { toast.warn( 'A new version of LiveSplit One is available! Click here to reload.', { - closeOnClick: true, - onClick: () => window.location.reload(), + closeOnClick: true, + onClick: () => window.location.reload(), }, ); } @@ -232,7 +235,7 @@ export class LiveSplit extends React.Component { const { serviceWorker } = navigator; if (serviceWorker) { - serviceWorker.addEventListener('controllerchange', this.notifyAboutUpdate); + serviceWorker.addEventListener('controllerchange', this.notifyAboutUpdate); } } @@ -296,6 +299,7 @@ export class LiveSplit extends React.Component { hotkeyConfig={this.state.menu.config} urlCache={this.state.layoutUrlCache} callbacks={this} + serverConnection={this.state.serverConnection} />; } else if (this.state.menu.kind === MenuKind.About) { return ; @@ -319,6 +323,7 @@ export class LiveSplit extends React.Component { sidebarOpen={this.state.sidebarOpen} timer={this.state.timer} renderer={this.state.renderer} + serverConnection={this.state.serverConnection} callbacks={this} />; } else if (this.state.menu.kind === MenuKind.Layout) { @@ -334,6 +339,7 @@ export class LiveSplit extends React.Component { sidebarOpen={this.state.sidebarOpen} timer={this.state.timer} renderer={this.state.renderer} + serverConnection={this.state.serverConnection} callbacks={this} />; } @@ -691,4 +697,66 @@ export class LiveSplit extends React.Component { private readWith(action: (timer: TimerRef) => T): T { return this.state.timer.readWith(action); } + + onServerConnectionOpened(serverConnection: LiveSplitServer): void { + this.setState({ serverConnection }); + } + + onServerConnectionClosed(): void { + this.setState({ serverConnection: null }); + } + + start() { + this.writeWith((t) => t.start()); + } + + split() { + this.writeWith((t) => t.split()); + } + + splitOrStart() { + this.writeWith((t) => t.splitOrStart()); + } + + reset() { + this.writeWith((t) => t.reset(true)); + } + + togglePauseOrStart() { + this.writeWith((t) => t.togglePauseOrStart()); + } + + undoSplit() { + this.writeWith((t) => t.undoSplit()); + } + + skipSplit() { + this.writeWith((t) => t.skipSplit()); + } + + initializeGameTime() { + this.writeWith((t) => t.initializeGameTime()); + } + + setGameTime(gameTime: string) { + using time = TimeSpan.parse(gameTime); + if (time !== null) { + this.writeWith((t) => t.setGameTime(time)); + } + } + + setLoadingTimes(loadingTimes: string) { + using time = TimeSpan.parse(loadingTimes); + if (time !== null) { + this.writeWith((t) => t.setLoadingTimes(time)); + } + } + + pauseGameTime() { + this.writeWith((t) => t.pauseGameTime()); + } + + resumeGameTime() { + this.writeWith((t) => t.resumeGameTime()); + } } diff --git a/src/ui/RunEditor.tsx b/src/ui/RunEditor.tsx index 80368b23..13ebdeae 100644 --- a/src/ui/RunEditor.tsx +++ b/src/ui/RunEditor.tsx @@ -855,14 +855,14 @@ export class RunEditor extends React.Component { this.props.editor.setEmulatorUsage(emulatorUsage); } else if (index < customVariablesOffset) { const stringValue = unwrapString(value); - const key = speedrunComVariables[index - speedrunComVariablesOffset].text; + const key = speedrunComVariables[index - speedrunComVariablesOffset].text as string; if (stringValue !== "") { this.props.editor.setSpeedrunComVariable(key, stringValue); } else { this.props.editor.removeSpeedrunComVariable(key); } } else { - const key = customVariables[index - customVariablesOffset].text; + const key = customVariables[index - customVariablesOffset].text as string; const stringValue = unwrapRemovableString(value); if (stringValue !== null) { this.props.editor.setCustomVariable(key, stringValue); diff --git a/src/ui/Settings.tsx b/src/ui/Settings.tsx index c32ab9b2..296aeb20 100644 --- a/src/ui/Settings.tsx +++ b/src/ui/Settings.tsx @@ -2,14 +2,15 @@ import * as React from "react"; import { Color, SettingsDescriptionValueJson } from "../livesplit-core"; import { assertNever, expect, Option } from "../util/OptionUtil"; import ColorPicker from "./ColorPicker"; - import HotkeyButton from "./HotkeyButton"; import ToggleCheckbox from "./ToggleCheckbox"; import { UrlCache } from "../util/UrlCache"; import { openFileAsArrayBuffer } from "../util/FileUtil"; import * as FontList from "../util/FontList"; +import { LiveSplitServer } from "../api/LiveSplitServer"; import "../css/Tooltip.scss"; +import "../css/LiveSplitServerButton.scss"; export interface Props { context: string, @@ -24,14 +25,20 @@ export interface ExtendedSettingsDescriptionJson { } export interface ExtendedSettingsDescriptionFieldJson { - text: string, - tooltip: string, + text: string | JSX.Element, + tooltip: string | JSX.Element, value: ExtendedSettingsDescriptionValueJson, } export type ExtendedSettingsDescriptionValueJson = SettingsDescriptionValueJson | - { RemovableString: string | null }; + { RemovableString: string | null } | + { + ServerConnection: { + url: string | undefined, + connection: Option, + } + }; export interface SettingValueFactory { fromBool(v: boolean): T; @@ -1348,6 +1355,27 @@ export class SettingsComponent extends React.Component> {
); } + } else if ("ServerConnection" in value) { + component =
+ +
; } else { assertNever(value); } @@ -1371,4 +1399,16 @@ export class SettingsComponent extends React.Component> { ); } + + private connectToServerOrDisconnect(valueIndex: number, serverUrl: string | undefined, connection: Option) { + if (connection) { + connection.close(); + return; + } + const url = prompt("Specify the WebSocket URL:", serverUrl); + if (!url) { + return; + } + this.props.setValue(valueIndex, this.props.factory.fromString(url)); + } } diff --git a/src/ui/SettingsEditor.tsx b/src/ui/SettingsEditor.tsx index b20b9dd1..42bb6781 100644 --- a/src/ui/SettingsEditor.tsx +++ b/src/ui/SettingsEditor.tsx @@ -5,6 +5,8 @@ import { SettingsDescriptionJson, SettingValue, HotkeyConfig } from "../livespli import { toast } from "react-toastify"; import { UrlCache } from "../util/UrlCache"; import { FRAME_RATE_AUTOMATIC as FRAME_RATE_BATTERY_AWARE, FRAME_RATE_MATCH_SCREEN as FRAME_RATE_MATCH_SCREEN, FrameRateSetting } from "../util/FrameRate"; +import { LiveSplitServer } from "../api/LiveSplitServer"; +import { Option } from "../util/OptionUtil"; import "../css/SettingsEditor.scss"; @@ -14,6 +16,7 @@ export interface GeneralSettings { showManualGameTime: boolean, speedrunComIntegration: boolean, splitsIoIntegration: boolean, + serverUrl: string | undefined, } export interface Props { @@ -21,6 +24,7 @@ export interface Props { hotkeyConfig: HotkeyConfig, urlCache: UrlCache, callbacks: Callbacks, + serverConnection: Option, } export interface State { @@ -31,6 +35,21 @@ export interface State { interface Callbacks { renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element, closeSettingsEditor(save: boolean, newGeneralSettings: GeneralSettings): void, + onServerConnectionOpened(serverConnection: LiveSplitServer): void, + onServerConnectionClosed(): void, + start(): void, + split(): void, + splitOrStart(): void, + reset(): void, + togglePauseOrStart(): void, + undoSplit(): void, + skipSplit(): void, + initializeGameTime(): void, + setGameTime(time: string): void, + setLoadingTimes(time: string): void, + pauseGameTime(): void, + resumeGameTime(): void, + forceUpdate(): void, } export class SettingsEditor extends React.Component { @@ -151,6 +170,21 @@ export class SettingsEditor extends React.Component { tooltip: "Allows you to upload splits to and download splits from splits.io.", value: { Bool: this.state.generalSettings.splitsIoIntegration }, }, + { + text: <> + Server Connection