All files / src/web machine-switch.js

100% Statements 34/34
100% Branches 22/22
100% Functions 8/8
100% Lines 28/28

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            5x   40x             16x 16x 13x         12x         9x 9x 9x                   150x 150x 150x 150x 150x         824x 824x                   12x 12x 12x 4x         4x   10x 10x 10x                   12x         2x 2x 1x      
import { requirementName, satisfiesRequirement } from "./media-catalogue.js";
import { toast } from "./toast.js";
import { note } from "./analytics.js";
import { mediaLabel } from "./analytics-filters.js";
import { StartupActionParams } from "./parameters.js";
 
const PendingSwitchKey = "jsbeeb-pending-switch";
 
const NoStartupActions = Object.fromEntries(StartupActionParams.map((name) => [name, undefined]));
 
/**
 * Reloads the page as the machine `params` name (`model`, `coProcessor`), the rest of the URL as
 * `params` say and the page's own startup actions dropped. `replace` leaves no history entry.
 */
export function reloadAsMachine(urlState, params, { replace = false } = {}) {
    const url = urlState.urlWith({ ...NoStartupActions, ...params });
    if (replace) window.location.replace(url);
    else window.location.href = url;
}
 
/** Leaves `value` under `key` for the page a reload brings up. */
export function leaveForNextPage(key, value) {
    sessionStorage.setItem(key, value);
}
 
/** Takes what the last page left under `key`, once; null when it left nothing. */
export function takeFromLastPage(key) {
    const value = sessionStorage.getItem(key);
    if (value !== null) sessionStorage.removeItem(key);
    return value;
}
 
/**
 * Switches to the machine a disc needs by reloading with the model, its co-processor, the fittings
 * it needs and the disc in the URL. The toast for the new page is stashed in sessionStorage until it
 * arrives. `fitted` is the settings the running machine was started with.
 */
export class MachineSwitch {
    constructor({ model, processor, fitted = {}, urlState, modals }) {
        this.model = model;
        this.processor = processor;
        this.fitted = fitted;
        this.urlState = urlState;
        this.modals = modals;
    }
 
    /** Whether the running machine meets the disc's requirement; a disc without one is always met. */
    satisfies(d) {
        const { model, processor, fitted } = this;
        return !d.requires || satisfiesRequirement(d.requires, { model, hasTube: processor.hasTube, fitted });
    }
 
    /**
     * Reloads as the machine `d` needs with `d` in `slot`: unasked when the disc is to boot,
     * after a confirm otherwise, since the reload discards the running machine.
     * @param {object} options `boot` puts Autoboot in the URL; `replace` leaves no history entry
     * @returns {Promise<boolean>} true once the reload is under way; false to load the disc here
     */
    async switchFor(d, slot, { boot, replace = false }) {
        const { requires } = d;
        const needed = requirementName(requires, this.model);
        if (!boot) {
            const wanted = await this.modals.confirm(
                `${d.title} needs a ${needed}. Switch machine? The emulator restarts.`,
                "Switch machine",
                "Load it here",
            );
            if (!wanted) return false;
        }
        note("media", { type: "switchMachine", label: mediaLabel(d.ref) });
        if (boot) leaveForNextPage(PendingSwitchKey, `Switched to a ${needed} for ${d.title}`);
        reloadAsMachine(
            this.urlState,
            {
                ...slot.urlParamsFor(d.ref),
                ...(requires.model !== undefined && { model: requires.model, coProcessor: requires.coProcessor }),
                ...requires.fittings,
                ...(boot ? { autoboot: true } : {}),
            },
            { replace },
        );
        return true;
    }
 
    /** Toasts the switch the last reload made, once. */
    announce() {
        const notice = takeFromLastPage(PendingSwitchKey);
        if (notice === null) return;
        toast(notice, { title: "Machine" });
    }
}