Source: view/Preview.js

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/** @module */

const PREVIEW_MARGIN = 15;

/** The preview area in the presentation editor. */
export class Preview {
    /** Initialize a new preview area.
     *
     * This method registers the event handlers for the preview area of the presentation editor.
     *
     * @param {HTMLElement} container - The HTML element that will contain this preview area.
     * @param {module:model/Presentation.Presentation} presentation - The current Sozi presentation.
     * @param {module:model/Selection.Selection} selection - The object that manages the frame and layer selection.
     * @param {module:player/Viewport.Viewport} viewport - The viewport where the presentation is displayed.
     * @param {module:Controller.Controller} controller - The controller that manages the current editor.
     */
    constructor(container, presentation, selection, viewport, controller) {
        /** The HTML element that will contain this preview area.
         *
         * @type {HTMLElement} */
        this.container = container;

        /** The current Sozi presentation.
         *
         * @type {module:model/Presentation.Presentation} */
        this.presentation = presentation;

        /** The object that manages the frame and layer selection.
         *
         * @type {module:model/Selection.Selection} */
        this.selection = selection;

        /** The viewport where the presentation is displayed.
         *
         * @type {module:player/Viewport.Viewport} */
        this.viewport = viewport;

        /** The controller that manages the current editor.
         *
         * @type {module:Controller.Controller} */
        this.controller = controller;

        presentation.on("svgChange", () => this.onLoad());
        window.addEventListener("resize", () => this.repaint());
        viewport.on("mouseDown", () => document.activeElement.blur());
        viewport.on("click", (btn, evt) => this.onClick(btn, evt));
        viewport.on("userChangeState", () => controller.updateCameraStates());
        controller.on("repaint", () => this.repaint());
    }

    /** Reset the preview area when a presentation is loaded or reloaded.
     *
     * @listens module:model/Presentation.svgChange
     */
    onLoad() {
        // Set the window title to the presentation title
        document.querySelector("html head title").innerHTML = this.presentation.title;

        // Replace the content of the preview area with the SVG document
        while(this.container.hasChildNodes()) {
            this.container.removeChild(this.container.firstChild);
        }
        this.container.appendChild(this.presentation.document.root);

        this.viewport.onLoad();
        this.presentation.setInitialCameraState();

        this.container.addEventListener("mouseenter", () => this.onMouseEnter(), false);
        this.container.addEventListener("mouseleave", () => this.onMouseLeave(), false);
    }

    /** Refresh this preview area on resize and repaint events.
     *
     * This method will update the geometry of the preview area,
     * realign all cameras and repaint the viewport.
     *
     * @listens resize
     * @listens module:Controller.repaint
     *
     * @see {@linkcode module:player/Viewport.Viewport#repaint}
     */
    repaint() {
        // this.container is assumed to have padding: 0
        const parentWidth  = this.container.parentNode.clientWidth;
        const parentHeight = this.container.parentNode.clientHeight;

        const maxWidth  = parentWidth  - 2 * PREVIEW_MARGIN;
        const maxHeight = parentHeight - 2 * PREVIEW_MARGIN;

        const width  = Math.min(maxWidth, maxHeight * this.presentation.aspectWidth / this.presentation.aspectHeight);
        const height = Math.min(maxHeight, maxWidth * this.presentation.aspectHeight / this.presentation.aspectWidth);

        this.container.style.left   = (parentWidth  - width)  / 2 + "px";
        this.container.style.top    = (parentHeight - height) / 2 + "px";
        this.container.style.width  = width + "px";
        this.container.style.height = height + "px";

        if (this.selection.currentFrame) {
            this.viewport.setAtStates(this.selection.currentFrame.cameraStates);
        }

        if (this.viewport.ready) {
            this.viewport.repaint();
        }
    }

    /** Choose an outline element on an Alt+click event in this preview area.
     *
     * @param {number} button - The mouse button number that was clicked.
     * @param {MouseEvent} evt - A DOM event.
     *
     * @listens click
     */
    onClick(button, evt) {
        if (button === 0 && evt.altKey) {
            const outlineElement = evt.target;
            if (outlineElement.hasAttribute("id") && outlineElement.getBBox) {
                this.controller.setOutlineElement(outlineElement);
            }
        }
    }

    /** When the mouse hovers the preview area, reveal the clipping rectangle.
     *
     * @listens mouseenter
     *
     * @see {@linkcode module:player/Camera.Camera#revealClipping}
     */
    onMouseEnter() {
        for (let camera of this.viewport.cameras) {
            if (camera.selected) {
                camera.revealClipping();
            }
        }
        this.viewport.showHiddenElements = true;
        this.viewport.repaint();
    }

    /** When the mouse leaves the preview area, conceal the clipping rectangle.
     *
     * @listens mouseleave
     *
     * @see {@linkcode module:player/Camera.Camera#concealClipping}
     */
    onMouseLeave() {
        for (let camera of this.viewport.cameras) {
            if (camera.selected) {
                camera.concealClipping();
            }
        }
        this.viewport.showHiddenElements = false;
        this.viewport.repaint();
    }
}