-
Notifications
You must be signed in to change notification settings - Fork 129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding back button & reload support #495
base: localisation
Are you sure you want to change the base?
Changes from all commits
0764e23
8cc8fc7
41b74f9
4a65d6d
c64d816
f1d90cf
b932328
9a5a4c5
3c7b11c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,8 @@ | ||
<%_ for (lang of langs) { _%> | ||
/game/* /<%-lang %>/index.html 200 Language=<%-lang %> | ||
/about/ /<%-lang %>/index.html 200 Language=<%-lang %> | ||
/* /<%-lang %>/:splat 200 Language=<%-lang %> | ||
<% } _%> | ||
|
||
/game/* /index.html 200 | ||
/about/ /index.html 200 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,6 +77,15 @@ const stateServicePromise: Promise<StateService> = (async () => { | |
return remoteServices.stateService; | ||
})() as any; | ||
|
||
function gameTypeToURL( | ||
width: number, | ||
height: number, | ||
mines: number, | ||
usedKeyboard: boolean | ||
) { | ||
return `/game/${width}:${height}:${mines}:${usedKeyboard ? "k" : "m"}`; | ||
} | ||
|
||
const nebulaDangerDark: Color = [40, 0, 0]; | ||
const nebulaDangerLight: Color = [131, 23, 71]; | ||
// Looking for nebulaSafeDark? It's defined in lib/nebula-safe-dark.js | ||
|
@@ -157,16 +166,39 @@ export default class Root extends Component<Props, State> { | |
}); | ||
}); | ||
|
||
// Is this the reload after an update? | ||
// Is this the reload after an old update? | ||
// TODO: we should be able to remove this after a few months. | ||
const instantGameDataStr = prerender | ||
? false | ||
: sessionStorage.getItem(immedateGameSessionKey); | ||
|
||
if (instantGameDataStr) { | ||
sessionStorage.removeItem(immedateGameSessionKey); | ||
const { width, height, mines, usedKeyboard } = JSON.parse( | ||
instantGameDataStr | ||
) as { | ||
width: number; | ||
height: number; | ||
mines: number; | ||
usedKeyboard: boolean; | ||
}; | ||
|
||
history.pushState( | ||
{}, | ||
"", | ||
gameTypeToURL(width, height, mines, usedKeyboard) + location.search | ||
); | ||
} | ||
// /TODO | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can remove this once we're confident few people are upgrading from versions that used I'll create an issue for that. |
||
|
||
// We're going to try to jump straight into a game. | ||
if (location.pathname.startsWith("/game/")) { | ||
this.setState({ awaitingGame: true }); | ||
} | ||
|
||
if (!prerender) { | ||
this._handleCurrentURL(); | ||
} | ||
|
||
stateServicePromise.then(async stateService => { | ||
this._stateService = stateService; | ||
|
||
|
@@ -206,34 +238,19 @@ export default class Root extends Component<Props, State> { | |
} | ||
} | ||
}); | ||
|
||
if (instantGameDataStr) { | ||
await gamePerquisites; | ||
const { width, height, mines, usedKeyboard } = JSON.parse( | ||
instantGameDataStr | ||
) as { | ||
width: number; | ||
height: number; | ||
mines: number; | ||
usedKeyboard: boolean; | ||
}; | ||
|
||
if (!usedKeyboard) { | ||
// This is a horrible hack to tell focus-visible.js not to initially show focus styles. | ||
document.body.dispatchEvent( | ||
new MouseEvent("mousemove", { bubbles: true }) | ||
); | ||
} | ||
|
||
this._stateService.initGame(width, height, mines); | ||
} | ||
}); | ||
} | ||
|
||
componentDidMount() { | ||
if (prerender) { | ||
prerenderDone(); | ||
} | ||
|
||
addEventListener("popstate", this._onPopState); | ||
} | ||
|
||
componentWillUnmount() { | ||
removeEventListener("popstate", this._onPopState); | ||
} | ||
|
||
render( | ||
|
@@ -303,6 +320,7 @@ export default class Root extends Component<Props, State> { | |
useMotion={motionPreference} | ||
bestTime={bestTime} | ||
useVibration={vibrationPreference} | ||
onBack={this._onBackClick} | ||
/> | ||
)} | ||
/> | ||
|
@@ -404,19 +422,71 @@ export default class Root extends Component<Props, State> { | |
|
||
@bind | ||
private _onSettingsCloseClick() { | ||
this.setState({ settingsOpen: false }, () => { | ||
this.previousFocus!.focus(); | ||
}); | ||
this._pushPath("/"); | ||
} | ||
|
||
@bind | ||
private _onSettingsClick() { | ||
this.previousFocus = document.activeElement as HTMLElement; | ||
this.setState({ settingsOpen: true, allowIntroAnim: false }); | ||
this._pushPath("/about/"); | ||
this._handleCurrentURL(); | ||
} | ||
|
||
@bind | ||
private async _onStartGame(width: number, height: number, mines: number) { | ||
private _onPopState() { | ||
this._handleCurrentURL(); | ||
} | ||
|
||
private _pushPath(path: string) { | ||
history.pushState({}, "", path + location.search); | ||
this._handleCurrentURL(); | ||
} | ||
|
||
private async _resetToStartScreen() { | ||
history.replaceState({}, "", "/" + location.search); | ||
this.setState( | ||
{ | ||
awaitingGame: false, | ||
dangerMode: false, | ||
settingsOpen: false | ||
}, | ||
() => { | ||
if (this.previousFocus) { | ||
this.previousFocus.focus(); | ||
this.previousFocus = null; | ||
} | ||
} | ||
); | ||
const stateService = await stateServicePromise; | ||
stateService.reset(); | ||
} | ||
|
||
private async _handleCurrentURL() { | ||
if (location.pathname === "/") { | ||
this._resetToStartScreen(); | ||
return; | ||
} | ||
|
||
if (location.pathname === "/about/") { | ||
this.setState({ settingsOpen: true, allowIntroAnim: false }); | ||
return; | ||
} | ||
|
||
if (!location.pathname.startsWith("/game/")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like the 2 const [,component] = location.pathname.split("/");
switch(component) {
case "": // start screen
// ...
break;
case "about":
// ...
break;
case "game":
// ...
break;
default:
this._resetToStartScreen()
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I'll find a better pattern here |
||
this._resetToStartScreen(); | ||
return; | ||
} | ||
|
||
// The rest of this function handles /game/etc-etc | ||
const gameStr = location.pathname.slice("/game/".length); | ||
const gameStrParts = gameStr.split(":"); | ||
const [width, height, mines] = gameStrParts.slice(0, 3).map(n => Number(n)); | ||
|
||
if (!width || !height || !mines) { | ||
this._resetToStartScreen(); | ||
return; | ||
} | ||
|
||
this._awaitingGameTimeout = setTimeout(() => { | ||
this.setState({ awaitingGame: true }); | ||
}, loadingScreenTimeout); | ||
|
@@ -426,28 +496,35 @@ export default class Root extends Component<Props, State> { | |
if (updateReady) { | ||
// There's an update available. Let's load it as part of starting the game… | ||
await skipWaiting(); | ||
|
||
// Did the user click the start button using keyboard? | ||
const usedKeyboard = !!document.querySelector(".focus-visible"); | ||
|
||
sessionStorage.setItem( | ||
immedateGameSessionKey, | ||
JSON.stringify({ width, height, mines, usedKeyboard }) | ||
); | ||
|
||
location.reload(); | ||
return; | ||
} | ||
|
||
// Wait for everything to be ready: | ||
await gamePerquisites; | ||
|
||
const usedKeyboard = gameStrParts[3] === "k"; | ||
|
||
if (!usedKeyboard) { | ||
// This is a horrible hack to tell focus-visible.js not to initially show focus styles. | ||
document.body.dispatchEvent( | ||
new MouseEvent("mousemove", { bubbles: true }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lol works for me :D (But also cc @robdodson) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code exists already, I've just moved it. WICG/focus-visible#198 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just checking, is there anything I need to do on my end? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh no, sorry. I just wanted to make sure you are aware of this hack in case we were missing something. I didn’t know that Jake already filed an issue ages ago. |
||
); | ||
} | ||
|
||
const stateService = await stateServicePromise; | ||
stateService.initGame(width, height, mines); | ||
} | ||
|
||
@bind | ||
private async _onStartGame(width: number, height: number, mines: number) { | ||
// Did the user click the start button using keyboard? | ||
// This is important if the page is reloaded (eg, if updateReady) | ||
const usedKeyboard = !!document.querySelector(".focus-visible"); | ||
this._pushPath(gameTypeToURL(width, height, mines, usedKeyboard)); | ||
} | ||
|
||
@bind | ||
private _onBackClick() { | ||
this.setState({ dangerMode: false }); | ||
this._stateService!.reset(); | ||
this._resetToStartScreen(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,9 +51,26 @@ self.addEventListener("fetch", event => { | |
} | ||
event.respondWith( | ||
(async function() { | ||
const cachedResponse = await caches.match(event.request, { | ||
let cachedResponse: Response | undefined; | ||
|
||
// Handle the URLs that just go to the root page | ||
if (event.request.mode === "navigate") { | ||
const url = new URL(event.request.url); | ||
if ( | ||
url.pathname === "/" || | ||
url.pathname === "/about/" || | ||
url.pathname.startsWith("/game/") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I worry about having to keeping the pages the router knows about in sync with the pages the service worker knows about. We don't need to solve it in this PR, but maybe we should just switch to always delivering There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That won't work offline though (and has the lifi problem). I agree it's a problem though. I guess we should create a seperate file of rewrites that feeds into this and |
||
) { | ||
cachedResponse = await caches.match("/"); | ||
} | ||
} | ||
|
||
if (!cachedResponse) { | ||
cachedResponse = await caches.match(event.request, { | ||
ignoreSearch: true | ||
}); | ||
} | ||
|
||
return cachedResponse || fetch(event.request); | ||
})() | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to do this to prevent 404s, when it tried to request stuff like
/game/index-abcdef.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean 'game/index-abcdef.js'?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes