From df0dc8d72ffee47f7fad05bcab8eabd8414161b7 Mon Sep 17 00:00:00 2001 From: wooferzfg Date: Mon, 11 May 2020 21:52:54 -0500 Subject: [PATCH 1/5] Add a loading state to the Run Editor --- src/css/Loading.scss | 15 +++++++++++++++ src/css/RunEditor.scss | 14 ++++++++------ src/css/Splits.scss | 2 +- src/css/SplitsSelection.scss | 12 ++---------- src/css/Title.scss | 2 +- src/css/main.scss | 6 ++---- src/index.html | 6 ++++-- src/ui/RunEditor.tsx | 13 +++++++++++++ 8 files changed, 46 insertions(+), 24 deletions(-) create mode 100644 src/css/Loading.scss diff --git a/src/css/Loading.scss b/src/css/Loading.scss new file mode 100644 index 000000000..a7b72f2c1 --- /dev/null +++ b/src/css/Loading.scss @@ -0,0 +1,15 @@ +@import 'variables'; + +$loading-text-font-size: 45px; + +@mixin loading { + .loading { + display: flex; + width: fit-content; + font-size: $loading-text-font-size; + + .loading-text { + margin-left: $ui-margin; + } + } +} diff --git a/src/css/RunEditor.scss b/src/css/RunEditor.scss index 3067cc695..e42cd9274 100644 --- a/src/css/RunEditor.scss +++ b/src/css/RunEditor.scss @@ -1,9 +1,10 @@ -@import "ContextMenu"; -@import "Markdown"; -@import "mobile"; -@import "Table"; -@import "Toggle"; -@import "variables"; +@import 'mobile'; +@import 'variables'; +@import 'ContextMenu'; +@import 'Loading'; +@import 'Markdown'; +@import 'Table'; +@import 'Toggle'; $tab-bar-height: 30px + $ui-margin / 2; $tab-width: 625px; @@ -17,6 +18,7 @@ $small-button-padding: 1px 3px 1px 3px; .run-editor { @include context-menu; + @include loading; @include markdown; @include table; @include toggle; diff --git a/src/css/Splits.scss b/src/css/Splits.scss index 251fd77bb..facd0d5be 100644 --- a/src/css/Splits.scss +++ b/src/css/Splits.scss @@ -1,4 +1,4 @@ -@import "Time"; +@import 'Time'; @import 'variables'; $split-column-width: $default-line-height * 3; diff --git a/src/css/SplitsSelection.scss b/src/css/SplitsSelection.scss index d554c71c3..18daa34b2 100644 --- a/src/css/SplitsSelection.scss +++ b/src/css/SplitsSelection.scss @@ -1,22 +1,14 @@ @import 'mobile'; @import 'variables'; +@import 'Loading'; @import 'Table'; @import 'ContextMenu'; -$loading-text-font-size: 40px; $splits-row-width: 500px; $splits-row-height: 40px; .splits-selection { - .loading { - display: flex; - width: fit-content; - font-size: $loading-text-font-size; - - .loading-text { - margin-left: $ui-margin; - } - } + @include loading; .splits-selection-container { display: flex; diff --git a/src/css/Title.scss b/src/css/Title.scss index 40eefd252..ebade86e3 100644 --- a/src/css/Title.scss +++ b/src/css/Title.scss @@ -1,4 +1,4 @@ -@import "variables"; +@import 'variables'; $game-icon-size: $two-row-height - $vertical-padding * 2; diff --git a/src/css/main.scss b/src/css/main.scss index fab3ecaba..804c7f37d 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -1,5 +1,6 @@ @import 'browser_source'; @import 'variables'; +@import 'Loading'; @font-face { font-family: timer; @@ -130,9 +131,6 @@ button.disabled:active { display: flex; justify-content: center; align-items: center; - font-size: 50px; - .initial-load-text { - margin-left: 10px; - } + @include loading; } diff --git a/src/index.html b/src/index.html index 99f1247bf..d38e1ac0b 100644 --- a/src/index.html +++ b/src/index.html @@ -11,8 +11,10 @@
-
-
Loading...
+
+
+
Loading...
+
diff --git a/src/ui/RunEditor.tsx b/src/ui/RunEditor.tsx index a03e22048..e2a79f84a 100644 --- a/src/ui/RunEditor.tsx +++ b/src/ui/RunEditor.tsx @@ -28,6 +28,7 @@ export interface Props { } export interface State { editor: LiveSplit.RunEditorStateJson, + isLoading: boolean, offsetIsValid: boolean, attemptCountIsValid: boolean, rowState: RowState, @@ -81,6 +82,7 @@ export class RunEditor extends React.Component { this.state = { attemptCountIsValid: true, editor: state, + isLoading: false, offsetIsValid: true, rowState: { bestSegmentTime: "", @@ -117,6 +119,17 @@ export class RunEditor extends React.Component { } private renderView() { + if (this.state.isLoading) { + return ( +
+
+
+
Loading...
+
+
+ ); + } + const gameIcon = this.getGameIcon(); let gameIconContextTrigger: any = null; From 33d15a0639c738e81e4d2d154921f62181f986fb Mon Sep 17 00:00:00 2001 From: wooferzfg Date: Mon, 11 May 2020 22:08:06 -0500 Subject: [PATCH 2/5] Update how splits are loaded from the leaderboard --- src/ui/LiveSplit.tsx | 26 +++++++++++++++++++------- src/ui/RunEditor.tsx | 27 ++++++++++++++++++++++----- src/ui/SplitsSelection.tsx | 15 ++++++++++++--- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/ui/LiveSplit.tsx b/src/ui/LiveSplit.tsx index 1e9dbc881..514a67f23 100644 --- a/src/ui/LiveSplit.tsx +++ b/src/ui/LiveSplit.tsx @@ -34,7 +34,7 @@ export enum MenuKind { type Menu = { kind: MenuKind.Timer } | { kind: MenuKind.Splits } | - { kind: MenuKind.RunEditor, editor: RunEditor, splitsKey?: number } | + { kind: MenuKind.RunEditor, editor: RunEditor, splitsKey?: number, persistChanges: boolean } | { kind: MenuKind.Layout } | { kind: MenuKind.LayoutEditor, editor: LayoutEditor } | { kind: MenuKind.SettingsEditor, config: HotkeyConfig } | @@ -370,13 +370,25 @@ export class LiveSplit extends React.Component { this.setLayout(layout); } - public openRunEditor({ splitsKey, run }: EditingInfo) { + public openRunEditor({ splitsKey, persistChanges, run }: EditingInfo) { const editor = expect( RunEditor.new(run), "The Run Editor should always be able to be opened.", ); this.setState({ - menu: { kind: MenuKind.RunEditor, editor, splitsKey }, + menu: { kind: MenuKind.RunEditor, editor, splitsKey, persistChanges }, + sidebarOpen: false, + }); + } + + public replaceRunEditor(editor: RunEditor) { + this.setState({ + menu: { + kind: MenuKind.RunEditor, + editor, + splitsKey: undefined, + persistChanges: true, + }, sidebarOpen: false, }); } @@ -385,16 +397,16 @@ export class LiveSplit extends React.Component { if (this.state.menu.kind !== MenuKind.RunEditor) { panic("No Run Editor to close"); } - const { editor, splitsKey } = this.state.menu; + const { editor, persistChanges, splitsKey } = this.state.menu; const run = editor.close(); if (save) { - if (splitsKey == null) { + if (persistChanges) { + Storage.storeRunAndDispose(run, splitsKey); + } else { assertNull( this.writeWith((t) => t.setRun(run)), "The Run Editor should always return a valid Run.", ); - } else { - Storage.storeRunAndDispose(run, splitsKey); } } else { run.dispose(); diff --git a/src/ui/RunEditor.tsx b/src/ui/RunEditor.tsx index e2a79f84a..df763a472 100644 --- a/src/ui/RunEditor.tsx +++ b/src/ui/RunEditor.tsx @@ -37,6 +37,7 @@ export interface State { interface Callbacks { renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element, + replaceRunEditor(editor: LiveSplit.RunEditor): void, closeRunEditor(save: boolean): void, } @@ -1871,6 +1872,18 @@ export class RunEditor extends React.Component { } private async downloadSplits(apiRun: Run, apiUri: string) { + // FIXME: Determine whether there are any unsaved changes in the Run Editor + if (!confirm( + "Are you sure you want to load these splits? Any unsaved changes will be lost.", + )) { + return; + } + + this.setState({ + ...this.state, + isLoading: true, + }); + const baseUri = "https://splits.io/api/v3/runs/"; assert(apiUri.startsWith(baseUri), "Unexpected Splits.io URL"); const splitsId = apiUri.slice(baseUri.length); @@ -1881,11 +1894,12 @@ export class RunEditor extends React.Component { const platformListDownload = downloadPlatformList(); const regionListDownload = downloadRegionList(); const gameInfoDownload = downloadGameInfo(gameName); + await gameInfoDownload; await platformListDownload; await regionListDownload; const run = await runDownload; - // TODO Race Condition with the Run Editor closing (and probably others) + run.with((run) => { const newEditor = LiveSplit.RunEditor.new(run); if (newEditor !== null) { @@ -1896,10 +1910,7 @@ export class RunEditor extends React.Component { apiRun, ); - // TODO Oh no, not internal pointer stuff - this.props.editor.dispose(); - this.props.editor.ptr = newEditor.ptr; - + this.props.callbacks.replaceRunEditor(newEditor); this.update(); } else { toast.error("The downloaded splits are not suitable for being edited."); @@ -1908,6 +1919,12 @@ export class RunEditor extends React.Component { } catch (_) { toast.error("Failed to download the splits."); } + + this.setState({ + ...this.state, + isLoading: false, + }); + this.update(Tab.RealTime); } private toggleExpandLeaderboardRow(rowIndex: number) { diff --git a/src/ui/SplitsSelection.tsx b/src/ui/SplitsSelection.tsx index e4b6ffa9b..7ad46bbdd 100644 --- a/src/ui/SplitsSelection.tsx +++ b/src/ui/SplitsSelection.tsx @@ -15,7 +15,8 @@ import DragUpload from "./DragUpload"; import { ContextMenuTrigger, ContextMenu, MenuItem } from "react-contextmenu"; export interface EditingInfo { - splitsKey?: number, + splitsKey: number | undefined, + persistChanges: boolean, run: Run, } @@ -179,7 +180,11 @@ export class SplitsSelection extends React.Component { } }); if (run !== null) { - this.props.callbacks.openRunEditor({ run }); + this.props.callbacks.openRunEditor({ + run, + splitsKey: this.props.openedSplitsKey, + persistChanges: false, + }); } else { toast.error("You can't edit your run while the timer is running."); } @@ -265,7 +270,11 @@ export class SplitsSelection extends React.Component { private async editSplits(splitsKey: number) { const run = await this.getRunFromKey(splitsKey); - this.props.callbacks.openRunEditor({ splitsKey, run }); + this.props.callbacks.openRunEditor({ + splitsKey, + persistChanges: true, + run, + }); } private async copySplits(key: number) { From 76015e8020b6d304be265ba014c6d6ba2ad01b79 Mon Sep 17 00:00:00 2001 From: wooferzfg Date: Tue, 16 Apr 2024 20:52:55 -0400 Subject: [PATCH 3/5] Dispose the old run editor --- src/ui/LiveSplit.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ui/LiveSplit.tsx b/src/ui/LiveSplit.tsx index ca96b057d..4964d1591 100644 --- a/src/ui/LiveSplit.tsx +++ b/src/ui/LiveSplit.tsx @@ -401,6 +401,13 @@ export class LiveSplit extends React.Component { } public replaceRunEditor(editor: RunEditor) { + const { menu } = this.state; + + if (menu.kind === MenuKind.RunEditor) { + const { editor: oldEditor } = menu; + oldEditor.dispose(); + } + this.setState({ menu: { kind: MenuKind.RunEditor, From 98a3de53fdbc7e5b7dc21098eac43f5ddcee5a67 Mon Sep 17 00:00:00 2001 From: wooferzfg Date: Tue, 16 Apr 2024 21:20:29 -0400 Subject: [PATCH 4/5] Fix disposing logic --- src/ui/RunEditor.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ui/RunEditor.tsx b/src/ui/RunEditor.tsx index b26d09910..25d1d9bb8 100644 --- a/src/ui/RunEditor.tsx +++ b/src/ui/RunEditor.tsx @@ -1497,11 +1497,11 @@ export class RunEditor extends React.Component { * the leaderboards. We don't want to update the editor if it has been * disposed in the meantime. */ - private maybeUpdate() { + private maybeUpdate(switchTab?: Tab) { if (this.props.editor.ptr === 0) { return; } - this.update(); + this.update(switchTab); } private update(switchTab?: Tab) { @@ -1905,6 +1905,11 @@ export class RunEditor extends React.Component { await platformListDownload; await regionListDownload; const run = await runDownload; + + if (this.props.editor.ptr === 0) { + // Old editor is already disposed, so do not continue + return; + } run.with((run) => { const newEditor = LiveSplit.RunEditor.new(run); if (newEditor !== null) { @@ -1916,7 +1921,6 @@ export class RunEditor extends React.Component { ); this.props.callbacks.replaceRunEditor(newEditor); - this.update(); } else { toast.error("The downloaded splits are not suitable for being edited."); } @@ -1929,7 +1933,7 @@ export class RunEditor extends React.Component { ...this.state, isLoading: false, }); - this.update(Tab.RealTime); + this.maybeUpdate(Tab.RealTime); } private toggleExpandLeaderboardRow(rowIndex: number) { From e691ba838225cb390d80420a97ff012c809427b1 Mon Sep 17 00:00:00 2001 From: wooferzfg Date: Sun, 26 May 2024 14:06:58 -0500 Subject: [PATCH 5/5] Fix dispose call --- src/ui/LiveSplit.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/LiveSplit.tsx b/src/ui/LiveSplit.tsx index 25b26353d..dfd5f9be5 100644 --- a/src/ui/LiveSplit.tsx +++ b/src/ui/LiveSplit.tsx @@ -457,7 +457,7 @@ export class LiveSplit extends React.Component { if (menu.kind === MenuKind.RunEditor) { const { editor: oldEditor } = menu; - oldEditor.dispose(); + oldEditor[Symbol.dispose](); } this.setState({