diff --git a/src/css/Loading.scss b/src/css/Loading.scss
new file mode 100644
index 00000000..a7b72f2c
--- /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 7a9d4ec1..35be7f5f 100644
--- a/src/css/RunEditor.scss
+++ b/src/css/RunEditor.scss
@@ -1,6 +1,7 @@
@use 'sass:math';
@import "ContextMenu";
+@import "Loading";
@import "Markdown";
@import "mobile";
@import "Table";
@@ -19,6 +20,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/SplitsSelection.scss b/src/css/SplitsSelection.scss
index b69649fa..4b313c2c 100644
--- a/src/css/SplitsSelection.scss
+++ b/src/css/SplitsSelection.scss
@@ -2,23 +2,15 @@
@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/main.scss b/src/css/main.scss
index e1c00716..93e446b6 100644
--- a/src/css/main.scss
+++ b/src/css/main.scss
@@ -2,6 +2,7 @@
@import 'browser_source';
@import 'variables';
+@import 'Loading';
@font-face {
font-family: timer;
@@ -147,9 +148,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 99f1247b..d38e1ac0 100644
--- a/src/index.html
+++ b/src/index.html
@@ -11,8 +11,10 @@
diff --git a/src/ui/LiveSplit.tsx b/src/ui/LiveSplit.tsx
index 8eec519e..ae9dc4de 100644
--- a/src/ui/LiveSplit.tsx
+++ b/src/ui/LiveSplit.tsx
@@ -55,7 +55,7 @@ function isMenuLocked(menuKind: 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 } |
@@ -576,28 +576,49 @@ 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.changeMenu({ kind: MenuKind.RunEditor, editor, splitsKey });
+ this.changeMenu(
+ { kind: MenuKind.RunEditor, editor, splitsKey, persistChanges },
+ );
+ }
+
+ public replaceRunEditor(editor: RunEditor) {
+ const { menu } = this.state;
+
+ if (menu.kind === MenuKind.RunEditor) {
+ const { editor: oldEditor } = menu;
+ oldEditor[Symbol.dispose]();
+ }
+
+ this.setState({
+ menu: {
+ kind: MenuKind.RunEditor,
+ editor,
+ splitsKey: undefined,
+ persistChanges: true,
+ },
+ sidebarOpen: false,
+ });
}
public closeRunEditor(save: boolean) {
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.state.eventSink.setRun(run),
"The Run Editor should always return a valid Run.",
);
- } else {
- Storage.storeRunAndDispose(run, splitsKey);
}
} else {
run[Symbol.dispose]();
diff --git a/src/ui/RunEditor.tsx b/src/ui/RunEditor.tsx
index dfade43e..ddb85a1e 100644
--- a/src/ui/RunEditor.tsx
+++ b/src/ui/RunEditor.tsx
@@ -37,6 +37,7 @@ export interface Props {
}
export interface State {
editor: LiveSplit.RunEditorStateJson,
+ isLoading: boolean,
foundGames: string[],
offsetIsValid: boolean,
attemptCountIsValid: boolean,
@@ -47,6 +48,7 @@ export interface State {
interface Callbacks {
renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element,
+ replaceRunEditor(editor: LiveSplit.RunEditor): void,
closeRunEditor(save: boolean): void,
}
@@ -93,6 +95,7 @@ export class RunEditor extends React.Component {
this.state = {
attemptCountIsValid: true,
editor: state,
+ isLoading: false,
foundGames,
offsetIsValid: true,
rowState: {
@@ -127,6 +130,17 @@ export class RunEditor extends React.Component {
}
private renderView() {
+ if (this.state.isLoading) {
+ return (
+
+ );
+ }
+
const gameIcon = this.getGameIcon();
let gameIconContextTrigger: any = null;
@@ -2118,6 +2132,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);
@@ -2129,10 +2155,16 @@ export class RunEditor extends React.Component {
const platformListDownload = downloadPlatformList();
const regionListDownload = downloadRegionList();
const gameInfoDownload = downloadGameInfo(gameName);
+
await gameInfoDownload;
await platformListDownload;
await regionListDownload;
using run = await runDownload;
+
+ if (this.props.editor.ptr === 0) {
+ // Old editor is already disposed, so do not continue
+ return;
+ }
const newEditor = LiveSplit.RunEditor.new(run);
if (newEditor !== null) {
associateRun(
@@ -2142,11 +2174,7 @@ export class RunEditor extends React.Component {
apiRun,
);
- // TODO Oh no, not internal pointer stuff
- this.props.editor[Symbol.dispose]();
- this.props.editor.ptr = newEditor.ptr;
-
- this.update();
+ this.props.callbacks.replaceRunEditor(newEditor);
} else {
toast.error("The downloaded splits are not suitable for being edited.");
}
@@ -2156,6 +2184,12 @@ export class RunEditor extends React.Component {
}
toast.error("Failed to download the splits.");
}
+
+ this.setState({
+ ...this.state,
+ isLoading: false,
+ });
+ this.maybeUpdate({ switchTab: Tab.RealTime });
}
private toggleExpandLeaderboardRow(rowIndex: number) {
diff --git a/src/ui/SplitsSelection.tsx b/src/ui/SplitsSelection.tsx
index 5b1b6f76..7f625e54 100644
--- a/src/ui/SplitsSelection.tsx
+++ b/src/ui/SplitsSelection.tsx
@@ -17,7 +17,8 @@ import { showDialog } from "./Dialog";
import "../css/SplitsSelection.scss";
export interface EditingInfo {
- splitsKey?: number,
+ splitsKey: number | undefined,
+ persistChanges: boolean,
run: Run,
}
@@ -191,7 +192,11 @@ export class SplitsSelection extends React.Component {
return;
}
const run = this.props.eventSink.getRun().clone();
- this.props.callbacks.openRunEditor({ run });
+ this.props.callbacks.openRunEditor({
+ run,
+ splitsKey: this.props.openedSplitsKey,
+ persistChanges: false,
+ });
}}>
Edit
@@ -286,7 +291,11 @@ export class SplitsSelection extends React.Component {
private async editSplits(splitsKey: number) {
const run = await this.getRunFromKey(splitsKey);
if (run !== undefined) {
- this.props.callbacks.openRunEditor({ splitsKey, run });
+ this.props.callbacks.openRunEditor({
+ splitsKey,
+ persistChanges: true,
+ run,
+ });
}
}