| @@ -17,7 +17,7 @@ setupCodePreview( | |||
| scripts, | |||
| 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 | |||
| (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, | |||
| css, | |||
| @@ -0,0 +1,35 @@ | |||
| <!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> | |||
| @@ -0,0 +1,31 @@ | |||
| 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) | |||
| @@ -234,6 +234,7 @@ | |||
| <h2 class="category">Interaction</h2> | |||
| <ul> | |||
| <li><a href="./dropzone-plugin/">Dropzone (Drag & Drop) Plugin </a></li> | |||
| <li><a href="./fullscreen-plugin/">FullScreen Plugin </a></li> | |||
| </ul> | |||
| <h2 class="category">UI Config</h2> | |||
| <ul> | |||
| @@ -1,6 +1,6 @@ | |||
| { | |||
| "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.", | |||
| "main": "src/index.ts", | |||
| "module": "dist/index.mjs", | |||
| @@ -13,6 +13,7 @@ export {TweakpaneUiPlugin} from './ui/tweakpane/TweakpaneUiPlugin' | |||
| // interaction | |||
| export {DropzonePlugin, type DropzonePluginOptions} from './interaction/DropzonePlugin' | |||
| export {FullScreenPlugin} from './interaction/FullScreenPlugin' | |||
| // import | |||
| export {Rhino3dmLoadPlugin} from './import/Rhino3dmLoadPlugin' | |||
| @@ -0,0 +1,106 @@ | |||
| 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 | |||
| } | |||
| } | |||
| @@ -55,8 +55,8 @@ export class TweakpaneUiPlugin extends UiConfigRendererTweakpane implements IVie | |||
| } | |||
| this._plugins.push(p) | |||
| 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 | |||
| this.appendChild(ui) | |||
| this._setupPluginSerializationContext(ui, p) | |||
| return ui | |||
| } | |||
| @@ -2,17 +2,18 @@ import {ISerializedConfig, ThreeViewer} from './ThreeViewer' | |||
| import {Event, EventDispatcher} from 'three' | |||
| import {SerializationMetaType, ThreeSerialization} from '../utils' | |||
| import {IViewerPlugin, IViewerPluginAsync} from './IViewerPlugin' | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| /** | |||
| * Base Class for Viewer Plugins | |||
| * @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 | |||
| public static readonly PluginType: string = 'AViewerPlugin' | |||
| 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 | |||
| @@ -1,4 +1,4 @@ | |||
| import {IUiConfigContainer} from 'uiconfig.js' | |||
| import {IUiConfigContainer, UiConfigContainer} from 'uiconfig.js' | |||
| import {Class, IDisposable, IJSONSerializable} from 'ts-browser-helpers' | |||
| import {SerializationMetaType} from '../utils' | |||
| import {ISerializedConfig, ThreeViewer} from './ThreeViewer' | |||
| @@ -8,7 +8,7 @@ import {ISerializedConfig, ThreeViewer} from './ThreeViewer' | |||
| * @category Viewer | |||
| */ | |||
| 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 | |||
| constructor: { | |||
| PluginType: string | |||