class Scene { constructor(id) { this.id = id; this.viewControllers = [ ]; this.modalContainer = document.createElement("DIV"); this.modalContainer.className = "modal-container"; document.body.appendChild(this.modalContainer); //Create the vertical centerer helper line. var verticalCenterer = document.createElement("DIV"); verticalCenterer.className = "vertical-centerer"; this.modalContainer.appendChild(verticalCenterer); this.popoverContainer = document.createElement("DIV"); this.popoverContainer.className = "popover-container"; document.body.appendChild(this.popoverContainer); } addViewController(viewController) { if (viewController.scene) { viewController.scene.removeViewController(viewController); } viewController.scene = this; this.viewControllers.push(viewController); } removeViewController(viewController) { var index = this.viewControllers.indexOf(viewController); if (index != -1) { this.viewControllers.splice(index, 1); } } /** * Shows the active view controller of this scene. * If no view controller is active, the default view controller is shown. * If a view controller is passes as a parameter, it is used as the view controller to show the scene with. */ show(viewControllerToShow = null) { this.hide(); var newLastViewController = this.activeViewController; if (viewControllerToShow) { this.activeViewController = viewControllerToShow; } this.lastViewController = newLastViewController; viewControllerToShow = this.activeViewController; if (!viewControllerToShow) { viewControllerToShow = this.initialViewController; } if (!viewControllerToShow) { console.error("Attempted to show a scene that has no initial view controller whatsoever."); return; } UIKit.activeScene = this; this.showViewControllerInternal(viewControllerToShow); } /** * Shows this scene and the given view controller. */ showWithViewController(viewController) { this.show(viewController); } /** * Hides the active view controller of this scene. */ hide() { if (this.activeViewController) { this.hideViewControllerInternal(this.activeViewController); } if (UIKit.activeScene == this) { UIKit.activeScene = null; } } /** * Activates and shows the given view controller inside the scene, given the fact that it is actually a view controller of this scene. */ showViewController(viewController) { var controllerIndex = this.viewControllers.indexOf(viewController); if (controllerIndex == -1) { return; } for (var i = 0; i < this.viewControllers.length; i++) { var thisViewController = this.viewControllers[i]; if (i == controllerIndex) { this.showViewControllerInternal(thisViewController); } else { this.hideViewControllerInternal(thisViewController); } } var newLastViewController = this.activeViewController; this.activeViewController = viewController; this.lastViewController = newLastViewController; } showViewControllerModally(viewController) { var controllerIndex = this.viewControllers.indexOf(viewController); if (controllerIndex == -1) { return; } if (this.modalViewController) { this.dismissModalViewController(); } this.modalViewController = viewController; this.showViewControllerInternal(viewController); this.previousParent = viewController.element.parentNode; this.modalContainer.appendChild(viewController.element); this.modalContainer.className = "modal-container visible"; } dismissModalViewController() { if (this.modalViewController) { if (this.previousParent) { this.previousParent.appendChild(this.modalViewController.element); } //Clear the hash if is the modal view controller's hash. if ("#" + this.modalViewController.hash == window.location.hash) { window.location.hash = ""; } //HINT: Moved inside the if (was two lines below before) because of an error when trying to dismiss a modal view controller without it being presented. this.hideViewControllerInternal(this.modalViewController); } this.modalViewController = null; this.modalContainer.className = "modal-container"; } /** * Shows the given popover in this scene's popover layer. */ showPopover(popover) { if (!this.activePopoverData) { this.activePopoverData = { }; } //Check if this popover is already visible (not being dismissed). if ((popover.id in this.activePopoverData) && (!this.dismissingPopovers || this.dismissingPopovers.indexOf(popover) == -1)) { //Call the disappearing and appearing functions in order. //The popover might want to prepare itself for being re-shown. popover.viewWillDisappear(); popover.viewDidDisappear(); popover.viewWillAppear(); popover.viewDidAppear(); //Do nothing more because the popover will stay visible. return; } //Stop dismissing if this popover is already being dismissed. var dismissingIndex = (this.dismissingPopovers ? this.dismissingPopovers.indexOf(popover) : -1); if (dismissingIndex != -1) { if (popover.id in this.activePopoverData) { clearTimeout(this.activePopoverData[popover.id].dismissPopoverTimeout); } //Move the dismissing popover all the way to the first position in the array and then call the finishing function. this.dismissingPopovers.splice(dismissingIndex, 1); this.finishDismissingPopover(popover); } //Add an object to the active popover data object. This is the indicator that the popover with the key index is active. this.activePopoverData[popover.id] = { disableBackground: popover.disableBackground }; popover.viewWillAppear(); this.popoverContainer.appendChild(popover.element); this.popoverContainer.className = "popover-container visible" + (this.anyPopoverDisablesBackground() ? "" : " click-through"); popover.viewDidAppear(); } /** * Hides the specified popover, if it is visible. */ dismissPopover(popover, animated = true) { if (!this.activePopoverData || !(popover.id in this.activePopoverData)) { return; } if (!this.dismissingPopovers) { this.dismissingPopovers = [ ]; } //Do not do anything if the popover is already being dismissed. if (this.dismissingPopovers.indexOf(popover) != -1) { return; } //Tell the popover to prepare for disappearing. popover.viewWillDisappear(); popover.visible = false; //Start the fade animation. popover.element.classList.add("dismissing"); //Make the popover container click-through while dismissing. //Only do this if no other non-click-through popover is currently visible. if (!this.anyPopoverDisablesBackground(popover.id)) { this.popoverContainer.className = "popover-container visible click-through"; } //Add the popover to the end of the list of dismissing popovers if the process is animated. if (animated) { this.dismissingPopovers.push(popover); } //Set the timeout for definitely hiding the element. if (animated) { this.activePopoverData[popover.id].dismissPopoverTimeout = setTimeout(this.finishDismissingPopover.bind(this), 410); } else { this.finishDismissingPopover(popover); } } /** * Returns true if any visible popover requires the background to be disabled, false otherwise. */ anyPopoverDisablesBackground(exceptionId = null) { if (!this.activePopoverData) { return false; } for (var id in this.activePopoverData) { if (id != exceptionId && this.activePopoverData[id].disableBackground) { return true; } } return false; } finishDismissingPopover(dismissingPopover = null) { if (!dismissingPopover) { //Fetch the popover to dismiss from the queue if it is not provided directly. dismissingPopover = this.dismissingPopovers[0]; this.dismissingPopovers.splice(0, 1); } if (!dismissingPopover || dismissingPopover.element.parentNode != this.popoverContainer) { return; } this.popoverContainer.removeChild(dismissingPopover.element); dismissingPopover.element.className = ""; dismissingPopover.viewDidDisappear(); delete this.activePopoverData[dismissingPopover.id]; //If in the meantime no other popover was shown, hide the popover container. if (Object.keys(this.activePopoverData).length == 0) { this.popoverContainer.className = "popover-container"; } } showViewControllerInternal(viewController) { if (!viewController.visible) { viewController.element.style.visibility = "hidden"; if (viewController.element.className.indexOf("visible") == -1) { viewController.element.classList.add("visible"); } viewController.viewWillAppear(); viewController.subviewsWillAppear(); document.body.appendChild(viewController.element); viewController.element.style.visibility = ""; viewController.visible = true; viewController.viewDidAppear(); viewController.subviewsDidAppear(); } } hideViewControllerInternal(viewController) { if (viewController.visible) { viewController.viewWillDisappear(); viewController.subviewsWillDisappear(); viewController.element.classList.remove("visible"); viewController.visible = false; document.body.removeChild(viewController.element); viewController.viewDidDisappear(); viewController.subviewsDidDisappear(); } } showLastViewController() { if (this.lastViewController) { this.showViewController(this.lastViewController); } } /** * Returns true if this scene contains the given view controller, false otherwise. */ containsViewController(viewController) { for (var i = 0; i < this.viewControllers.length; i++) { if (this.viewControllers[i] == viewController) { return true; } } return false; } /** * Looks for any view controller inside this scene that has the given hash and returns it. */ getViewControllerWithHash(hash) { for (var i = 0; i < this.viewControllers.length; i++) { if (this.viewControllers[i].hash == hash) { return this.viewControllers[i]; } } return null; } /** * Looks for any view controller inside this scene that has the given request path and returns it. */ getViewControllerWithRequestPath(requestPath) { for (var i = 0; i < this.viewControllers.length; i++) { if (this.viewControllers[i].requestPath == requestPath) { return this.viewControllers[i]; } } return null; } }