| scripts, | scripts, | ||||
| scripts.map(s=>s.textContent ? 'js' : s.split('.').pop()), // title | scripts.map(s=>s.textContent ? 'js' : s.split('.').pop()), // title | ||||
| scripts.map(s=>(typeof s === 'string' && s.endsWith('.js')) ? s : 'https://github.com/repalash/threepipe/tree/master/examples/'+ window.location.pathname.split('/examples/').pop().replace('index.html', '')+(s.textContent ? 'index.html' : s)), // todo: github link | scripts.map(s=>(typeof s === 'string' && s.endsWith('.js')) ? s : 'https://github.com/repalash/threepipe/tree/master/examples/'+ window.location.pathname.split('/examples/').pop().replace('index.html', '')+(s.textContent ? 'index.html' : s)), // todo: github link | ||||
| (c) => replaceImports(c).replaceAll(` from '../`, ` from 'https://threepipe.org/examples/`), | |||||
| (c) => '// Threepipe example: ' + window.location.href + '\n' + replaceImports(c).replaceAll(` from '../`, ` from 'https://threepipe.org/examples/`), | |||||
| { | { | ||||
| title: 'ThreePipe: ' + document.title, | title: 'ThreePipe: ' + document.title, | ||||
| css, | css, |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Fullscreen Plugin</title> | |||||
| <!-- Import maps polyfill --> | |||||
| <!-- Remove this when import maps will be widely supported --> | |||||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||||
| <script type="importmap"> | |||||
| { | |||||
| "imports": { | |||||
| "threepipe": "./../../dist/index.mjs", | |||||
| "uiconfig-tweakpane": "https://unpkg.com/uiconfig-tweakpane@latest/dist/index.mjs" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style id="example-style"> | |||||
| html, body, #canvas-container, #mcanvas { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="canvas-container"> | |||||
| <canvas id="mcanvas"></canvas> | |||||
| </div> | |||||
| </body> |
| import {_testFinish, FullScreenPlugin, IObject3D, ThreeViewer, TweakpaneUiPlugin} from 'threepipe' | |||||
| import {createSimpleButtons} from '../examples-utils/simple-bottom-buttons.js' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| }) | |||||
| const fullScreenPlugin = viewer.addPluginSync(new FullScreenPlugin()) | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf') | |||||
| createSimpleButtons({ | |||||
| ['Enter/Exit fullscreen']: () => { | |||||
| if (fullScreenPlugin.isFullScreen()) fullScreenPlugin.exit() | |||||
| else fullScreenPlugin.enter(document.body) // parameter is optional, if not specified, the viewer canvas will be used | |||||
| // or just use | |||||
| // fullScreenPlugin.toggle(document.body) | |||||
| }, | |||||
| }) | |||||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||||
| ui.setupPluginUi(FullScreenPlugin) | |||||
| } | |||||
| init().then(_testFinish) |
| <h2 class="category">Interaction</h2> | <h2 class="category">Interaction</h2> | ||||
| <ul> | <ul> | ||||
| <li><a href="./dropzone-plugin/">Dropzone (Drag & Drop) Plugin </a></li> | <li><a href="./dropzone-plugin/">Dropzone (Drag & Drop) Plugin </a></li> | ||||
| <li><a href="./fullscreen-plugin/">FullScreen Plugin </a></li> | |||||
| </ul> | </ul> | ||||
| <h2 class="category">UI Config</h2> | <h2 class="category">UI Config</h2> | ||||
| <ul> | <ul> |
| { | { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.7", | |||||
| "version": "0.0.8-dev", | |||||
| "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | ||||
| "main": "src/index.ts", | "main": "src/index.ts", | ||||
| "module": "dist/index.mjs", | "module": "dist/index.mjs", |
| // interaction | // interaction | ||||
| export {DropzonePlugin, type DropzonePluginOptions} from './interaction/DropzonePlugin' | export {DropzonePlugin, type DropzonePluginOptions} from './interaction/DropzonePlugin' | ||||
| export {FullScreenPlugin} from './interaction/FullScreenPlugin' | |||||
| // import | // import | ||||
| export {Rhino3dmLoadPlugin} from './import/Rhino3dmLoadPlugin' | export {Rhino3dmLoadPlugin} from './import/Rhino3dmLoadPlugin' |
| import {uiButton, uiFolderContainer} from 'uiconfig.js' | |||||
| import {AViewerPluginSync} from '../../viewer' | |||||
| /** | |||||
| * A simple plugin that provides functions to enter, exit, toggle full screen mode and check if the viewer is in full screen mode. | |||||
| * Implementation from: | |||||
| * https://stackoverflow.com/questions/50568474/how-to-enter-fullscreen-in-three-js | |||||
| * @todo: try out some lib like https://github.com/sindresorhus/screenfull for proper cross browser support | |||||
| */ | |||||
| @uiFolderContainer('Full Screen') | |||||
| export class FullScreenPlugin extends AViewerPluginSync<'enter'|'exit'> { | |||||
| public static readonly PluginType = 'FullScreenPlugin' | |||||
| toJSON: any = undefined | |||||
| enabled = true | |||||
| constructor() { | |||||
| super() | |||||
| this.enter = this.enter.bind(this) | |||||
| this.exit = this.exit.bind(this) | |||||
| } | |||||
| private _lastSize = ['100%', '100%'] | |||||
| private _lastFsElement: any = null | |||||
| private _fsChangeHandler = (_: Event) => { | |||||
| if (this.isFullScreen()) { | |||||
| /* Run code when going to fs mode */ | |||||
| this.dispatchEvent({type: 'enter'}) | |||||
| } else { | |||||
| /* Run code when going back from fs mode */ | |||||
| const elem = this._lastFsElement || this._viewer?.canvas | |||||
| if (elem) { | |||||
| elem.style.width = this._lastSize[0] | |||||
| elem.style.height = this._lastSize[1] | |||||
| } | |||||
| document.removeEventListener('webkitfullscreenchange', this._fsChangeHandler, false) | |||||
| document.removeEventListener('mozfullscreenchange', this._fsChangeHandler, false) | |||||
| document.removeEventListener('fullscreenchange', this._fsChangeHandler, false) | |||||
| document.removeEventListener('MSFullscreenChange', this._fsChangeHandler, false) | |||||
| this.dispatchEvent({type: 'exit'}) | |||||
| } | |||||
| } | |||||
| @uiButton('Enter FullScreen') | |||||
| async enter(element?: HTMLElement): Promise<void> { | |||||
| if (this.isFullScreen()) return | |||||
| const elem = element || this._viewer?.canvas as any | |||||
| if (!elem) return | |||||
| this._lastFsElement = elem | |||||
| if (document.addEventListener) { | |||||
| document.addEventListener('webkitfullscreenchange', this._fsChangeHandler, false) | |||||
| document.addEventListener('mozfullscreenchange', this._fsChangeHandler, false) | |||||
| document.addEventListener('fullscreenchange', this._fsChangeHandler, false) | |||||
| document.addEventListener('MSFullscreenChange', this._fsChangeHandler, false) | |||||
| } | |||||
| this._lastSize = [elem.style.width, elem.style.height] | |||||
| elem.style.width = '100%' | |||||
| elem.style.height = '100%' | |||||
| if (elem.requestFullscreen) { | |||||
| return elem.requestFullscreen() | |||||
| } else if (elem.mozRequestFullScreen) { /* Firefox */ | |||||
| return elem.mozRequestFullScreen() | |||||
| } else if (elem.webkitRequestFullscreen) { /* Chrome, Safari & Opera */ | |||||
| return elem.webkitRequestFullscreen() | |||||
| } else if (elem.msRequestFullscreen) { /* IE/Edge */ | |||||
| return elem.msRequestFullscreen() | |||||
| } | |||||
| } | |||||
| @uiButton('Exit FullScreen') | |||||
| async exit(): Promise<void> { | |||||
| if (document.exitFullscreen) { | |||||
| return document.exitFullscreen() | |||||
| } else if ((document as any).mozCancelFullScreen) { /* Firefox */ | |||||
| return (document as any).mozCancelFullScreen() | |||||
| } else if ((document as any).webkitExitFullscreen) { /* Chrome, Safari and Opera */ | |||||
| return (document as any).webkitExitFullscreen() | |||||
| } else if ((document as any).msExitFullscreen) { /* IE/Edge */ | |||||
| return (document as any).msExitFullscreen() | |||||
| } | |||||
| } | |||||
| @uiButton('Toggle FullScreen') | |||||
| async toggle(elem: HTMLElement): Promise<void> { | |||||
| if (this.isFullScreen()) { | |||||
| return this.exit() | |||||
| } else { | |||||
| return this.enter(elem) | |||||
| } | |||||
| } | |||||
| isFullScreen() { | |||||
| return (document as any).webkitIsFullScreen || | |||||
| (document as any).mozFullScreen || | |||||
| (document as any).msFullscreenElement !== undefined | |||||
| } | |||||
| } |
| } | } | ||||
| this._plugins.push(p) | this._plugins.push(p) | ||||
| if (p.uiConfig && p.uiConfig.hidden === undefined) p.uiConfig.hidden = false // todo; this is a hack for now | if (p.uiConfig && p.uiConfig.hidden === undefined) p.uiConfig.hidden = false // todo; this is a hack for now | ||||
| this.appendChild(p) | |||||
| const ui = p.uiConfig | const ui = p.uiConfig | ||||
| this.appendChild(ui) | |||||
| this._setupPluginSerializationContext(ui, p) | this._setupPluginSerializationContext(ui, p) | ||||
| return ui | return ui | ||||
| } | } |
| import {Event, EventDispatcher} from 'three' | import {Event, EventDispatcher} from 'three' | ||||
| import {SerializationMetaType, ThreeSerialization} from '../utils' | import {SerializationMetaType, ThreeSerialization} from '../utils' | ||||
| import {IViewerPlugin, IViewerPluginAsync} from './IViewerPlugin' | import {IViewerPlugin, IViewerPluginAsync} from './IViewerPlugin' | ||||
| import {UiObjectConfig} from 'uiconfig.js' | |||||
| /** | /** | ||||
| * Base Class for Viewer Plugins | * Base Class for Viewer Plugins | ||||
| * @category Viewer | * @category Viewer | ||||
| */ | */ | ||||
| export abstract class AViewerPlugin<T extends string, TViewer extends ThreeViewer = ThreeViewer, IsSync extends boolean = boolean> extends EventDispatcher<Event, T|'serialize'|'deserialize'> implements IViewerPlugin<TViewer, IsSync> { | |||||
| export abstract class AViewerPlugin<T extends string = string, TViewer extends ThreeViewer = ThreeViewer, IsSync extends boolean = boolean> extends EventDispatcher<Event, T|'serialize'|'deserialize'> implements IViewerPlugin<TViewer, IsSync> { | |||||
| declare ['constructor']: typeof AViewerPlugin | declare ['constructor']: typeof AViewerPlugin | ||||
| public static readonly PluginType: string = 'AViewerPlugin' | public static readonly PluginType: string = 'AViewerPlugin' | ||||
| protected _dirty = false | protected _dirty = false | ||||
| // uiConfig?: UiObjectConfig = undefined // todo: this should work when uncommented, remove all get uiConfig and do it properly | |||||
| uiConfig?: UiObjectConfig = undefined // todo: this should work when uncommented, remove all get uiConfig and do it properly | |||||
| protected _viewer?: TViewer | protected _viewer?: TViewer | ||||
| import {IUiConfigContainer} from 'uiconfig.js' | |||||
| import {IUiConfigContainer, UiConfigContainer} from 'uiconfig.js' | |||||
| import {Class, IDisposable, IJSONSerializable} from 'ts-browser-helpers' | import {Class, IDisposable, IJSONSerializable} from 'ts-browser-helpers' | ||||
| import {SerializationMetaType} from '../utils' | import {SerializationMetaType} from '../utils' | ||||
| import {ISerializedConfig, ThreeViewer} from './ThreeViewer' | import {ISerializedConfig, ThreeViewer} from './ThreeViewer' | ||||
| * @category Viewer | * @category Viewer | ||||
| */ | */ | ||||
| export interface IViewerPlugin<TViewer extends ThreeViewer = ThreeViewer, IsSync extends boolean = boolean> | export interface IViewerPlugin<TViewer extends ThreeViewer = ThreeViewer, IsSync extends boolean = boolean> | ||||
| extends IUiConfigContainer, Partial<IJSONSerializable<ISerializedConfig, SerializationMetaType>>, IDisposable { | |||||
| extends IUiConfigContainer, Partial<IJSONSerializable<ISerializedConfig, SerializationMetaType>>, IDisposable, Partial<UiConfigContainer> { | |||||
| // all classes must have this static property with a unique identifier value for this plugin | // all classes must have this static property with a unique identifier value for this plugin | ||||
| constructor: { | constructor: { | ||||
| PluginType: string | PluginType: string |