VisiOmatic web client

source

control/DocUI.js

/**
 #	This file part of:	VisiOmatic
 * @file User Interface for the online documentation.

 * @requires util/VUtil.js
 * @requires control/UI.js

 * @copyright (c) 2015-2023 CNRS/IAP/CFHT/SorbonneU
 * @author Emmanuel Bertin <bertin@cfht.hawaii.edu>
*/
import {
	DomEvent,
	DomUtil,
	Util
} from 'leaflet';

import {VUtil} from '../util'
import {UI} from './UI'


export const DocUI = UI.extend( /** @lends DocUI */ {
	options: {
		title: 'Documentation',
		pdflink: undefined,
		collapsed: true,
		position: 'topleft'
	},

	/**
	 * Create a VisiOmatic dialog for the online documentation.

	 * @extends UI
	 * @memberof module:control/DocUI.js
	 * @constructs
	 * @param {string} [url] - Documentation URL.
	 * @param {object} [options] - Options.

	 * @param {string} [options.title='Documentation']
	   Title of the dialog window or panel.

	 * @param {string} [options.pdflink=undefined]
	   URL of the PDF version of the documentation.

	 * @see {@link UI} for additional control options.

	 * @returns {DocUI} Instance of a VisiOmatic documentation user interface.
	 */
	initialize: function (url, options) {
		Util.setOptions(this, options);

		this._className = 'visiomatic-control';
		this._id = 'visiomatic-doc';
		this._sideClass = 'doc';
		this._url = url;
	},

	/**
	 * Initialize the documentation dialog.
	 * @method
	 * @static
	 * @private
	 */
	_initDialog: function () {
		const	_this = this,
			className = this._className,
			layer = this._layer,
			frameBox = DomUtil.create(
				'div',
				this._className + '-framebox',
				this._dialog
		    ),
			iframe = this._iframe = DomUtil.create(
				'iframe',
				this._className + '-doc',
				frameBox
			);
		iframe.src = this._url;
		iframe.frameborder = 0;

		this._navHistory = [];
		this._navPos = 1;
		this._navIgnore = false;

		DomEvent.on(iframe, 'load', this._onloadNav, this);

		const	box = this._addDialogBox('visiomatic-doc-dialog'),
			line = this._addDialogLine('Navigate:', box),
			elem = this._addDialogElement(line);

		this._homeButton = this._addButton(
			className + '-button',
			elem,
			'home',
			'Navigate home',
			this._homeNav
		);
		this._backButton = this._addButton(
			className + '-button',
			elem,
			'back',
			'Navigate backward',
			this._backNav
		);
		this._forwardButton = this._addButton(
			className + '-button',
			elem,
			'forward',
			'Navigate forward',
			this._forwardNav
		);

		if (this.options.pdflink) {
			const	pdfButton = this._addButton(
				className + '-button',
				elem,
				'pdf',
				'Download PDF version'
			);
			pdfButton.href = this.options.pdflink;
		}
	},

	/**
	 * Update navigation buttons.
	 * @see {@link http://stackoverflow.com/a/7704305}.
	 * @private
	 * @param {number} newPos - Position in navigation history.
	 */
	_updateNav: function (newPos) {
		if (newPos !== this._navPos) {
			this._navPos = newPos;
			this._navIgnore = true;
			this._iframe.src = this._navHistory[this._navPos - 1];
			this._disableNav();
		}
	},

	/**
	 * Disable navigation buttons on both sides of history positions.
	 * @private
	 */
	_disableNav: function () {
		// Enable / disable back button?
		this._backButton.disabled = (this._navPos === 1);
		// Enable / disable forward button?
		this._forwardButton.disabled = (this._navPos >= this._navHistory.length);
	},

	/**
	 * Navigate back in the IFrame.
	 * @see {@link http://stackoverflow.com/a/7704305}.
	 * @private
	 */
	_backNav: function () {
		if (!this._backButton.disabled) {
			this._updateNav(Math.max(1, this._navPos - 1));
		}
	},

	/**
	 * Navigate forward in the IFrame.
	 * @see {@link http://stackoverflow.com/a/7704305}.
	 * @private
	 */
	_forwardNav: function () {
		if (!this._forwardButton.disabled) {
			this._updateNav(Math.min(this._navHistory.length, this._navPos + 1));
		}
	},

	/**
	 * Navigate home in the IFrame.
	 * @see {@link http://stackoverflow.com/a/7704305}.
	 * @private
	 */
	_homeNav: function () {
		if (!this._backButton.disabled) {
			this._updateNav(1);
		}
	},

	/**
	 * Triggered on IFrame load.
	 * @see {@link http://stackoverflow.com/a/7704305}.
	 * @private
	 */
	_onloadNav: function (event) {
		// Do specific things if the event is a page load
		if (event.type == 'load') {
			// Add listener for hashchanges on current page
			DomEvent.on(this._iframe.contentWindow, 'hashchange', this._onloadNav, this);
			// Force all external page links to open in new tab/window
			const	as = this._iframe.contentDocument.getElementsByTagName('a');
			for (var i = 0; i < as.length; i++) {
				if (VUtil.isExternal(as[i].href)) {
					as[i].setAttribute('target', '_blank');
				}
			}
		}

		if (!this._navIgnore) {
			const	href = this._iframe.contentWindow.location.href;
			if (href !== this._navHistory[this._navPos - 1]) {
				this._navHistory.splice(this._navPos, this._navHistory.length - this._navPos);
				this._navHistory.push(href);
				this._navPos = this._navHistory.length;
				this._disableNav();
			}
		} else {
			this._navIgnore = false;
		}
	}

});

/**
 * Instantiate a VisiOmatic dialog for the online documentation.
 * @function
 * @param {string} [url] - Documentation URL.
 * @param {object} [options] - Options: see {@link DocUI}
 * @returns {DocUI} Instance of a VisiOmatic documentation user interface.
 */
export const docUI = function (url, options) {
	return new DocUI(url, options);
};