/**
* 3D Foundation Project
* Copyright 2025 Smithsonian Institution
*
* 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.
*/
import CFullscreen from "@ff/scene/components/CFullscreen";
import CVARManager from "client/components/CVARManager";
import CVViewer from "client/components/CVViewer";
import CustomElement, { customElement, html } from "@ff/ui/CustomElement";
import Icon from "@ff/ui/Icon";
import Notification from "@ff/ui/Notification";
import ExplorerApplication, { IExplorerApplicationProps } from "../../applications/ExplorerApplication";
import ContentView from "./ContentView";
import ChromeView from "./ChromeView";
import styles from "./styles.scss";
////////////////////////////////////////////////////////////////////////////////
// EXPLORER ICONS
Icon.add("globe", html``);
Icon.add("cog", html``);
Icon.add("eye", html``);
Icon.add("palette", html``);
Icon.add("comment", html``);
//Icon.add("information", html``);
Icon.add("article", html``);
Icon.add("document", html``);
Icon.add("share", html``);
Icon.add("expand", html``);
Icon.add("zoom", html``);
Icon.add("tools", html``);
Icon.add("environment", html``);
Icon.add("bulb", html``);
Icon.add("spot", html``);
Icon.add("area", html``);
Icon.add("sun", html``);
Icon.add("half-sun", html``);
Icon.add("tape", html``);
Icon.add("knife", html``);
Icon.add("bars", html``);
Icon.add("triangle-left", html``);
Icon.add("triangle-right", html``);
Icon.add("twitter", html``);
Icon.add("facebook", html``);
Icon.add("instagram", html``);
Icon.add("linkedin", html``);
Icon.add("email", html``);
Icon.add("copy", html``);
Icon.add("ar", html``);
Icon.add("device-move", html``);
Icon.add("audio", html``);
Icon.add("help", html``);
Icon.add("move", html``);
Icon.add("rotate", html``);
Icon.add("pause", html``);
Icon.add("caption", html``);
Icon.add("undo", html``);
//Icon.add("name", html``);
////////////////////////////////////////////////////////////////////////////////
/**
* Main UI view for the Voyager Explorer application.
*/
@customElement("voyager-explorer")
export default class MainView extends CustomElement
{
application: ExplorerApplication = null;
static get observedAttributes() { return ['root', 'document']; }
constructor(application?: ExplorerApplication)
{
super();
if (application) {
this.application = application;
}
this.addEventListener('focus', this.onFocus);
}
protected get fullscreen() {
return this.application.system.getMainComponent(CFullscreen);
}
protected get arManager() {
return this.application.system.getMainComponent(CVARManager);
}
protected get viewer() {
return this.application.system.getComponent(CVViewer);
}
protected firstConnected()
{
super.firstConnected();
if (!this.application) {
const props: IExplorerApplicationProps = {
root: this.getAttribute("root"),
dracoRoot: this.getAttribute("dracoRoot"),
resourceRoot: this.getAttribute("resourceRoot"),
document: this.getAttribute("document"),
model: this.getAttribute("model"),
geometry: this.getAttribute("geometry"),
texture: this.getAttribute("texture"),
quality: this.getAttribute("quality"),
uiMode: this.getAttribute("uiMode"),
bgColor: this.getAttribute("bgColor"),
bgStyle: this.getAttribute("bgStyle"),
controls: this.getAttribute("controls"),
prompt: this.getAttribute("prompt"),
reader: this.getAttribute("reader"),
lang: this.getAttribute("lang")
};
this.application = new ExplorerApplication(null, props);
}
this.attachShadow({mode: 'open'});
const shadowRoot = this.shadowRoot;
// add style
const styleElement = document.createElement("style");
styleElement.innerText = styles;
shadowRoot.appendChild(styleElement);
const system = this.application.system;
shadowRoot.appendChild(new ContentView(system));
shadowRoot.appendChild(new ChromeView(system));
const notifications = document.createElement("div");
notifications.setAttribute("id", Notification.stackId);
shadowRoot.appendChild(notifications);
Notification.shadowRootNode = shadowRoot;
//this.setAttribute("tabindex", "0");
const introAnnouncement = document.createElement("div");
introAnnouncement.classList.add("sr-only");
introAnnouncement.setAttribute("id", "sr-intro");
introAnnouncement.setAttribute("aria-live", "polite");
shadowRoot.appendChild(introAnnouncement);
}
protected connected()
{
this.fullscreen.fullscreenElement = this;
this.viewer.rootElement = this;
this.arManager.shadowRoot = this.shadowRoot;
}
protected disconnected()
{
super.disconnected();
this.fullscreen.fullscreenElement = null;
this.viewer.rootElement = null;
if(!window["VoyagerStory"]) {
this.application.dispose();
this.application = null;
}
}
attributeChangedCallback(name: string, old: string | null, value: string | null)
{
const app = this.application;
super.attributeChangedCallback(name, old, value);
if(app && name === "root") {
const newRoot = this.getAttribute("root");
if(newRoot.length > 0) {
app.props.root = newRoot
app.reloadDocument();
this.connected();
}
}
else if(app && name === "document") {
app.props.document = this.getAttribute("document");
app.reloadDocument();
this.connected();
}
else if(app && name === "controls") {
app.enableNavigation(value);
}
}
protected onFocus()
{
this.shadowRoot.getElementById("sr-intro").innerText =
"The Voyager web application allows you to view "
+ "and interact with a 3D model from the Smithsonian collection. Use the tab key to "
+ "move through interactive elements, enter or spacebar keys to activate, and the escape key to exit menus.";
}
//** Pass-through for API functions so they can be called from the main component element */
toggleAnnotations()
{
if(this.application) {
this.application.toggleAnnotations();
}
}
setAnnotationsEnabled(visible: boolean)
{
if(this.application) {
this.application.setAnnotationsEnabled(visible);
}
}
toggleReader()
{
if(this.application) {
this.application.toggleReader();
}
}
setReaderEnabled(enabled: boolean)
{
if(this.application) {
this.application.setReaderEnabled(enabled);
}
}
toggleTours()
{
if(this.application) {
this.application.toggleTours();
}
}
setToursEnabled(enabled: boolean)
{
if(this.application) {
this.application.setToursEnabled(enabled);
}
}
toggleTools()
{
if(this.application) {
this.application.toggleTools();
}
}
setToolsEnabled(visible: boolean)
{
if(this.application) {
this.application.setToolsEnabled(visible);
}
}
toggleMeasurement()
{
if(this.application) {
this.application.toggleMeasurement();
}
}
setMeasurementEnabled(visible: boolean)
{
if(this.application) {
this.application.setMeasurementEnabled(visible);
}
}
enableAR()
{
if(this.application) {
this.application.enableAR();
}
}
getArticles()
{
if(this.application) {
return this.application.getArticles();
}
}
getAnnotations()
{
if(this.application) {
return this.application.getAnnotations();
}
}
getCameraOrbit(type?: string)
{
if(this.application) {
return this.application.getCameraOrbit(type ? type : null);
}
}
setCameraOrbit( yaw: string, pitch: string)
{
if(this.application) {
this.application.setCameraOrbit(yaw, pitch);
}
}
getCameraOffset(type?: string)
{
if(this.application) {
return this.application.getCameraOffset(type || null);
}
}
setCameraOffset( x: string, y: string, z: string)
{
if(this.application) {
this.application.setCameraOffset(x, y, z);
}
}
setBackgroundColor(color0: string, color1?: string)
{
if(this.application) {
this.application.setBackgroundColor(color0, color1 || null);
}
}
setBackgroundStyle(style: string)
{
if(this.application) {
this.application.setBackgroundStyle(style);
}
}
setActiveAnnotation(id: string)
{
if(this.application) {
this.viewer.ins.activeAnnotation.setValue(id);
}
}
setActiveArticle(id: string)
{
if(this.application) {
this.application.setActiveArticle(id);
}
}
setTourStep(tourIdx: string, stepIdx: string, interpolate?: boolean)
{
if(this.application) {
this.application.setTourStep(tourIdx, stepIdx, interpolate ?? true);
}
}
getTours()
{
if(this.application) {
return this.application.getTours();
}
}
setLanguage(languageID: string)
{
if(this.application) {
this.application.setLanguage(languageID);
}
}
getLanguages()
{
if(this.application) {
return this.application.getLanguages();
}
}
getActiveLanguage()
{
if(this.application) {
return this.application.getActiveLanguage();
}
}
resetViewer()
{
if(this.application) {
this.application.resetViewer();
}
}
// get tags as displayed by the tag cloud
getTags()
{
if(this.application) {
return this.viewer.outs.tagCloud.value;
}
}
// set active tags
setActiveTags(tags: string)
{
if(this.application) {
const viewerActiveTags = this.viewer.ins.activeTags;
viewerActiveTags.setValue(tags);
}
}
}