Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 30 additions & 14 deletions src/Options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
import Settings from './permalink/Settings';
import Acknowledgement from './Acknowledgment';
import { RawOptions } from './permalink/SettingsTypes';
import { withCancel } from './utils/Cancel';

interface State {
settings: Settings;
Expand All @@ -25,6 +26,10 @@ export default class Options extends React.Component<Record<string, never>, Stat
changeTriforceRequired: () => void;
changeSkywardStrike: () => void;

// If the user switches branches quickly, or the GitHub API quickly responds with a release,
// we must cancel the previous load, otherwise we run into problems.
cancelSettingsLoad?: () => void;

constructor(props: Record<string, never>) {
super(props);
this.state = {
Expand All @@ -33,12 +38,7 @@ export default class Options extends React.Component<Record<string, never>, Stat
latestVersion: '',
source: 'main',
};
const versionData = this.getVersionData();
versionData.then((value) => {
// pull the name of the latest version
const latestVersion = value[0].tag_name;
this.setState({ source: latestVersion, latestVersion })
});

/*
_.forEach(regions, (region) => {
this[_.camelCase(`changeRegion${region.internal}`)] = this.changeBannedLocation.bind(this, region.internal);
Expand Down Expand Up @@ -66,10 +66,16 @@ export default class Options extends React.Component<Record<string, never>, Stat
this.changeSkywardStrike = this.changeBinaryOption.bind(this, 'Upgraded Skyward Strike');
this.permalinkChanged = this.permalinkChanged.bind(this);
this.updateSource = this.updateSource.bind(this);
}

componentDidMount() {
this.updateSettingsFromSource(this.state.source);

this.state.settings.init(this.state.source).then(() => {
this.state.settings.loadDefaults();
this.setState({ ready: true });
this.getVersionData().then((value) => {
// pull the name of the latest version
const latestVersion = value[0].tag_name;
this.setState({ latestVersion });
this.updateSettingsFromSource(latestVersion);
});
}

Expand Down Expand Up @@ -140,11 +146,21 @@ export default class Options extends React.Component<Record<string, never>, Stat

updateSource(e: ChangeEvent<HTMLInputElement>) {
const { value } = e.target;
this.state.settings.init(value).then(() => {
this.state.settings.loadDefaults();
this.setState({ source: value });
this.forceUpdate();
});
this.updateSettingsFromSource(value);
}

updateSettingsFromSource(source: string) {
this.cancelSettingsLoad?.();
const [cancelToken, cancel] = withCancel();
this.cancelSettingsLoad = cancel;

const settings = new Settings();
settings.init(source).then(() => {
if (!cancelToken.canceled) {
settings.loadDefaults();
this.setState({ settings, source, ready: true });
}
}).finally(() => this.forceUpdate());
}

render() {
Expand Down
40 changes: 40 additions & 0 deletions src/utils/Cancel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* A CancelToken should be passed to cancelable functions. Those functions should then check the state of the
* token and return early, or use checkCanceled to throw a CanceledError if the token has been canceled. Callers
* of cancelable functions should catch CanceledError.
*/
export interface CancelToken {
readonly canceled: boolean;
checkCanceled: () => void;
}

/**
* Indicates that the function was canceled by a call to the cancellation token's cancel function.
*/
export class CanceledError extends Error {
constructor() {
super('canceled');
this.name = 'CanceledError';
}
}

/**
* Returns a cancel token and a cancellation function. The token can be passed to functions and checked
* to see whether it has been canceled. The function can be called to cancel the token.
*/
export function withCancel(): [CancelToken, () => void] {
let isCanceled = false;
return [
{
get canceled() {
return isCanceled;
},
checkCanceled() {
if (isCanceled) {
throw new CanceledError();
}
},
},
() => (isCanceled = true),
];
}