@google/model-viewer
Version:
Easily display interactive 3D models on the web and in AR!
150 lines • 6.53 kB
JavaScript
/*
* Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var _a, _b;
import { EventDispatcher } from 'three';
import { debounce, getFirstMapKey } from '../../utilities.js';
export const INITIAL_STATUS_ANNOUNCEMENT = 'This page includes one or more 3D models that are loading';
export const FINISHED_LOADING_ANNOUNCEMENT = 'All 3D models in the page have loaded';
const UPDATE_STATUS_DEBOUNCE_MS = 100;
const $modelViewerStatusInstance = Symbol('modelViewerStatusInstance');
const $updateStatus = Symbol('updateStatus');
/**
* The LoadingStatusAnnouncer manages announcements of loading status across
* all <model-viewer> elements in the document at any given time. As new
* <model-viewer> elements are connected to the document, they are registered
* with a LoadingStatusAnnouncer singleton. As they are disconnected, the are
* also unregistered. Announcements are made to indicate the following
* conditions:
*
* 1. There are <model-viewer> elements that have yet to finish loading
* 2. All <model-viewer> elements in the page have finished attempting to load
*/
export class LoadingStatusAnnouncer extends EventDispatcher {
constructor() {
super();
/**
* The "status" instance is the <model-viewer> instance currently designated
* to announce the loading status of all <model-viewer> elements in the
* document at any given time. It might change as <model-viewer> elements are
* attached or detached over time.
*/
this[_a] = null;
this.registeredInstanceStatuses = new Map();
this.loadingPromises = [];
/**
* This element is a node that floats around the document as the status
* instance changes (see above). It is a singleton that represents the loading
* status for all <model-viewer> elements currently in the page. It has its
* role attribute set to "status", which causes screen readers to announce
* any changes to its text content.
*
* @see https://www.w3.org/TR/wai-aria-1.1/#status
*/
this.statusElement = document.createElement('p');
this.statusUpdateInProgress = false;
this[_b] = debounce(() => this.updateStatus(), UPDATE_STATUS_DEBOUNCE_MS);
const { statusElement } = this;
const { style } = statusElement;
statusElement.setAttribute('role', 'status');
style.position = 'absolute';
style.color = 'transparent';
style.top = style.left = style.margin = '0';
style.pointerEvents = 'none';
}
/**
* Register a <model-viewer> element with the announcer. If it is not yet
* loaded, its loading status will be tracked by the announcer.
*/
registerInstance(modelViewer) {
if (this.registeredInstanceStatuses.has(modelViewer)) {
return;
}
let onUnregistered = () => { };
const loadShouldBeMeasured = modelViewer.loaded === false && !!modelViewer.src;
const loadAttemptCompletes = new Promise((resolve) => {
if (!loadShouldBeMeasured) {
resolve();
return;
}
const resolveHandler = () => {
resolve();
modelViewer.removeEventListener('load', resolveHandler);
modelViewer.removeEventListener('error', resolveHandler);
};
modelViewer.addEventListener('load', resolveHandler);
modelViewer.addEventListener('error', resolveHandler);
onUnregistered = resolveHandler;
});
this.registeredInstanceStatuses.set(modelViewer, { onUnregistered });
this.loadingPromises.push(loadAttemptCompletes);
if (this.modelViewerStatusInstance == null) {
this.modelViewerStatusInstance = modelViewer;
}
}
/**
* Unregister a <model-viewer> element with the announcer. Its loading status
* will no longer be tracked by the announcer.
*/
unregisterInstance(modelViewer) {
if (!this.registeredInstanceStatuses.has(modelViewer)) {
return;
}
const statuses = this.registeredInstanceStatuses;
const instanceStatus = statuses.get(modelViewer);
statuses.delete(modelViewer);
instanceStatus.onUnregistered();
if (this.modelViewerStatusInstance === modelViewer) {
this.modelViewerStatusInstance = statuses.size > 0 ?
getFirstMapKey(statuses) :
null;
}
}
get modelViewerStatusInstance() {
return this[$modelViewerStatusInstance];
}
set modelViewerStatusInstance(value) {
const currentInstance = this[$modelViewerStatusInstance];
if (currentInstance === value) {
return;
}
const { statusElement } = this;
if (value != null && value.shadowRoot != null) {
value.shadowRoot.appendChild(statusElement);
}
else if (statusElement.parentNode != null) {
statusElement.parentNode.removeChild(statusElement);
}
this[$modelViewerStatusInstance] = value;
this[$updateStatus]();
}
async updateStatus() {
if (this.statusUpdateInProgress || this.loadingPromises.length === 0) {
return;
}
this.statusElement.textContent = INITIAL_STATUS_ANNOUNCEMENT;
this.statusUpdateInProgress = true;
this.dispatchEvent({ type: 'initial-status-announced' });
while (this.loadingPromises.length) {
const { loadingPromises } = this;
this.loadingPromises = [];
await Promise.all(loadingPromises);
}
this.statusElement.textContent = FINISHED_LOADING_ANNOUNCEMENT;
this.statusUpdateInProgress = false;
this.dispatchEvent({ type: 'finished-loading-announced' });
}
}
_a = $modelViewerStatusInstance, _b = $updateStatus;
//# sourceMappingURL=status-announcer.js.map