forked from google/model-viewer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* initial logic for UI for mobile view * add posts * add qr code capability via npm package qrious * name changes * add basic routing * move mobile to own expandable section * add todos * env image, glb, poster, and snippet receive and updates * remove router * add catch statements and use an update object to initiate mobile view * add modal for mobile view instead of in bar * searching for the listener... * fetching loops such that updates are constantly being asked for, and the editor knows when mobile is ready * testing tests * remove poster, fix async everything but applying material edits * add random number for session * add ar toggle * initial test of ar modes * add ar modes, dynamic refresh button if viewers out of sync * get materials to update by sending packed model * code cleanup * moved open section into mobile view * fix wording of modal * cleaning up comments
- Loading branch information
1 parent
60a1164
commit aa2587c
Showing
17 changed files
with
1,546 additions
and
667 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<!-- | ||
@license | ||
Copyright 2020 Google LLC. 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. | ||
--> | ||
|
||
<!DOCTYPE html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="description" content="<model-viewer> editor"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
<title>Model Editor</title> | ||
<link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet"> | ||
<link rel="shortcut icon" type="image/png" href="../../shared-assets/icons/favicon.png"/> | ||
<!-- Web animations for paper-dropdown --> | ||
<script src="../../node_modules/web-animations-js/web-animations-next-lite.min.js"></script> | ||
<script src="../../node_modules/js-beautify/js/lib/beautify-html.js"></script> | ||
<script src="../../node_modules/js-beautify/js/lib/beautify-css.js"></script> | ||
<script> | ||
// Necessary hack for Redux. See: https://github.com/reduxjs/redux/pull/2910 | ||
window.process = { | ||
env: { | ||
NODE_ENV :'production' | ||
} | ||
}; | ||
</script> | ||
<script type="module" src="../../dist/space-opera.js"></script> | ||
</head> | ||
<style> | ||
:root { | ||
--expandable-section-background: #2b2d30; | ||
--expandable-section-text: #EEEEEE; | ||
--text-on-expandable-background: #F5F5F5; | ||
--number-input-background: #212121; | ||
} | ||
|
||
body { | ||
margin: 0 | ||
} | ||
</style> | ||
<body> | ||
<mobile-view></mobile-view> | ||
</body> |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
packages/space-opera/src/components/mobile_view/components/mobile_modal.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/** | ||
* @license | ||
* Copyright 2020 Google LLC. 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. | ||
* | ||
*/ | ||
|
||
import {customElement, html, internalProperty, property, query} from 'lit-element'; | ||
// @ts-ignore, the qrious package isn't typed | ||
import QRious from 'qrious'; | ||
|
||
import {openModalStyles} from '../../../styles.css.js'; | ||
import {ConnectedLitElement} from '../../connected_lit_element/connected_lit_element.js'; | ||
|
||
/** | ||
* Modal for displaying the QR Code & link | ||
*/ | ||
@customElement('mobile-modal') | ||
export class MobileModal extends ConnectedLitElement { | ||
static styles = openModalStyles; | ||
|
||
@property({type: Number}) pipeId = 0; | ||
@internalProperty() isOpen: boolean = false; | ||
@internalProperty() isNewQRCode = true; | ||
@query('canvas#qr') canvasQR!: HTMLCanvasElement; | ||
|
||
get viewableSite(): string { | ||
const path = window.location.origin + window.location.pathname; | ||
return `${path}view/?id=${this.pipeId}`; | ||
} | ||
|
||
open() { | ||
if (this.isNewQRCode) { | ||
new QRious({element: this.canvasQR, value: this.viewableSite, size: 200}); | ||
this.isNewQRCode = false | ||
} | ||
this.isOpen = true; | ||
} | ||
|
||
close() { | ||
this.isOpen = false; | ||
} | ||
|
||
render() { | ||
return html` | ||
<paper-dialog id="file-modal" modal ?opened=${this.isOpen} class="dialog"> | ||
<div class="FileModalContainer"> | ||
<div class="FileModalHeader"> | ||
<div>Mobile View</div> | ||
</div> | ||
<div style="font-size: 14px; font-weight: 500; margin: 10px 0px; color: white; word-wrap: break-word; width: 100%;"> | ||
Use the QR Code to open your edited model, environment image, and <model-viewer> snippet on a mobile device to test out AR features. | ||
After every subsequent change, click the "Refresh Mobile" button. | ||
</div> | ||
<canvas id="qr" style="display: block; margin-bottom: 20px;"></canvas> | ||
<div style="margin: 10px 0px; overflow-wrap: break-word; word-wrap: break-word;"> | ||
<a href=${this.viewableSite} style="color: white;" target="_blank"> | ||
${this.viewableSite} | ||
</a> | ||
</div> | ||
</div> | ||
<div class="FileModalCancel"> | ||
<mwc-button unelevated icon="cancel" | ||
@click=${this.close}>Close</mwc-button> | ||
</div> | ||
</paper-dialog>`; | ||
} | ||
} | ||
|
||
declare global { | ||
interface HTMLElementTagNameMap { | ||
'mobile-modal': MobileModal; | ||
} | ||
} |
216 changes: 216 additions & 0 deletions
216
packages/space-opera/src/components/mobile_view/mobile_view.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
/** | ||
* @license | ||
* Copyright 2020 Google LLC. 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. | ||
* | ||
*/ | ||
|
||
import {GltfModel, ModelViewerConfig} from '@google/model-viewer-editing-adapter/lib/main'; | ||
import {customElement, html, internalProperty} from 'lit-element'; | ||
import {ifDefined} from 'lit-html/directives/if-defined'; | ||
|
||
import {reduxStore} from '../../space_opera_base.js'; | ||
import {ArConfigState, State} from '../../types.js'; | ||
import {applyCameraEdits, Camera, INITIAL_CAMERA} from '../camera_settings/camera_state.js'; | ||
import {dispatchSetCamera, getCamera} from '../camera_settings/reducer.js'; | ||
import {dispatchEnvrionmentImage, dispatchSetConfig, getConfig} from '../config/reducer.js'; | ||
import {ConnectedLitElement} from '../connected_lit_element/connected_lit_element.js'; | ||
import {dispatchSetHotspots, getHotspots} from '../hotspot_panel/reducer.js'; | ||
import {HotspotConfig} from '../hotspot_panel/types.js'; | ||
import {dispatchGltfUrl, getGltfModel, getGltfUrl} from '../model_viewer_preview/reducer.js'; | ||
import {renderHotspots} from '../utils/hotspot/render_hotspots.js'; | ||
import {dispatchArConfig, getArConfig} from './reducer.js'; | ||
|
||
import {styles} from './styles.css.js'; | ||
|
||
/** | ||
* The view loaded at /editor/view/?id=xyz | ||
*/ | ||
@customElement('mobile-view') | ||
export class MobileView extends ConnectedLitElement { | ||
static styles = styles; | ||
|
||
@internalProperty() gltfUrl: string|undefined; | ||
@internalProperty() config: ModelViewerConfig = {}; | ||
@internalProperty() arConfig: ArConfigState = {}; | ||
@internalProperty() camera: Camera = INITIAL_CAMERA; | ||
@internalProperty() hotspots: HotspotConfig[] = []; | ||
@internalProperty() gltf?: GltfModel; | ||
|
||
@internalProperty() pipeId = window.location.search.replace('?id=', ''); | ||
@internalProperty() base = 'https://ppng.io/modelviewereditor'; | ||
@internalProperty() snippetPipeUrl = `${this.base}-state-${this.pipeId}`; | ||
@internalProperty() updatesPipeUrl = `${this.base}-updates-${this.pipeId}`; | ||
@internalProperty() mobilePingUrl = `${this.base}-ping-${this.pipeId}`; | ||
|
||
stateChanged(state: State) { | ||
this.gltfUrl = getGltfUrl(state); | ||
this.config = getConfig(state); | ||
this.arConfig = getArConfig(state); | ||
this.hotspots = getHotspots(state); | ||
this.camera = getCamera(state); | ||
this.gltf = getGltfModel(state); | ||
} | ||
|
||
getSrcPipeUrl(srcType: string): string { | ||
return `https://ppng.io/modelviewereditor-srcs-${srcType}-${this.pipeId}`; | ||
} | ||
|
||
// TODO: https://javascript.info/fetch-progress | ||
async waitForModel() { | ||
await fetch(this.getSrcPipeUrl('gltf')) | ||
.then(response => response.blob()) | ||
.then(blob => { | ||
const modelUrl = URL.createObjectURL(blob); | ||
reduxStore.dispatch(dispatchGltfUrl(modelUrl)); | ||
reduxStore.dispatch(dispatchSetHotspots([])); | ||
}) | ||
.catch((error) => { | ||
console.error('Error:', error); | ||
}); | ||
} | ||
|
||
async waitForState(envChanged: boolean) { | ||
let partialState: any = {}; | ||
await fetch(this.snippetPipeUrl) | ||
.then((response) => { | ||
if (response.ok) { | ||
return response.json(); | ||
} else { | ||
throw new Error('Something went wrong'); | ||
} | ||
}) | ||
.then((responseJson) => { | ||
partialState = responseJson; | ||
}) | ||
.catch((error) => { | ||
console.log('error', error); | ||
}); | ||
|
||
// These links would be corresponding to the original editor's link. | ||
if (envChanged) { | ||
partialState.config.environmentImage = undefined; | ||
} else if (this.config.environmentImage) { | ||
partialState.config.environmentImage = this.config.environmentImage; | ||
} | ||
partialState.config.src = this.gltfUrl; | ||
|
||
reduxStore.dispatch(dispatchSetHotspots(partialState.hotspots)); | ||
reduxStore.dispatch(dispatchSetCamera(partialState.camera)); | ||
reduxStore.dispatch(dispatchSetConfig(partialState.config)); | ||
reduxStore.dispatch(dispatchArConfig(partialState.arConfig)); | ||
} | ||
|
||
async waitForEnv(envIsHdr: boolean) { | ||
await fetch(this.getSrcPipeUrl('env')) | ||
.then(response => response.blob()) | ||
.then(blob => { | ||
// simulating createBlobUrlFromEnvironmentImage | ||
const addOn = envIsHdr ? '#.hdr' : ''; | ||
const envUrl = URL.createObjectURL(blob) + addOn; | ||
reduxStore.dispatch(dispatchEnvrionmentImage(envUrl)); | ||
}) | ||
.catch((error) => { | ||
console.error('Error:', error); | ||
}); | ||
} | ||
|
||
async waitForData(json: any) { | ||
if (json.gltfChanged) { | ||
await this.waitForModel(); | ||
} | ||
if (json.stateChanged) { | ||
await this.waitForState(json.envChanged); | ||
} | ||
if (json.envChanged) { | ||
await this.waitForEnv(json.envIsHdr); | ||
} | ||
} | ||
|
||
async fetchLoop() { | ||
await fetch(this.updatesPipeUrl) | ||
.then(response => response.json()) | ||
.then(json => this.waitForData(json)) | ||
.catch((error) => { | ||
console.error('Error:', error); | ||
}); | ||
} | ||
|
||
async triggerFetchLoop() { | ||
await this.fetchLoop(); | ||
await this.triggerFetchLoop(); | ||
} | ||
|
||
render() { | ||
const config = {...this.config}; | ||
applyCameraEdits(config, this.camera); | ||
const skyboxImage = | ||
config.useEnvAsSkybox ? config.environmentImage : undefined; | ||
const childElements = [...renderHotspots(this.hotspots)]; | ||
return html` | ||
<div class="app"> | ||
<div class="mvContainer"> | ||
<model-viewer | ||
src=${this.gltfUrl || ''} | ||
?ar=${ifDefined(!!this.arConfig.ar)} | ||
ar-modes=${ifDefined(this.arConfig!.arModes)} | ||
?autoplay=${!!config.autoplay} | ||
?auto-rotate=${!!config.autoRotate} | ||
?camera-controls=${!!config.cameraControls} | ||
environment-image=${ifDefined(config.environmentImage)} | ||
skybox-image=${ifDefined(skyboxImage)} | ||
exposure=${ifDefined(config.exposure)} | ||
poster=${ifDefined(config.poster)} | ||
reveal=${ifDefined(config.reveal)} | ||
shadow-intensity=${ifDefined(config.shadowIntensity)} | ||
shadow-softness=${ifDefined(config.shadowSoftness)} | ||
camera-target=${ifDefined(config.cameraTarget)} | ||
camera-orbit=${ifDefined(config.cameraOrbit)} | ||
field-of-view=${ifDefined(config.fieldOfView)} | ||
min-camera-orbit=${ifDefined(config.minCameraOrbit)} | ||
max-camera-orbit=${ifDefined(config.maxCameraOrbit)} | ||
min-field-of-view=${ifDefined(config.minFov)} | ||
max-field-of-view=${ifDefined(config.maxFov)} | ||
animation-name=${ifDefined(config.animationName)} | ||
>${childElements}</model-viewer> | ||
</div> | ||
</div>`; | ||
} | ||
|
||
async ping() { | ||
await fetch(this.mobilePingUrl, { | ||
method: 'POST', | ||
body: JSON.stringify({isPing: true}), | ||
}) | ||
.then(response => { | ||
console.log('Success:', response); | ||
}) | ||
.catch((error) => { | ||
console.log('Error:', error); | ||
throw new Error(`Failed to post: ${this.mobilePingUrl}`); | ||
}); | ||
} | ||
|
||
// (Overriding default) Tell editor session that it is ready for data. | ||
// @ts-ignore changedProperties is unused | ||
firstUpdated(changedProperties: any) { | ||
this.ping(); | ||
this.triggerFetchLoop(); | ||
} | ||
} | ||
|
||
declare global { | ||
interface HTMLElementTagNameMap { | ||
'mobile-view': MobileView; | ||
} | ||
} |
Oops, something went wrong.