All files / src/web machine-switch.js

100% Statements 31/31
100% Branches 19/19
100% Functions 8/8
100% Lines 25/25

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          3x   21x             13x 13x 11x         7x         8x 8x 8x                 113x 113x 113x 113x         35x                   9x 9x 5x         5x   7x 7x 7x                   9x         2x 2x 1x      
import { satisfiesRequirement } from "./media-catalogue.js";
import { toast } from "./toast.js";
import { noteEvent } from "./analytics.js";
import { StartupActionParams } from "./url-state.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 and the
 * disc in the URL. The toast for the new page is stashed in sessionStorage until it arrives.
 */
export class MachineSwitch {
    constructor({ model, processor, urlState, modals }) {
        this.model = model;
        this.processor = processor;
        this.urlState = urlState;
        this.modals = modals;
    }
 
    /** Whether the running machine meets the disc's requirement; a disc without one is always met. */
    satisfies(d) {
        return !d.requires || satisfiesRequirement(d.requires, { model: this.model, hasTube: this.processor.hasTube });
    }
 
    /**
     * 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;
        if (!boot) {
            const wanted = await this.modals.confirm(
                `${d.title} needs a ${requires.name}. Switch machine? The emulator restarts.`,
                "Switch machine",
                "Load it here",
            );
            if (!wanted) return false;
        }
        noteEvent("media", "switchMachine", d.ref);
        if (boot) leaveForNextPage(PendingSwitchKey, `Switched to a ${requires.name} for ${d.title}`);
        reloadAsMachine(
            this.urlState,
            {
                ...slot.urlParamsFor(d.ref),
                model: requires.model,
                coProcessor: requires.coProcessor,
                ...(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" });
    }
}