Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 4x 4x 4x 8x 8x 4x 183x 8x 129x 65x 29x 2x 2x 2x 1x 1x 1x 1x 1x 1x 61x 61x 61x 61x 61x 61x 61x 1x 1x 61x 61x 61x 61x 61x 61x 61x 61x 61x 61x 61x 61x 61x 122x 1x 132x 48x 57x 7x 48x 57x 50x 49x 57x 38x 36x 55x 57x 48x | import { DefaultModel, findModel } from "../models.js";
import { DefaultAudioOutput, isAudioOutput } from "../audio-output.js";
import { guessModelFromHostname } from "../url-params.js";
import { fittedRoms } from "./config.js";
import { MaxPersistenceMs, persistenceSettings } from "./canvas.js";
import { toast } from "./toast.js";
// Kept in browser storage for next time, as well as in the URL.
const StoredSettings = ["keyLayout", "displayMode", "audioOutput", "speakerAmount"];
// Set by sliders, so a burst of changes makes one history entry.
const SlidSettings = ["speakerAmount"];
for (const { setting } of persistenceSettings()) {
StoredSettings.push(setting);
SlidSettings.push(setting);
}
const storedNumber = (params, name, fallback) =>
[params[name], parseFloat(window.localStorage[name])].find(Number.isFinite) ?? fallback;
const PersistenceDefaults = new Map(persistenceSettings().map(({ setting, default: fallback }) => [setting, fallback]));
const clampPersistence = (value) => Math.min(MaxPersistenceMs, Math.max(0, value));
/** The URL spellings of a model plus a fitting, from before fittings had settings of their own. */
export function mapLegacyModels(parsedQuery) {
if (!parsedQuery.model) return;
switch (parsedQuery.model.toLowerCase()) {
case "masterturbo":
parsedQuery.model = "Master";
parsedQuery.coProcessor = true;
break;
case "bmusic5000":
parsedQuery.model = "B-DFS1.2";
parsedQuery.hasMusic5000 = true;
break;
case "bteletext":
parsedQuery.model = "B-DFS1.2";
parsedQuery.hasTeletextAdaptor = true;
break;
}
}
/**
* The user's settings, resolved from the URL, browser storage and the
* defaults. set() adopts a change, persists it and dispatches an event named
* for each changed setting carrying its new value, then one "change" event
* carrying them all; whatever a setting reaches subscribes with on().
*/
export class Settings extends EventTarget {
constructor({ urlState }) {
super();
this.urlState = urlState;
const params = urlState.params;
mapLegacyModels(params);
const requestedModelName = params.model || guessModelFromHostname(window.location.hostname);
this.model = findModel(requestedModelName);
if (!this.model) {
toast(`There is no model called "${requestedModelName}". Using ${DefaultModel.name} instead.`, {
title: "Model",
});
this.model = DefaultModel;
}
this.keyLayout =
(params.keyLayout && `${params.keyLayout}`.toLowerCase()) || window.localStorage.keyLayout || "physical";
this.tubeCpuMultiplier = params.tubeCpuMultiplier || 1;
this.microphoneChannel = params.microphoneChannel;
this.coProcessor = !!params.coProcessor;
this.hasEconet = !!params.hasEconet;
this.hasMusic5000 = !!params.hasMusic5000;
this.hasTeletextAdaptor = !!params.hasTeletextAdaptor;
this.mouseJoystickEnabled = !!params.mouseJoystickEnabled;
this.speechOutput = !!params.speechOutput;
this.displayMode = params.displayMode || window.localStorage.displayMode || "rgb";
this.audioOutput =
[params.audioOutput, window.localStorage.audioOutput].find(isAudioOutput) ?? DefaultAudioOutput;
this.speakerAmount = storedNumber(params, "speakerAmount", 1);
for (const { setting, default: fallback } of persistenceSettings())
this[setting] = clampPersistence(storedNumber(params, setting, fallback));
}
get extraRoms() {
return fittedRoms(this);
}
/** Calls `listener` with the new value whenever the setting `name` is set. */
on(name, listener) {
this.addEventListener(name, () => listener(this[name]));
}
/** Adopts `changes` (an undefined value clears a setting), persists them and tells the subscribers. */
set(changes) {
for (const name of Object.keys(changes))
if (PersistenceDefaults.has(name) && changes[name] !== undefined)
changes[name] = clampPersistence(changes[name]);
for (const [name, value] of Object.entries(changes)) {
// A cleared persistence is back at its display's default, though nothing remembers it.
if (name === "model") this[name] = findModel(value) ?? this.model;
else if (value === undefined && PersistenceDefaults.has(name)) this[name] = PersistenceDefaults.get(name);
else this[name] = value;
if (!StoredSettings.includes(name)) continue;
if (value === undefined) window.localStorage.removeItem(name);
else window.localStorage[name] = value;
}
this.urlState.set(changes, { settle: Object.keys(changes).some((name) => SlidSettings.includes(name)) });
for (const name of Object.keys(changes)) this.dispatchEvent(new Event(name));
this.dispatchEvent(new CustomEvent("change", { detail: changes }));
}
}
|