mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Add settings
This commit is contained in:
@@ -31,6 +31,7 @@ import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
|
||||
import { queryParamOptions } from "./core/query_parameters";
|
||||
import { NoGameAnalytics } from "./platform/browser/no_game_analytics";
|
||||
import { StorageImplBrowserIndexedDB } from "./platform/browser/storage_indexed_db";
|
||||
import { SettingsState } from "./states/settings";
|
||||
|
||||
const logger = createLogger("application");
|
||||
|
||||
@@ -142,7 +143,7 @@ export class Application {
|
||||
*/
|
||||
registerStates() {
|
||||
/** @type {Array<typeof GameState>} */
|
||||
const states = [PreloadState, MainMenuState, InGameState];
|
||||
const states = [PreloadState, MainMenuState, InGameState, SettingsState];
|
||||
|
||||
for (let i = 0; i < states.length; ++i) {
|
||||
this.stateMgr.register(states[i]);
|
||||
|
||||
@@ -90,7 +90,7 @@ export const globalConfig = {
|
||||
allBuildingsUnlocked: true,
|
||||
upgradesNoCost: true,
|
||||
disableUnlockDialog: false,
|
||||
testTranslations: true,
|
||||
// testTranslations: true,
|
||||
/* dev:end */
|
||||
},
|
||||
|
||||
|
||||
153
src/js/core/textual_game_state.js
Normal file
153
src/js/core/textual_game_state.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
||||
import { GameState } from "./game_state";
|
||||
import { T } from "../translations";
|
||||
|
||||
/**
|
||||
* Baseclass for all game states which are structured similary: A header with back button + some
|
||||
* scrollable content.
|
||||
*/
|
||||
export class TextualGameState extends GameState {
|
||||
///// INTERFACE ////
|
||||
|
||||
/**
|
||||
* Should return the states inner html. If not overriden, will create a scrollable container
|
||||
* with the content of getMainContentHTML()
|
||||
* @returns {string}
|
||||
*/
|
||||
getInnerHTML() {
|
||||
return `
|
||||
<div class="content mainContent">
|
||||
${this.getMainContentHTML()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return the states HTML content.
|
||||
*/
|
||||
getMainContentHTML() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return the title of the game state. If null, no title and back button will
|
||||
* get created
|
||||
* @returns {string|null}
|
||||
*/
|
||||
getStateHeaderTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/////////////
|
||||
|
||||
/**
|
||||
* Back button handler, can be overridden. Per default it goes back to the main menu,
|
||||
* or if coming from the game it moves back to the game again.
|
||||
*/
|
||||
onBackButton() {
|
||||
if (this.backToStateId) {
|
||||
this.moveToState(this.backToStateId, this.backToStatePayload);
|
||||
} else {
|
||||
this.moveToState(this.getDefaultPreviousState());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default state to go back to
|
||||
*/
|
||||
getDefaultPreviousState() {
|
||||
return "MainMenuState";
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes to a new state, telling him to go back to this state later
|
||||
* @param {string} stateId
|
||||
*/
|
||||
moveToStateAddGoBack(stateId) {
|
||||
this.moveToState(stateId, {
|
||||
backToStateId: this.key,
|
||||
backToStatePayload: {
|
||||
backToStateId: this.backToStateId,
|
||||
backToStatePayload: this.backToStatePayload,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all click detectors, except the one on the back button. Useful when regenerating
|
||||
* content.
|
||||
*/
|
||||
clearClickDetectorsExceptHeader() {
|
||||
for (let i = 0; i < this.clickDetectors.length; ++i) {
|
||||
const detector = this.clickDetectors[i];
|
||||
if (detector.element === this.headerElement) {
|
||||
continue;
|
||||
}
|
||||
detector.cleanup();
|
||||
this.clickDetectors.splice(i, 1);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the GameState implementation to provide our own html
|
||||
*/
|
||||
internalGetFullHtml() {
|
||||
let headerHtml = "";
|
||||
if (this.getStateHeaderTitle()) {
|
||||
headerHtml = `
|
||||
<div class="headerBar">
|
||||
<button class="backButton"></button>
|
||||
<h1>${this.getStateHeaderTitle()}</h1>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return `
|
||||
${headerHtml}
|
||||
<div class="container">
|
||||
${this.getInnerHTML()}
|
||||
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
//// INTERNALS /////
|
||||
|
||||
/**
|
||||
* Overrides the GameState leave callback to cleanup stuff
|
||||
*/
|
||||
internalLeaveCallback() {
|
||||
super.internalLeaveCallback();
|
||||
this.dialogs.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the GameState enter callback to setup required stuff
|
||||
* @param {any} payload
|
||||
*/
|
||||
internalEnterCallback(payload) {
|
||||
super.internalEnterCallback(payload, false);
|
||||
if (payload.backToStateId) {
|
||||
this.backToStateId = payload.backToStateId;
|
||||
this.backToStatePayload = payload.backToStatePayload;
|
||||
}
|
||||
|
||||
this.htmlElement.classList.add("textualState");
|
||||
if (this.getStateHeaderTitle()) {
|
||||
this.htmlElement.classList.add("hasTitle");
|
||||
}
|
||||
|
||||
this.containerElement = this.htmlElement.querySelector(".widthKeeper .container");
|
||||
this.headerElement = this.htmlElement.querySelector(".headerBar > .backButton");
|
||||
|
||||
if (this.headerElement) {
|
||||
this.trackClicks(this.headerElement, this.onBackButton);
|
||||
}
|
||||
|
||||
this.dialogs = new HUDModalDialogs(null, this.app);
|
||||
const dialogsElement = document.body.querySelector(".modalDialogParent");
|
||||
this.dialogs.initializeToElement(dialogsElement);
|
||||
|
||||
this.onEnter(payload);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,10 @@ export class HUDSettingsMenu extends BaseHUDPart {
|
||||
title: T.ingame.settingsMenu.buttons.continue,
|
||||
action: () => this.close(),
|
||||
},
|
||||
{
|
||||
title: T.ingame.settingsMenu.buttons.settings,
|
||||
action: () => this.goToSettings(),
|
||||
},
|
||||
{
|
||||
title: T.ingame.settingsMenu.buttons.menu,
|
||||
action: () => this.returnToMenu(),
|
||||
@@ -47,6 +51,10 @@ export class HUDSettingsMenu extends BaseHUDPart {
|
||||
this.root.gameState.goBackToMenu();
|
||||
}
|
||||
|
||||
goToSettings() {
|
||||
this.root.gameState.goToSettings();
|
||||
}
|
||||
|
||||
shouldPauseGame() {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Application } from "../application";
|
||||
/* typehints:end */
|
||||
|
||||
import { createLogger } from "../core/logging";
|
||||
import { T } from "../translations";
|
||||
|
||||
const logger = createLogger("setting_types");
|
||||
|
||||
@@ -63,8 +64,8 @@ export class BaseSetting {
|
||||
|
||||
showRestartRequiredDialog() {
|
||||
const { restart } = this.dialogs.showInfo(
|
||||
"Restart required",
|
||||
"You need to restart the game to apply the settings.",
|
||||
T.dialogs.restartRequired.title,
|
||||
T.dialogs.restartRequired.text,
|
||||
this.app.platformWrapper.getSupportsRestart() ? ["later:grey", "restart:misc"] : ["ok:good"]
|
||||
);
|
||||
if (restart) {
|
||||
@@ -113,11 +114,11 @@ export class EnumSetting extends BaseSetting {
|
||||
return `
|
||||
<div class="setting cardbox ${this.enabled ? "enabled" : "disabled"}">
|
||||
<div class="row">
|
||||
<label>TODO: SETTING TITLE</label>
|
||||
<label>${T.settings.labels[this.id].title}</label>
|
||||
<div class="value enum" data-setting="${this.id}"></div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
TODO: SETTING DESC
|
||||
${T.settings.labels[this.id].description}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -153,7 +154,7 @@ export class EnumSetting extends BaseSetting {
|
||||
}
|
||||
|
||||
modify() {
|
||||
const { optionSelected } = this.dialogs.showOptionChooser("TODO: SETTING TITLE", {
|
||||
const { optionSelected } = this.dialogs.showOptionChooser(T.settings.labels[this.id].title, {
|
||||
active: this.app.settings.getSetting(this.id),
|
||||
options: this.options.map(option => ({
|
||||
value: this.valueGetter(option),
|
||||
@@ -186,13 +187,13 @@ export class BoolSetting extends BaseSetting {
|
||||
return `
|
||||
<div class="setting cardbox ${this.enabled ? "enabled" : "disabled"}">
|
||||
<div class="row">
|
||||
<label>TODO: SETTING TITLE</label>
|
||||
<label>${T.settings.labels[this.id].title}</label>
|
||||
<div class="value checkbox checked" data-setting="${this.id}">
|
||||
<span class="knob"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
TODO: SETTING DESC
|
||||
${T.settings.labels[this.id].description}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -144,6 +144,13 @@ export class InGameState extends GameState {
|
||||
this.saveThenGoToState("MainMenuState");
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes back to the settings state
|
||||
*/
|
||||
goToSettings() {
|
||||
this.saveThenGoToState("SettingsState");
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves to a state outside of the game
|
||||
* @param {string} stateId
|
||||
|
||||
@@ -285,6 +285,12 @@ export class MainMenuState extends GameState {
|
||||
const savegame = this.app.savegameMgr.createNewSavegame();
|
||||
|
||||
this.app.analytics.trackUiClick("startgame");
|
||||
|
||||
if (G_IS_DEV) {
|
||||
// TODO
|
||||
this.moveToState("SettingsState");
|
||||
}
|
||||
|
||||
this.moveToState("InGameState", {
|
||||
savegame,
|
||||
});
|
||||
|
||||
127
src/js/states/settings.js
Normal file
127
src/js/states/settings.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import { TextualGameState } from "../core/textual_game_state";
|
||||
import { formatSecondsToTimeAgo } from "../core/utils";
|
||||
import { allApplicationSettings } from "../profile/application_settings";
|
||||
import { T } from "../translations";
|
||||
|
||||
export class SettingsState extends TextualGameState {
|
||||
constructor() {
|
||||
super("SettingsState");
|
||||
}
|
||||
|
||||
getStateHeaderTitle() {
|
||||
return T.settings.title;
|
||||
}
|
||||
|
||||
getMainContentHTML() {
|
||||
return `
|
||||
|
||||
<div class="upperLinks">
|
||||
${
|
||||
this.app.platformWrapper.getSupportsKeyboard()
|
||||
? `
|
||||
<button class="styledButton editKeybindings">KEYBINDING TODO</button>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
<button class="styledButton changelog">CHANGELOG TODO</button>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
${this.getSettingsHtml()}
|
||||
<div class="versionbar">
|
||||
<div class="buildVersion">${T.global.loading} ...</div>
|
||||
<button class="styledButton copyright">COPYRIGHT TODO</button>
|
||||
</div>
|
||||
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
getSettingsHtml() {
|
||||
let lastCategory = null;
|
||||
let html = "";
|
||||
for (let i = 0; i < allApplicationSettings.length; ++i) {
|
||||
const setting = allApplicationSettings[i];
|
||||
|
||||
if (setting.categoryId !== lastCategory) {
|
||||
lastCategory = setting.categoryId;
|
||||
if (i !== 0) {
|
||||
html += "</div>";
|
||||
}
|
||||
html += `<strong class="categoryLabel">${T.settings.categories[lastCategory]}</strong>`;
|
||||
html += "<div class='settingsContainer'>";
|
||||
}
|
||||
|
||||
html += setting.getHtml();
|
||||
}
|
||||
if (lastCategory) {
|
||||
html += "</div>";
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
renderBuildText() {
|
||||
const labelVersion = this.htmlElement.querySelector(".buildVersion");
|
||||
const lastBuildMs = new Date().getTime() - G_BUILD_TIME;
|
||||
const lastBuildText = formatSecondsToTimeAgo(lastBuildMs / 1000.0);
|
||||
|
||||
const version = T.settings.versionBadges[G_APP_ENVIRONMENT];
|
||||
|
||||
labelVersion.innerHTML = `
|
||||
<span class='version'>
|
||||
${G_BUILD_VERSION} @ ${version} @ ${G_BUILD_COMMIT_HASH}
|
||||
</span>
|
||||
<span class='buildTime'>
|
||||
${T.settings.buildDate.replace("<at-date>", lastBuildText)}<br />
|
||||
</span>`;
|
||||
}
|
||||
|
||||
onEnter(payload) {
|
||||
this.renderBuildText();
|
||||
this.trackClicks(this.htmlElement.querySelector(".copyright"), this.onCopyrightClicked, {
|
||||
preventDefault: false,
|
||||
});
|
||||
this.trackClicks(this.htmlElement.querySelector(".changelog"), this.onChangelogClicked, {
|
||||
preventDefault: false,
|
||||
});
|
||||
|
||||
const keybindingsButton = this.htmlElement.querySelector(".editKeybindings");
|
||||
|
||||
if (keybindingsButton) {
|
||||
this.trackClicks(keybindingsButton, this.onKeybindingsClicked, { preventDefault: false });
|
||||
}
|
||||
|
||||
this.initSettings();
|
||||
}
|
||||
|
||||
initSettings() {
|
||||
allApplicationSettings.forEach(setting => {
|
||||
const element = this.htmlElement.querySelector("[data-setting='" + setting.id + "']");
|
||||
setting.bind(this.app, element, this.dialogs);
|
||||
setting.syncValueToElement();
|
||||
this.trackClicks(
|
||||
element,
|
||||
() => {
|
||||
setting.modify();
|
||||
},
|
||||
{ preventDefault: false }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
onCopyrightClicked() {
|
||||
// this.moveToStateAddGoBack("CopyrightState");
|
||||
}
|
||||
|
||||
onChangelogClicked() {
|
||||
// this.moveToStateAddGoBack("ChangelogState");
|
||||
}
|
||||
|
||||
onKeybindingsClicked() {
|
||||
// this.moveToStateAddGoBack("KeybindingsState");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user