mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Initial commit
This commit is contained in:
191
src/js/profile/application_settings.js
Normal file
191
src/js/profile/application_settings.js
Normal file
@@ -0,0 +1,191 @@
|
||||
/* typehints:start */
|
||||
import { Application } from "../application";
|
||||
/* typehints:end */
|
||||
|
||||
import { ReadWriteProxy } from "../core/read_write_proxy";
|
||||
import { BoolSetting, EnumSetting, BaseSetting } from "./setting_types";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { ExplainedResult } from "../core/explained_result";
|
||||
|
||||
const logger = createLogger("application_settings");
|
||||
|
||||
const categoryGame = "game";
|
||||
const categoryApp = "app";
|
||||
|
||||
export const uiScales = [
|
||||
{
|
||||
id: "super_small",
|
||||
size: 0.6,
|
||||
label: "Super small",
|
||||
},
|
||||
{
|
||||
id: "small",
|
||||
size: 0.8,
|
||||
label: "Small",
|
||||
},
|
||||
{
|
||||
id: "regular",
|
||||
size: 1,
|
||||
label: "Regular",
|
||||
},
|
||||
{
|
||||
id: "large",
|
||||
size: 1.2,
|
||||
label: "Large",
|
||||
},
|
||||
{
|
||||
id: "huge",
|
||||
size: 1.4,
|
||||
label: "Huge",
|
||||
},
|
||||
];
|
||||
|
||||
/** @type {Array<BaseSetting>} */
|
||||
export const allApplicationSettings = [
|
||||
new EnumSetting("uiScale", {
|
||||
options: uiScales.sort((a, b) => a.size - b.size),
|
||||
valueGetter: scale => scale.id,
|
||||
textGetter: scale => scale.label,
|
||||
category: categoryApp,
|
||||
restartRequired: false,
|
||||
changeCb:
|
||||
/**
|
||||
* @param {Application} app
|
||||
*/
|
||||
(app, id) => app.updateAfterUiScaleChanged(),
|
||||
}),
|
||||
new BoolSetting(
|
||||
"fullscreen",
|
||||
categoryApp,
|
||||
/**
|
||||
* @param {Application} app
|
||||
*/
|
||||
(app, value) => {
|
||||
if (app.platformWrapper.getSupportsFullscreen()) {
|
||||
app.platformWrapper.setFullscreen(value);
|
||||
}
|
||||
},
|
||||
G_IS_STANDALONE
|
||||
),
|
||||
|
||||
// GAME
|
||||
];
|
||||
|
||||
export function getApplicationSettingById(id) {
|
||||
return allApplicationSettings.find(setting => setting.id === id);
|
||||
}
|
||||
|
||||
class SettingsStorage {
|
||||
constructor() {
|
||||
this.uiScale = "regular";
|
||||
this.fullscreen = G_IS_STANDALONE;
|
||||
}
|
||||
}
|
||||
|
||||
export class ApplicationSettings extends ReadWriteProxy {
|
||||
constructor(app) {
|
||||
super(app, "app_settings.bin");
|
||||
}
|
||||
|
||||
initialize() {
|
||||
// Read and directly write latest data back
|
||||
return this.readAsync().then(() => this.writeAsync());
|
||||
}
|
||||
|
||||
save() {
|
||||
return this.writeAsync();
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
/**
|
||||
* @returns {SettingsStorage}
|
||||
*/
|
||||
getAllSettings() {
|
||||
return this.getCurrentData().settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
getSetting(key) {
|
||||
assert(this.getAllSettings().hasOwnProperty(key), "Setting not known: " + key);
|
||||
return this.getAllSettings()[key];
|
||||
}
|
||||
|
||||
getInterfaceScaleId() {
|
||||
if (!this.currentData) {
|
||||
// Not initialized yet
|
||||
return "regular";
|
||||
}
|
||||
return this.getAllSettings().uiScale;
|
||||
}
|
||||
|
||||
getInterfaceScaleValue() {
|
||||
const id = this.getInterfaceScaleId();
|
||||
for (let i = 0; i < uiScales.length; ++i) {
|
||||
if (uiScales[i].id === id) {
|
||||
return uiScales[i].size;
|
||||
}
|
||||
}
|
||||
logger.error("Unknown ui scale id:", id);
|
||||
return 1;
|
||||
}
|
||||
|
||||
getIsFullScreen() {
|
||||
return this.getAllSettings().fullscreen;
|
||||
}
|
||||
|
||||
// Setters
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {string|boolean} value
|
||||
*/
|
||||
updateSetting(key, value) {
|
||||
assert(this.getAllSettings().hasOwnProperty(key), "Setting not known: " + key);
|
||||
this.getAllSettings()[key] = value;
|
||||
return this.writeAsync();
|
||||
}
|
||||
|
||||
// RW Proxy impl
|
||||
verify(data) {
|
||||
if (!data.settings) {
|
||||
return ExplainedResult.bad("missing key 'settings'");
|
||||
}
|
||||
if (typeof data.settings !== "object") {
|
||||
return ExplainedResult.bad("Bad settings object");
|
||||
}
|
||||
|
||||
const settings = data.settings;
|
||||
for (let i = 0; i < allApplicationSettings.length; ++i) {
|
||||
const setting = allApplicationSettings[i];
|
||||
const storedValue = settings[setting.id];
|
||||
if (!setting.validate(storedValue)) {
|
||||
return ExplainedResult.bad("Bad setting value for " + setting.id + ": " + storedValue);
|
||||
}
|
||||
}
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
getDefaultData() {
|
||||
return {
|
||||
version: this.getCurrentVersion(),
|
||||
settings: new SettingsStorage(),
|
||||
};
|
||||
}
|
||||
|
||||
getCurrentVersion() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
migrate(data) {
|
||||
// Simply reset
|
||||
if (data.version < 1) {
|
||||
data.settings = new SettingsStorage();
|
||||
data.version = 1;
|
||||
}
|
||||
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
}
|
||||
208
src/js/profile/setting_types.js
Normal file
208
src/js/profile/setting_types.js
Normal file
@@ -0,0 +1,208 @@
|
||||
/* typehints:start */
|
||||
import { Application } from "../application";
|
||||
/* typehints:end */
|
||||
|
||||
import { createLogger } from "../core/logging";
|
||||
|
||||
const logger = createLogger("setting_types");
|
||||
|
||||
export class BaseSetting {
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {string} categoryId
|
||||
* @param {function(Application, any):void} changeCb
|
||||
* @param {boolean} enabled
|
||||
*/
|
||||
constructor(id, categoryId, changeCb, enabled) {
|
||||
this.id = id;
|
||||
this.categoryId = categoryId;
|
||||
this.changeCb = changeCb;
|
||||
this.enabled = enabled;
|
||||
|
||||
/** @type {Application} */
|
||||
this.app = null;
|
||||
|
||||
this.element = null;
|
||||
this.dialogs = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Application} app
|
||||
* @param {Element} element
|
||||
* @param {HUDModalDialogs} dialogs
|
||||
*/
|
||||
bind(app, element, dialogs) {
|
||||
this.app = app;
|
||||
this.element = element;
|
||||
this.dialogs = dialogs;
|
||||
}
|
||||
|
||||
getHtml() {
|
||||
abstract;
|
||||
return "";
|
||||
}
|
||||
|
||||
syncValueToElement() {
|
||||
abstract;
|
||||
}
|
||||
|
||||
modify() {
|
||||
abstract;
|
||||
}
|
||||
|
||||
showRestartRequiredDialog() {
|
||||
const { restart } = this.dialogs.showInfo(
|
||||
"Restart required",
|
||||
"You need to restart the game to apply the settings.",
|
||||
this.app.platformWrapper.getSupportsRestart() ? ["later:grey", "restart:misc"] : ["ok:good"]
|
||||
);
|
||||
if (restart) {
|
||||
restart.add(() => this.app.platformWrapper.performRestart());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validate(value) {
|
||||
abstract;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class EnumSetting extends BaseSetting {
|
||||
constructor(
|
||||
id,
|
||||
{
|
||||
options,
|
||||
valueGetter,
|
||||
textGetter,
|
||||
descGetter = null,
|
||||
category,
|
||||
restartRequired = true,
|
||||
iconPrefix = null,
|
||||
changeCb = null,
|
||||
magicValue = null,
|
||||
enabled = true,
|
||||
}
|
||||
) {
|
||||
super(id, category, changeCb, enabled);
|
||||
|
||||
this.options = options;
|
||||
this.valueGetter = valueGetter;
|
||||
this.textGetter = textGetter;
|
||||
this.descGetter = descGetter || (() => null);
|
||||
this.restartRequired = restartRequired;
|
||||
this.iconPrefix = iconPrefix;
|
||||
this.magicValue = magicValue;
|
||||
}
|
||||
|
||||
getHtml() {
|
||||
return `
|
||||
<div class="setting cardbox ${this.enabled ? "enabled" : "disabled"}">
|
||||
<div class="row">
|
||||
<label>TODO: SETTING TITLE</label>
|
||||
<div class="value enum" data-setting="${this.id}"></div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
TODO: SETTING DESC
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
validate(value) {
|
||||
if (value === this.magicValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const availableValues = this.options.map(option => this.valueGetter(option));
|
||||
if (availableValues.indexOf(value) < 0) {
|
||||
logger.error(
|
||||
"Value '" + value + "' is not contained in available values:",
|
||||
availableValues,
|
||||
"of",
|
||||
this.id
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
syncValueToElement() {
|
||||
const value = this.app.settings.getSetting(this.id);
|
||||
let displayText = "???";
|
||||
const matchedInstance = this.options.find(data => this.valueGetter(data) === value);
|
||||
if (matchedInstance) {
|
||||
displayText = this.textGetter(matchedInstance);
|
||||
} else {
|
||||
logger.warn("Setting value", value, "not found for", this.id, "!");
|
||||
}
|
||||
this.element.innerText = displayText;
|
||||
}
|
||||
|
||||
modify() {
|
||||
const { optionSelected } = this.dialogs.showOptionChooser("TODO: SETTING TITLE", {
|
||||
active: this.app.settings.getSetting(this.id),
|
||||
options: this.options.map(option => ({
|
||||
value: this.valueGetter(option),
|
||||
text: this.textGetter(option),
|
||||
desc: this.descGetter(option),
|
||||
iconPrefix: this.iconPrefix,
|
||||
})),
|
||||
});
|
||||
optionSelected.add(value => {
|
||||
this.app.settings.updateSetting(this.id, value);
|
||||
this.syncValueToElement();
|
||||
|
||||
if (this.restartRequired) {
|
||||
this.showRestartRequiredDialog();
|
||||
}
|
||||
|
||||
if (this.changeCb) {
|
||||
this.changeCb(this.app, value);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
export class BoolSetting extends BaseSetting {
|
||||
constructor(id, category, changeCb = null, enabled = true) {
|
||||
super(id, category, changeCb, enabled);
|
||||
}
|
||||
|
||||
getHtml() {
|
||||
return `
|
||||
<div class="setting cardbox ${this.enabled ? "enabled" : "disabled"}">
|
||||
<div class="row">
|
||||
<label>TODO: SETTING TITLE</label>
|
||||
<div class="value checkbox checked" data-setting="${this.id}">
|
||||
<span class="knob"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
TODO: SETTING DESC
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
syncValueToElement() {
|
||||
const value = this.app.settings.getSetting(this.id);
|
||||
this.element.classList.toggle("checked", value);
|
||||
}
|
||||
|
||||
modify() {
|
||||
const newValue = !this.app.settings.getSetting(this.id);
|
||||
this.app.settings.updateSetting(this.id, newValue);
|
||||
this.syncValueToElement();
|
||||
|
||||
if (this.changeCb) {
|
||||
this.changeCb(this.app, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
validate(value) {
|
||||
return typeof value === "boolean";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user