From 325c0608c5bfad7c4b7e65f47b3909d8d8a84fb0 Mon Sep 17 00:00:00 2001 From: mistic100 Date: Sat, 12 Feb 2022 17:49:43 +0100 Subject: [PATCH 01/17] doc: reorganize --- docs/.vuepress/config.js | 4 +- docs/.vuepress/public/_redirects | 2 + docs/guide/README.md | 2 +- docs/guide/adapters/README.md | 2 +- .../{tiles.md => equirectangular-tiles.md} | 0 docs/guide/adapters/equirectangular.md | 101 +++++++++++++++++- docs/guide/config.md | 2 +- docs/guide/cropped-panorama.md | 97 ----------------- docs/plugins/plugin-visible-range.md | 2 +- 9 files changed, 104 insertions(+), 108 deletions(-) create mode 100644 docs/.vuepress/public/_redirects rename docs/guide/adapters/{tiles.md => equirectangular-tiles.md} (100%) delete mode 100644 docs/guide/cropped-panorama.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index eae85b53a..a1e60be90 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -40,15 +40,13 @@ module.exports = { 'methods', 'events', 'navbar', - 'cropped-panorama', - 'migration-v3', { title : 'Adapters', path : '/guide/adapters/', collapsable: false, children : [ 'adapters/equirectangular', - 'adapters/tiles', + 'adapters/equirectangular-tiles', 'adapters/cubemap', ['adapters/cubemap-tiles', 'Cubemap tiles (NEW)'], ], diff --git a/docs/.vuepress/public/_redirects b/docs/.vuepress/public/_redirects new file mode 100644 index 000000000..24a31a601 --- /dev/null +++ b/docs/.vuepress/public/_redirects @@ -0,0 +1,2 @@ +/guide/adapters/tiles.html /guide/adapters/equirectangular-tiles.html +/guide/cropped-panorama.html /guide/adapters/equirectangular.html#cropped-panorama diff --git a/docs/guide/README.md b/docs/guide/README.md index 19bbbdcdf..4e0ea739e 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -92,7 +92,7 @@ const viewer = new Viewer({ The `panorama` must be an [equirectangular projection](https://en.wikipedia.org/wiki/Equirectangular_projection) of your photo. Other modes are supported through [adapters](./adapters/). ::: tip Cropped panoramas -If your image is not covering a full 360°×180° sphere, it will be deformed. You can fix it by providing [cropping data](./cropped-panorama.md). +If your image is not covering a full 360°×180° sphere, it will be deformed. You can fix it by providing [cropping data](./adapters/equirectangular.md#cropped-panorama). ::: ::: tip Caching diff --git a/docs/guide/adapters/README.md b/docs/guide/adapters/README.md index 0a01f8c8e..8c1585944 100644 --- a/docs/guide/adapters/README.md +++ b/docs/guide/adapters/README.md @@ -4,7 +4,7 @@ Adapters are small pieces of code responsible to load the panorama texture(s) in The supported adapters are: - [equirectangular](equirectangular.md): the default adapter, used to load full or partial equirectangular panoramas -- [equirectangular tiles](tiles.md): used to load tiled equirectangular panoramas +- [equirectangular tiles](equirectangular-tiles.md): used to load tiled equirectangular panoramas - [cubemap](cubemap.md): used to load cubemaps projections (six textures) - [cubemap tiles](cubemap-tiles.md): used to load tiled cubemap panoramas diff --git a/docs/guide/adapters/tiles.md b/docs/guide/adapters/equirectangular-tiles.md similarity index 100% rename from docs/guide/adapters/tiles.md rename to docs/guide/adapters/equirectangular-tiles.md diff --git a/docs/guide/adapters/equirectangular.md b/docs/guide/adapters/equirectangular.md index 59882f50b..6e3f78830 100644 --- a/docs/guide/adapters/equirectangular.md +++ b/docs/guide/adapters/equirectangular.md @@ -15,10 +15,6 @@ new PhotoSphereViewer.Viewer({ }); ``` -::: tip Cropped panoramas -If your image is not covering a full 360°×180° sphere, it will be deformed. You can fix it by providing [cropping data](../cropped-panorama.md). -::: - ## Configuration @@ -29,3 +25,100 @@ If your image is not covering a full 360°×180° sphere, it will be deformed. Y The number of faces of the sphere geometry used to display the panorama, higher values can reduce deformations on straight lines at the cost of performances. _Note: the actual number of faces is `resolution² / 2`._ + + +## Cropped panorama + +**Photo Sphere Viewer** supports cropped panorama given the appropriate configuration is provided. Cropped panoramas are not covering the whole 360°×180° sphere area but only a smaller portion. For example you might have a image covering 360° horizontally but only 90° vertically, or a semi sphere (180°×180°) + +These incomplete panoramas are handled in two ways by Photo Sphere viewer: + - Read XMP metadata directly from the file with `useXmpData` option (this is the default) + - Provide the `panoData` configuration object/function + +Use the [Playground](#playground) at the bottom of this page to find the best values for your panorama. + +### Theory + +In both case the data contains six important values: + - Full panorama width + - Full panorama height + - Cropped area width + - Cropped area height + - Cropped area left + - Cropped area right + +The `Full panorama width` / `Full panorama height` ratio must always be 2:1. `Cropped area width` and `Cropped area height` are the actual size of your image. `Cropped area left` and `Cropped area right` are used to define the cropped area position. + +The data can also contains angular values: + - Pose Heading + - Pose Pitch + - Pose Roll + +![XMP_pano_pixels](/assets/XMP_pano_pixels.png) + +More information on [Google documentation](https://developers.google.com/streetview/spherical-metadata). + + +### Provide cropping data + +#### With XMP + +If you created your panorama with a mobile phone or dedicated 360° camera, it should already contain the correct XMP data. Otherwise you can inject it yourself with tools like [exiftool](https://sno.phy.queensu.ca/~phil/exiftool/). + +The XMP payload is as follow: + +```xml + + + + + equirectangular + 6000 + 3000 + 4000 + 2000 + 1000 + 500 + 0 + 0 + 0 + + + + +``` + +To write the XMP data to an image file, paste it in a text file and use this command: + +```bash +exiftool -tagsfromfile data.xmp -all:all panorama.jpg +``` + +#### Manually + +You can also directly pass the values to Photo Sphere Viewer with the `panoData` parameter. + +```js +const viewer = new PhotoSphereViewer.Viewer({ + container: 'viewer', + panorama: 'path/to/panorama.jpg', + panoData: { + fullWidth: 6000, + fullHeight: 3000, + croppedWidth: 4000, + croppedHeight: 2000, + croppedX: 1000, + croppedY: 500, + poseHeading: 0, // 0 to 360 + posePitch: 0, // -90 to 90 + poseRoll: 0, // -180 to 180 + } +}); +``` + + +### Playground + +Use this demo to find the best values for your image. + + diff --git a/docs/guide/config.md b/docs/guide/config.md index 2990a99d9..e1f303cd3 100644 --- a/docs/guide/config.md +++ b/docs/guide/config.md @@ -201,7 +201,7 @@ Speed multiplicator for panorama zooms. Used for mouse wheel, touch pinch and na - type: `boolean` - default `true` -Read real image size from XMP data, must be kept `true` if the panorama has been cropped after shot. +Read real image size from XMP data, must be kept `true` if the panorama has been cropped after shot. This is used for [cropped panorama](./adapters/equirectangular.md#cropped-panorama). #### `panoData` - type: `object | function` diff --git a/docs/guide/cropped-panorama.md b/docs/guide/cropped-panorama.md deleted file mode 100644 index e1fb5089c..000000000 --- a/docs/guide/cropped-panorama.md +++ /dev/null @@ -1,97 +0,0 @@ -# Cropped panorama - -[[toc]] - -**Photo Sphere Viewer** supports cropped panorama given the appropriate configuration is provided. Cropped panoramas are not covering the whole 360°×180° sphere area but only a smaller portion. For example you might have a image covering 360° horizontally but only 90° vertically, or a semi sphere (180°×180°) - -These incomplete panoramas are handled in two ways by Photo Sphere viewer: - - Read XMP metadata directly from the file - - Provide the `panoData` configuration object - -Use the [Playground](#playground) at the bottom of this page to find the best values for your panorama. - -## Theory - -In both case the data contains six important values: - - Full panorama width - - Full panorama height - - Cropped area width - - Cropped area height - - Cropped area left - - Cropped area right - -The `Full panorama width` / `Full panorama height` ratio must always be 2:1. `Cropped area width` and `Cropped area height` are the actual size of your image. `Cropped area left` and `Cropped area right` are used to define the cropped area position. - -The data can also contains angular values: - - Pose Heading - - Pose Pitch - - Pose Roll - -![XMP_pano_pixels](/assets/XMP_pano_pixels.png) - -More information on [Google documentation](https://developers.google.com/streetview/spherical-metadata). - - -## Provide cropping data - -### With XMP - -If you created your panorama with a mobile phone or dedicated 360° camera, it should already contain the correct XMP data. Otherwise you can inject it yourself with tools like [exiftool](https://sno.phy.queensu.ca/~phil/exiftool/). - -The XMP payload is as follow: - -```xml - - - - - equirectangular - 6000 - 3000 - 4000 - 2000 - 1000 - 500 - 0 - 0 - 0 - - - - -``` - -To write the XMP data to an image file, paste it in a text file and use this command: - -```bash -exiftool -tagsfromfile data.xmp -all:all panorama.jpg -``` - -### Manually - -You can also directly pass the values to Photo Sphere Viewer with the `panoData` parameter. - -```js -var viewer = new PhotoSphereViewer.Viewer({ - container: 'viewer', - panorama: 'path/to/panorama.jpg', - panoData: { - fullWidth: 6000, - fullHeight: 3000, - croppedWidth: 4000, - croppedHeight: 2000, - croppedX: 1000, - croppedY: 500, - poseHeading: 0, // 0 to 360 - posePitch: 0, // -90 to 90 - poseRoll: 0, // -180 to 180 - } -}); -``` - - -## Playground - -Use this demo to find the best values for your image. - - diff --git a/docs/plugins/plugin-visible-range.md b/docs/plugins/plugin-visible-range.md index d6550310e..c9e9438ef 100644 --- a/docs/plugins/plugin-visible-range.md +++ b/docs/plugins/plugin-visible-range.md @@ -29,7 +29,7 @@ visibleRangePlugin.setLongitudeRange(['0deg', '180deg']); visibleRangePlugin.setLatitudeRange(null); ``` -Alternatively, if `usePanoData` is set to `true`, the visible range is limited to the [cropped panorama data](../guide/cropped-panorama.md#provide-cropping-data) provided to the viewer. +Alternatively, if `usePanoData` is set to `true`, the visible range is limited to the [cropped panorama data](../guide/adapters/equirectangular.md#cropped-panorama) provided to the viewer. ## Example From a0d9154525e2862dfc1b46448b3cc4baab423778 Mon Sep 17 00:00:00 2001 From: mistic100 Date: Sun, 13 Feb 2022 18:32:19 +0100 Subject: [PATCH 02/17] add `downloadUrl` option + hide download button when the source cannot be downloaded --- docs/guide/config.md | 6 ++++++ src/adapters/AbstractAdapter.js | 8 ++++++++ src/adapters/cubemap-tiles/index.js | 1 + src/adapters/cubemap/index.js | 1 + src/adapters/equirectangular-tiles/index.js | 1 + src/adapters/equirectangular/index.js | 1 + src/buttons/DownloadButton.js | 11 +++++++++-- src/data/config.js | 1 + types/Viewer.d.ts | 1 + 9 files changed, 29 insertions(+), 2 deletions(-) diff --git a/docs/guide/config.md b/docs/guide/config.md index e1f303cd3..f6cd5b590 100644 --- a/docs/guide/config.md +++ b/docs/guide/config.md @@ -43,6 +43,12 @@ List of enabled [plugins](../plugins/README.md). A text displayed in the navbar. If the navbar is disabled it will be shown anyway but with no button. HTML is allowed. +#### `downloadUrl` +- type: `string` +- default: `=panorama` for equirectangular panoramas + +Define the file which will be downloaded with the `download` button. This is particularly useful for adapters that use multiple files, like the CubemapAdapter or the EquirectangularTilesAdapter. + #### `size` - type: `{ width: integer, height: integer }` diff --git a/src/adapters/AbstractAdapter.js b/src/adapters/AbstractAdapter.js index 66715bf4e..608e4f112 100644 --- a/src/adapters/AbstractAdapter.js +++ b/src/adapters/AbstractAdapter.js @@ -35,6 +35,14 @@ export class AbstractAdapter { */ static supportsPreload = false; + /** + * @summary Indicates if the adapter supports panorama download natively + * @type {boolean} + * @readonly + * @static + */ + static supportsDownload = false; + /** * @param {PSV.Viewer} psv */ diff --git a/src/adapters/cubemap-tiles/index.js b/src/adapters/cubemap-tiles/index.js index d31321199..80315f398 100644 --- a/src/adapters/cubemap-tiles/index.js +++ b/src/adapters/cubemap-tiles/index.js @@ -69,6 +69,7 @@ export class CubemapTilesAdapter extends CubemapAdapter { static id = 'cubemap-tiles'; static supportsTransition = false; static supportsPreload = false; + static supportsDownload = false; /** * @param {PSV.Viewer} psv diff --git a/src/adapters/cubemap/index.js b/src/adapters/cubemap/index.js index 6b12f529b..30c32b0fb 100644 --- a/src/adapters/cubemap/index.js +++ b/src/adapters/cubemap/index.js @@ -34,6 +34,7 @@ export class CubemapAdapter extends AbstractAdapter { static id = 'cubemap'; static supportsTransition = true; static supportsPreload = true; + static supportsDownload = false; /** * @param {PSV.Viewer} psv diff --git a/src/adapters/equirectangular-tiles/index.js b/src/adapters/equirectangular-tiles/index.js index e4460216f..da8d5bc97 100644 --- a/src/adapters/equirectangular-tiles/index.js +++ b/src/adapters/equirectangular-tiles/index.js @@ -89,6 +89,7 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { static id = 'equirectangular-tiles'; static supportsTransition = false; static supportsPreload = false; + static supportsDownload = false; /** * @param {PSV.Viewer} psv diff --git a/src/adapters/equirectangular/index.js b/src/adapters/equirectangular/index.js index 6bf95d1e0..9473e3b5e 100644 --- a/src/adapters/equirectangular/index.js +++ b/src/adapters/equirectangular/index.js @@ -21,6 +21,7 @@ export class EquirectangularAdapter extends AbstractAdapter { static id = 'equirectangular'; static supportsTransition = true; static supportsPreload = true; + static supportsDownload = true; /** * @param {PSV.Viewer} psv diff --git a/src/buttons/DownloadButton.js b/src/buttons/DownloadButton.js index 53c015898..9d1a098aa 100644 --- a/src/buttons/DownloadButton.js +++ b/src/buttons/DownloadButton.js @@ -18,14 +18,21 @@ export class DownloadButton extends AbstractButton { super(navbar, 'psv-button--hover-scale psv-download-button', true); } + /** + * @override + */ + isSupported() { + return this.psv.adapter.constructor.supportsDownload || !!this.psv.config.downloadUrl; + } + /** * @override * @description Asks the browser to download the panorama source file */ onClick() { const link = document.createElement('a'); - link.href = this.psv.config.panorama; - link.download = this.psv.config.panorama; + link.href = this.psv.config.downloadUrl || this.psv.config.panorama; + link.download = link.href.split('/').pop(); this.psv.container.appendChild(link); link.click(); diff --git a/src/data/config.js b/src/data/config.js index 3fe4b54ea..3337b533a 100644 --- a/src/data/config.js +++ b/src/data/config.js @@ -17,6 +17,7 @@ export const DEFAULTS = { adapter : null, plugins : [], caption : null, + downloadUrl : null, loadingImg : null, loadingTxt : 'Loading...', size : null, diff --git a/types/Viewer.d.ts b/types/Viewer.d.ts index 270e46a8f..458f2552d 100644 --- a/types/Viewer.d.ts +++ b/types/Viewer.d.ts @@ -33,6 +33,7 @@ export type ViewerOptions = { panorama?: any; adapter?: AdapterConstructor | [AdapterConstructor, any]; caption?: string; + downloadUrl?: string; loadingImg?: string; loadingTxt?: string; size?: Size; From a914dd000143bd8a097ef5a9cda3514b765446e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 11:04:31 +0100 Subject: [PATCH 03/17] Bump axios from 0.25.0 to 0.26.0 (#641) Bumps [axios](https://github.com/axios/axios) from 0.25.0 to 0.26.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v0.25.0...v0.26.0) --- updated-dependencies: - dependency-name: axios dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac9977db7..831c3dbcd 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@vuepress/plugin-back-to-top": "^1.8.2", "@vuepress/plugin-google-analytics": "^1.8.2", "autoprefixer": "^10.3.3", - "axios": "^0.25.0", + "axios": "^0.26.0", "cpx2": "^4.1.2", "date-fns": "^2.23.0", "eslint": "^8.2.0", From e30a3a5b80d5e8e86c0a075d0bb35f3825ebeb30 Mon Sep 17 00:00:00 2001 From: Damien Sorel Date: Tue, 15 Feb 2022 13:34:52 +0100 Subject: [PATCH 04/17] doc: SCSS variables reference --- docs/.vuepress/config.js | 1 + docs/guide/style.md | 127 +++++++++++++++++++++++++++++++++++ src/styles/_vars.scss | 10 ++- src/styles/notification.scss | 4 +- src/styles/panel.scss | 2 +- 5 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 docs/guide/style.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index a1e60be90..b80af97e9 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -40,6 +40,7 @@ module.exports = { 'methods', 'events', 'navbar', + 'style', { title : 'Adapters', path : '/guide/adapters/', diff --git a/docs/guide/style.md b/docs/guide/style.md new file mode 100644 index 000000000..a7ae941bf --- /dev/null +++ b/docs/guide/style.md @@ -0,0 +1,127 @@ +# Style + +Photo Sphere Viewer comes with a default darkish theme. You can customize it by building yourself the stylesheet from the SCSS source and some variables overrides. + +```scss +// overrides +$psv-loader-color: rgba(0, 0, 0, 0.5); +$psv-loader-width: 100px; +.... + +// main stylesheet +@import '~photo-sphere-viewer/src/styles/index.scss'; + +// plugins stylesheets +@import '~photo-sphere-viewer/src/plugins/markers/style.scss'; +@import '~photo-sphere-viewer/src/plugins/virtual-tour/style.scss'; +.... +``` + +## Global + +| variable | default | description | +|---|---|---| +| $psv-main-background | radial-gradient(...) | Background of the viewer, visible when no panorama is set. | + +## Loader + +| variable | default | description | +|---|---|---| +| $psv-loader-color | rgba(61, 61, 61, .7) | Color of the loader bar | +| $psv-loader-width | 150px | Size of the loader | +| $psv-loader-tickness | 10px | Thickness of the loader bar | +| $psv-loader-font | 14px sans-serif | Font of the loading text | + +## Navbar + +| variable | default | description | +|---|---|---| +| $psv-navbar-height | 40px | Height of the navbar | +| $psv-navbar-background | rgba(61, 61, 61, .5) | Background color of the navbar | +| $psv-caption-font | 16px sans-serif | Font of the caption | +| $psv-caption-color | rgba(255, 255, 255, .7) | Text color of the caption | + +#### Buttons + +| variable | default | description | +|---|---|---| +| $psv-buttons-height | 20px | Inner height of the buttons | +| $psv-buttons-color | rgba(255, 255, 255, .7 | Icon color of the buttons | +| $psv-buttons-background | transparent | Background color of the buttons | +| $psv-buttons-active-background | rgba(255, 255, 255, .2) | Background color of the buttons when active | +| $psv-buttons-disabled-opacity | .5 | Opacity of disabled buttons | +| $psv-buttons-hover-scale | 1.2 | Scale applied to buttons on mouse hover | +| $psv-buttons-hover-scale-delay | .2s | Duration of the scale animation | + +#### Zoom range + +| variable | default | description | +|---|---|---| +| $psv-zoom-range-width | 80px | Size of the zoom range | +| $psv-zoom-range-tickness | 1px | Tickness of the zoom range | +| $psv-zoom-disk-diameter | 7px | Size of the zoom handle | +| $psv-zoom-range-media-min-width | 600px | Hides the zoom range on small screens | + + +## Tooltip + +| variable | default | description | +|---|---|---| +| $psv-tooltip-background-color | rgba(61, 61, 61, .8) | Background color of tooltips | +| $psv-tooltip-radius | 4px | Border radius of the tooltips | +| $psv-tooltip-padding | .5em 1em | Content padding of the tooltips | +| $psv-tooltip-arrow-size | 7px | Tooltips' arrow size | +| $psv-tooltip-max-width | 200px | Maximum width of the tooltips' content | +| $psv-tooltip-text-color | rgb(255, 255, 255) | Text color of the tooltips | +| $psv-tooltip-font | 14px sans-serif | Font of the tooltips | +| $psv-tooltip-text-shadow | 0 1px #000 | Shadow applied to the tooltips' text | +| $psv-tooltip-shadow-color | rgba(90, 90, 90, .7) | Color of the tooltips' shadow | +| $psv-tooltip-shadow-offset | 3px | Size of the tooltips' shadow | +| $psv-tooltip-animate-offset | 5px | Distance travelled on show animation | +| $psv-tooltip-animate-delay | 0.1s | Duration of the show animation | + +## Panel + +| variable | default | description | +|---|---|---| +| $psv-panel-background | rgba(10, 10, 10, .7) | Background of the panel | +| $psv-panel-width | 400px | Default width of the panel | +| $psv-panel-padding | 1em | Content padding of the panel | +| $psv-panel-text-color | rgb(220, 220, 220) | Default text color of the panel | +| $psv-panel-font | 16px sans-serif | Default font of the panel | + +#### Menu + +| variable | default | description | +|---|---|---| +| $psv-panel-title-font | 24px sans-serif | Font of the menu title | +| $psv-panel-title-icon-size | 24px | Size of the menu title icon | +| $psv-panel-title-margin | 24px | Margin of the menu title | +| $psv-panel-menu-item-height | 20px | Height of an item in the menu | +| $psv-panel-menu-item-padding | .5em 1em | Padding of an item in the menu | +| $psv-panel-menu-odd-background | rgba(255, 255, 255, .1) | Background color of odd items in the menu | +| $psv-panel-menu-even-background | transparent | Background color of even items in the menu | + +## Notification + +| variable | default | description | +|---|---|---| +| $psv-notification-position-from | -$psv-navbar-height | Position of the notification when hidden | +| $psv-notification-position-to | $psv-navbar-height * 2 | Position of the notification when visible | +| $psv-notification-animate-delay | 0.2s | Duration of the show animation | +| $psv-notification-background-color | $psv-tooltip-background-color | Background color of the notification | +| $psv-notification-radius | $psv-tooltip-radius | Border radius of the notification | +| $psv-notification-padding | $psv-tooltip-padding | Content padding of the notification | +| $psv-notification-font | $psv-tooltip-font | Font of the notification | +| $psv-notification-text-color | $psv-tooltip-text-color | Text color of the notification | + +## Overlay + +| variable | default | description | +|---|---|---| +| $psv-overlay-color | black | Text color of the overlay | +| $psv-overlay-opacity | .8 | Opacity of the overlay | +| $psv-overlay-font-family | sans-serif | Default font of the overlay | +| $psv-overlay-text-size | 30px | Main text size in the overlay | +| $psv-overlay-subtext-size | 20px | Secondary text size in the overlay | +| $psv-overlay-image-size | (portrait: 50vw,
landscape: 25vw) | Image/Icon size, depending on screen orientation | diff --git a/src/styles/_vars.scss b/src/styles/_vars.scss index 021306e31..25e2aadab 100644 --- a/src/styles/_vars.scss +++ b/src/styles/_vars.scss @@ -18,7 +18,7 @@ $psv-caption-font: 16px sans-serif !default; $psv-caption-color: rgba(255, 255, 255, .7) !default; $psv-buttons-height: 20px !default; -$psv-buttons-padding: 10px !default; +$psv-buttons-padding: (($psv-navbar-height - $psv-buttons-height) * .5) !default; $psv-buttons-background: transparent !default; $psv-buttons-active-background: rgba(255, 255, 255, .2) !default; $psv-buttons-color: rgba(255, 255, 255, .7) !default; @@ -30,7 +30,6 @@ $psv-buttons-hover-scale-delay: 200ms !default; $psv-zoom-range-width: 80px !default; $psv-zoom-range-tickness: 1px !default; $psv-zoom-disk-diameter: 7px !default; -$psv-zoom-buttons-width: 16px !default; $psv-zoom-range-media-min-width: 600px !default; @@ -54,7 +53,7 @@ $psv-tooltip-shadow-offset: 3px !default; // the shadow is always at the opposit // *** PANEL *** $psv-panel-background: rgba(10, 10, 10, .7) !default; $psv-panel-text-color: rgb(220, 220, 220) !default; - +$psv-panel-font: 16px sans-serif !default; $psv-panel-width: 400px !default; $psv-panel-padding: 1em !default; @@ -66,8 +65,6 @@ $psv-panel-close-button-width: 24px !default; $psv-panel-close-button-background: $psv-panel-resizer-background !default; $psv-panel-close-button-color: #fff !default; -$psv-panel-font: 16px sans-serif !default; - $psv-panel-title-font: 24px sans-serif !default; $psv-panel-title-icon-size: 24px !default; $psv-panel-title-margin: 24px !default; @@ -80,7 +77,8 @@ $psv-panel-menu-even-background: transparent !default; // *** NOTIFICATION *** -$psv-notification-bottom: (from: - $psv-navbar-height, to: $psv-navbar-height * 2) !default; +$psv-notification-position-from: -$psv-navbar-height !default; +$psv-notification-position-to: $psv-navbar-height * 2 !default; $psv-notification-animate-delay: 200ms !default; $psv-notification-background-color: $psv-tooltip-background-color !default; $psv-notification-radius: $psv-tooltip-radius !default; diff --git a/src/styles/notification.scss b/src/styles/notification.scss index 55b255ebe..83f9e7658 100644 --- a/src/styles/notification.scss +++ b/src/styles/notification.scss @@ -4,7 +4,7 @@ .psv-notification { position: absolute; z-index: $psv-notification-zindex; - bottom: map.get($psv-notification-bottom, from); + bottom: $psv-notification-position-from; display: flex; justify-content: center; box-sizing: border-box; @@ -26,6 +26,6 @@ &--visible { opacity: 100; - bottom: map.get($psv-notification-bottom, to); + bottom: $psv-notification-position-to; } } diff --git a/src/styles/panel.scss b/src/styles/panel.scss index 2a4291fec..2f5465f53 100644 --- a/src/styles/panel.scss +++ b/src/styles/panel.scss @@ -36,7 +36,7 @@ margin-left: $psv-panel-resizer-width; .psv--has-navbar & { - height: calc(100% - #{$psv-buttons-height + 2 * $psv-buttons-padding}); + height: calc(100% - #{$psv-navbar-height}); } &-close-button { From 25d07b8ffadc28acce2d5fb603abd40a620ff733 Mon Sep 17 00:00:00 2001 From: Damien Sorel Date: Wed, 16 Feb 2022 13:24:14 +0100 Subject: [PATCH 05/17] markers/virtual tour: remove options for buttons --- docs/.vuepress/components/Playground.vue | 70 +++++++++++---------- docs/plugins/plugin-gyroscope.md | 12 ++-- docs/plugins/plugin-markers.md | 51 ++++++--------- docs/plugins/plugin-settings.md | 12 ++-- docs/plugins/plugin-stereo.md | 12 ++-- docs/plugins/plugin-virtual-tour.md | 18 +++--- example/plugin-markers.html | 2 - src/plugins/markers/MarkersButton.js | 2 +- src/plugins/markers/MarkersListButton.js | 2 +- src/plugins/markers/index.js | 17 +++-- src/plugins/virtual-tour/NodesListButton.js | 2 +- src/plugins/virtual-tour/index.js | 10 ++- 12 files changed, 107 insertions(+), 103 deletions(-) diff --git a/docs/.vuepress/components/Playground.vue b/docs/.vuepress/components/Playground.vue index 12c259e82..0e6bb537c 100644 --- a/docs/.vuepress/components/Playground.vue +++ b/docs/.vuepress/components/Playground.vue @@ -483,7 +483,6 @@ export default { data: () => ({ - psv : null, markers : null, file : null, imageData : null, @@ -493,7 +492,7 @@ roll: 0 }, options : { - ...omit(cloneDeep(DEFAULTS), ['panorama', 'panoData', 'container', 'plugins', 'navbar', 'loadingImg']), + ...omit(cloneDeep(DEFAULTS), ['panorama', 'panoData', 'sphereCorrection', 'container', 'plugins', 'navbar', 'loadingImg']), }, panoData : { fullWidth : null, @@ -585,43 +584,18 @@ this.oldOptions = cloneDeep(this.options); - this.applyOptions = debounce(() => { - try { - if (this.options.defaultZoomLvl !== this.oldOptions.defaultZoomLvl) { - this.psv.zoom(this.options.defaultZoomLvl); - } - else if (this.options.defaultLong !== this.oldOptions.defaultLong || this.options.defaultLat !== this.oldOptions.defaultLat) { - this.psv.rotate({ - longitude: this.options.defaultLong, - latitude : this.options.defaultLat, - }); - } - else { - Object.keys(this.options) - .some(optName => { - if (!isEqual(this.options[optName], this.oldOptions[optName])) { - this.psv.setOption(optName, this.options[optName]); - return true; - } - }); - } - } catch (e) { - // ignore parsing errors - } - this.oldOptions = cloneDeep(this.options); - }, 200); + this._applyOptions = debounce(() => this.applyOptions(), 200); this.psv = new Viewer({ container : 'viewer', loadingImg: 'https://photo-sphere-viewer.js.org/assets/photosphere-logo.gif', plugins : [ - [MarkersPlugin, { - hideButton: false, - listButton: false, - }], + [MarkersPlugin, {}], ], }); + this.applyNavbar(); + this.markers = this.psv.getPlugin(MarkersPlugin); this.psv.on('click', (e, data) => this.onClick(data)); @@ -638,7 +612,7 @@ options : { deep: true, handler() { - this.applyOptions(); + this._applyOptions(); }, }, sphereCorrection: { @@ -654,7 +628,7 @@ navbar : { deep: true, handler() { - this.psv.setOption('navbar', this.navbar.filter(i => i.enabled).map(i => i.code)); + this.applyNavbar(); }, }, markerForm : { @@ -726,6 +700,36 @@ } }, + applyOptions() { + try { + if (this.options.defaultZoomLvl !== this.oldOptions.defaultZoomLvl) { + this.psv.zoom(this.options.defaultZoomLvl); + } + else if (this.options.defaultLong !== this.oldOptions.defaultLong || this.options.defaultLat !== this.oldOptions.defaultLat) { + this.psv.rotate({ + longitude: this.options.defaultLong, + latitude : this.options.defaultLat, + }); + } + else { + Object.keys(this.options) + .some(optName => { + if (!isEqual(this.options[optName], this.oldOptions[optName])) { + this.psv.setOption(optName, this.options[optName]); + return true; + } + }); + } + } catch (e) { + // ignore parsing errors + } + this.oldOptions = cloneDeep(this.options); + }, + + applyNavbar() { + this.psv.setOption('navbar', this.navbar.filter(i => i.enabled).map(i => i.code)); + }, + newMarker(type) { this.cancelMarker(); this.markerForm.id = 'marker-' + Math.random().toString(36).slice(2); diff --git a/docs/plugins/plugin-gyroscope.md b/docs/plugins/plugin-gyroscope.md index 5528b2ef0..012da1fec 100644 --- a/docs/plugins/plugin-gyroscope.md +++ b/docs/plugins/plugin-gyroscope.md @@ -23,10 +23,6 @@ const viewer = new PhotoSphereViewer.Viewer({ There are known inconsistencies of orientation data accross devices. If the panorama is not displayed in the expected orientation, this plugin is not faulty. ::: -::: tip Custom navbar -The button is added to the default navbar configuration. If you use a [custom navbar](../guide/navbar.md) you will need to manually add the `'gyroscope'` button to the list. -::: - ## Configuration @@ -52,3 +48,11 @@ lang: { ``` _Note: this option is not part of the plugin but is merged with the main [`lang`](../guide/config.md#lang) object._ + + +## Buttons + +This plugin adds buttons to the default navbar: +- `gyroscope` allows to toggle the gyroscope control + +If you use a [custom navbar](../guide/navbar.md) you will need to manually add the buttons to the list. diff --git a/docs/plugins/plugin-markers.md b/docs/plugins/plugin-markers.md index 78d91aef4..dbf8795b2 100644 --- a/docs/plugins/plugin-markers.md +++ b/docs/plugins/plugin-markers.md @@ -25,7 +25,7 @@ Markers can be added at startup with the `markers` option or after load with the const viewer = new PhotoSphereViewer.Viewer({ plugins: [ [PhotoSphereViewer.MarkersPlugin, { - markers: [ + markers: [ { id: 'new-marker', longitude: '45deg', @@ -33,7 +33,7 @@ const viewer = new PhotoSphereViewer.Viewer({ image: 'assets/pin-red.png', }, ], - }], + }], ], }); @@ -100,7 +100,7 @@ One of these options is required. ``` ::: tip What is the difference between "image" and "imageLayer" ? -Both allows to display an image but the difference is in the rendering technique. +Both allows to display an image but the difference is in the rendering technique. And `image` marker is rendered flat above the viewer but and `imageLayer` is rendered inside the panorama itself, this allows for more natural movements and scaling. ::: @@ -120,20 +120,20 @@ Unique identifier of the marker. #### `x` & `y` or `latitude` & `longitude` (required for all but polygons/polylines) - type: `integer` or `double` -Position of the marker in **texture coordinates** (pixels) or **spherical coordinates** (radians). +Position of the marker in **texture coordinates** (pixels) or **spherical coordinates** (radians). _(This option is ignored for polygons and polylines)._ #### `width` & `height` (required for images, recommended for html) - type: `integer` -Size of the marker in pixels. +Size of the marker in pixels. _(This option is ignored for polygons and polylines)._ #### `scale` - type: `double[] | { zoom: double[], longitude: [] }` - default: no scalling -Configures the scale of the marker depending on the zoom level and/or the longitude offset. This aims to give a natural feeling to the size of the marker as the users zooms and moves. +Configures the scale of the marker depending on the zoom level and/or the longitude offset. This aims to give a natural feeling to the size of the marker as the users zooms and moves. _(This option is ignored for polygons, polylines and imageLayer)._ Scales depending on zoom level, the array contains `[scale at minimum zoom, scale at maximum zoom]` : @@ -166,13 +166,13 @@ scale: { #### `className` - type: `string` -CSS class(es) added to the marker element. +CSS class(es) added to the marker element. _(This option is ignored for imageLayer markers)._ #### `style` - type: `object` -CSS properties to set on the marker (background, border, etc.). +CSS properties to set on the marker (background, border, etc.). _(This option is ignored for imageLayer markers)._ ```js @@ -185,7 +185,7 @@ style: { #### `svgStyle` - type: `object` -SVG properties to set on the marker (fill, stroke, etc.). +SVG properties to set on the marker (fill, stroke, etc.). _(Only for polygons, polylines and svg markers)._ ```js @@ -219,7 +219,7 @@ And use it in your marker : `fill: 'url(#image)'`. - type: `string` - default: `'center center'` -Defines where the marker is placed toward its defined position. Any CSS position is valid like `bottom center` or `20% 80%`. +Defines where the marker is placed toward its defined position. Any CSS position is valid like `bottom center` or `20% 80%`. _(This option is ignored for polygons and polylines)._ #### `visible` @@ -246,7 +246,7 @@ tooltip: { // tooltip with custom position #### `listContent` - type: `string` -The name that appears in the list of markers. If not provided, the tooltip content will be used. +The name that appears in the list of markers. If not provided, the tooltip content will be used. #### `content` - type: `string` @@ -279,26 +279,6 @@ lang: { _Note: this option is not part of the plugin but is merged with the main [`lang`](../guide/config.md#lang) object._ -#### `hideButton` -- type: `boolean` -- default: `true` - -Adds a navbar button to hide/show all the markers. - -::: tip Custom navbar -The button is added to the default navbar configuration. If you use a [custom navbar](../guide/navbar.md) you will need to manually add the `'markers'` button to the list. -::: - -#### `listButton` -- type: `boolean` -- default: `true` - -Adds a navbar button to display the list of all markers. - -::: tip Custom navbar -The button is added to the default navbar configuration. If you use a [custom navbar](../guide/navbar.md) you will need to manually add the `'markersList'` button to the list. -::: - #### `clickEventOnMarker` - type: `boolean` - default: `false` @@ -381,3 +361,12 @@ Triggered when the user clicks on a marker. The `data` object indicates if the m #### `unselect-marker(marker)` Triggered when a marker was selected and the user clicks elsewhere. + + +## Buttons + +This plugin adds buttons to the default navbar: +- `markers` allows to hide/show all markers +- `markersList` allows to open a list of all markers on the left panel + +If you use a [custom navbar](../guide/navbar.md) you will need to manually add the buttons to the list. diff --git a/docs/plugins/plugin-settings.md b/docs/plugins/plugin-settings.md index a46956f65..10ee87db8 100644 --- a/docs/plugins/plugin-settings.md +++ b/docs/plugins/plugin-settings.md @@ -21,10 +21,6 @@ const viewer = new PhotoSphereViewer.Viewer({ }); ``` -::: tip Custom navbar -The button is added to the default navbar configuration. If you use a [custom navbar](../guide/navbar.md) you will need to manually add the `'settings'` button to the list. -::: - ## Example @@ -82,3 +78,11 @@ lang: { ``` _Note: this option is not part of the plugin but is merged with the main [`lang`](../guide/config.md#lang) object._ + + +## Buttons + +This plugin adds buttons to the default navbar: +- `settings` allows to open the settings panel + +If you use a [custom navbar](../guide/navbar.md) you will need to manually add the buttons to the list. diff --git a/docs/plugins/plugin-stereo.md b/docs/plugins/plugin-stereo.md index 5b6ea303f..5735f3430 100644 --- a/docs/plugins/plugin-stereo.md +++ b/docs/plugins/plugin-stereo.md @@ -22,10 +22,6 @@ const viewer = new PhotoSphereViewer.Viewer({ }); ``` -::: tip Custom navbar -The button is added to the default navbar configuration. If you use a [custom navbar](../guide/navbar.md) you will need to manually add the `'stereo'` button to the list. -::: - ## Configuration @@ -41,3 +37,11 @@ lang: { ``` _Note: this option is not part of the plugin but is merged with the main [`lang`](../guide/config.md#lang) object._ + + +## Buttons + +This plugin adds buttons to the default navbar: +- `stereo` allows to start the stereo view + +If you use a [custom navbar](../guide/navbar.md) you will need to manually add the buttons to the list. diff --git a/docs/plugins/plugin-virtual-tour.md b/docs/plugins/plugin-virtual-tour.md index 999906c71..30b02d7e8 100644 --- a/docs/plugins/plugin-virtual-tour.md +++ b/docs/plugins/plugin-virtual-tour.md @@ -227,16 +227,6 @@ Enable the preloading of linked nodes, can be a function that returns true or fa When a link is clicked, adds a panorama rotation to face it before actually changing the node. If `false` the viewer won't rotate at all and keep the current orientation. -#### `listButton` -- type: `boolean` -- default: `true` id client data mode - -Adds a navbar button to display the list of all nodes. - -::: tip Custom navbar -The button is added to the default navbar configuration. If you use a [custom navbar](../guide/navbar.md) you will need to manually add the `'nodesList'` button to the list. -::: - #### `linksOnCompass` - type: `boolean` - default: `true` if markers render mode @@ -328,3 +318,11 @@ virtualTourPlugin.on('node-changed', (e, nodeId, data) => { } }); ``` + + +## Buttons + +This plugin adds buttons to the default navbar: +- `nodesList` allows to open a list of all nodes on the left panel (client mode only) + +If you use a [custom navbar](../guide/navbar.md) you will need to manually add the buttons to the list. diff --git a/example/plugin-markers.html b/example/plugin-markers.html index 6a76d8d0d..199c2a5f4 100644 --- a/example/plugin-markers.html +++ b/example/plugin-markers.html @@ -106,8 +106,6 @@

Header Level 3

PhotoSphereViewer.GyroscopePlugin, PhotoSphereViewer.StereoPlugin, [PhotoSphereViewer.MarkersPlugin, { - hideButton: true, - listButton: true, markers : (() => { const a = []; diff --git a/src/plugins/markers/MarkersButton.js b/src/plugins/markers/MarkersButton.js index 806697e47..3d6408c10 100644 --- a/src/plugins/markers/MarkersButton.js +++ b/src/plugins/markers/MarkersButton.js @@ -49,7 +49,7 @@ export class MarkersButton extends AbstractButton { * @override */ isSupported() { - return this.plugin?.config.hideButton; + return !!this.plugin; } /** diff --git a/src/plugins/markers/MarkersListButton.js b/src/plugins/markers/MarkersListButton.js index dba807e2e..71ff661e3 100644 --- a/src/plugins/markers/MarkersListButton.js +++ b/src/plugins/markers/MarkersListButton.js @@ -45,7 +45,7 @@ export class MarkersListButton extends AbstractButton { * @override */ isSupported() { - return this.plugin?.config.listButton; + return !!this.plugin; } /** diff --git a/src/plugins/markers/index.js b/src/plugins/markers/index.js index eb72b561a..4d388a433 100644 --- a/src/plugins/markers/index.js +++ b/src/plugins/markers/index.js @@ -16,8 +16,6 @@ import './style.scss'; /** * @typedef {Object} PSV.plugins.MarkersPlugin.Options - * @property {boolean} [hideButton=true] - adds a button to show/hide the markers - * @property {boolean} [listButton=true] - adds a button to show the list of markers * @property {boolean} [clickEventOnMarker=false] If a `click` event is triggered on the viewer additionally to the `select-marker` event. * @property {PSV.plugins.MarkersPlugin.Properties[]} [markers] */ @@ -85,12 +83,15 @@ export class MarkersPlugin extends AbstractPlugin { * @type {PSV.plugins.MarkersPlugin.Options} */ this.config = { - hideButton : true, - listButton : true, clickEventOnMarker: false, ...options, }; + if (options?.listButton === false || options?.hideButton === false) { + utils.logWarn('MarkersPlugin: listButton and hideButton options are deprecated. ' + + 'Please define the global navbar options according to your needs.'); + } + /** * @member {HTMLElement} * @readonly @@ -939,12 +940,8 @@ export class MarkersPlugin extends AbstractPlugin { } } else { - if (this.config.hideButton) { - markersButton?.show(); - } - if (this.config.listButton) { - markersListButton?.show(); - } + markersButton?.show(); + markersListButton?.show(); if (this.psv.panel.isVisible(ID_PANEL_MARKERS_LIST)) { this.showMarkersList(); diff --git a/src/plugins/virtual-tour/NodesListButton.js b/src/plugins/virtual-tour/NodesListButton.js index 3584d93b1..1a8c2939a 100644 --- a/src/plugins/virtual-tour/NodesListButton.js +++ b/src/plugins/virtual-tour/NodesListButton.js @@ -43,7 +43,7 @@ export class NodesListButton extends AbstractButton { * @override */ isSupported() { - return this.plugin?.config.listButton; + return !!this.plugin && !this.plugin.isServerSide(); } /** diff --git a/src/plugins/virtual-tour/index.js b/src/plugins/virtual-tour/index.js index 23595d3de..22fd5f74d 100644 --- a/src/plugins/virtual-tour/index.js +++ b/src/plugins/virtual-tour/index.js @@ -93,7 +93,6 @@ import { bearing, distance, setMeshColor } from './utils'; * @property {string} [startNodeId] - id of the initial node, if not defined the first node will be used * @property {boolean|PSV.plugins.VirtualTourPlugin.Preload} [preload=false] - preload linked panoramas * @property {boolean|string|number} [rotateSpeed='20rpm'] - speed of rotation when clicking on a link, if 'false' the viewer won't rotate at all - * @property {boolean} [listButton] - adds a button to show the list of nodes, defaults to `true` only in client data mode * @property {boolean} [linksOnCompass] - if the Compass plugin is enabled, displays the links on the compass, defaults to `true` on in markers render mode * @property {PSV.plugins.MarkersPlugin.Properties} [markerStyle] - global marker style * @property {PSV.plugins.VirtualTourPlugin.ArrowStyle} [arrowStyle] - global arrow style @@ -168,7 +167,6 @@ export class VirtualTourPlugin extends AbstractPlugin { markerLatOffset: -0.1, arrowPosition : 'bottom', linksOnCompass : options?.renderMode === MODE_MARKERS, - listButton : options?.dataMode !== MODE_SERVER, ...options, markerStyle : { ...DEFAULT_MARKER, @@ -180,6 +178,14 @@ export class VirtualTourPlugin extends AbstractPlugin { }, }; + if (options?.listButton === false) { + utils.logWarn('VirtualTourPlugin: listButton option is deprecated. ' + + 'Please define the global navbar options according to your needs.'); + } + if (options?.listButton === true && this.config.dataMode === MODE_SERVER) { + utils.logWarn('VirtualTourPlugin: the list button is not supported in server mode.'); + } + /** * @type {PSV.plugins.MarkersPlugin} * @private From cfc4f439372931ae05bcd602c965fe1e4d7e6db1 Mon Sep 17 00:00:00 2001 From: Damien Sorel Date: Wed, 16 Feb 2022 13:41:51 +0100 Subject: [PATCH 06/17] add "caption" option to "setPanorama" method --- docs/guide/methods.md | 2 +- docs/plugins/plugin-virtual-tour.md | 2 +- src/Viewer.js | 7 +++++++ src/components/Navbar.js | 8 +------- src/index.js | 1 + src/plugins/markers/index.js | 4 ++-- src/plugins/virtual-tour/index.js | 10 +++------- types/models.d.ts | 3 ++- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/docs/guide/methods.md b/docs/guide/methods.md index e3ee1642a..50a909ba6 100644 --- a/docs/guide/methods.md +++ b/docs/guide/methods.md @@ -89,7 +89,7 @@ viewer.setOptions({ ### `setPanorama(panorama, options): Promise` -Change the panorama image with an optional transition animation (enabled by default). You can also set the new `sphereCorrection` and `panoData` if needed. +Change the panorama image with an optional transition animation (enabled by default). See all options on the . ```js viewer.setPanorama('image.jpg') diff --git a/docs/plugins/plugin-virtual-tour.md b/docs/plugins/plugin-virtual-tour.md index 30b02d7e8..22a7fe95f 100644 --- a/docs/plugins/plugin-virtual-tour.md +++ b/docs/plugins/plugin-virtual-tour.md @@ -176,7 +176,7 @@ Overrides the global style of the marker used to display the link. See global co - default: ```js lang: { - nodesList: Locations', + nodesList: 'Locations', } ``` diff --git a/src/Viewer.js b/src/Viewer.js index 04066b8b5..22a433d88 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -471,6 +471,9 @@ export class Viewer extends EventEmitter { if (options.showLoader === undefined) { options.showLoader = true; } + if (options.caption === undefined) { + options.caption = this.config.caption; + } const positionProvided = isExtendedPosition(options); const zoomProvided = 'zoom' in options; @@ -482,6 +485,7 @@ export class Viewer extends EventEmitter { this.hideError(); this.config.panorama = path; + this.config.caption = options.caption; const done = (err) => { this.loader.hide(); @@ -494,15 +498,18 @@ export class Viewer extends EventEmitter { return false; } else if (err) { + this.navbar.setCaption(''); this.showError(this.config.lang.loadError); console.error(err); return Promise.reject(err); } else { + this.navbar.setCaption(this.config.caption); return true; } }; + this.navbar.setCaption(`${this.config.loadingTxt || ''}`); if (options.showLoader || !this.prop.ready) { this.loader.show(); } diff --git a/src/components/Navbar.js b/src/components/Navbar.js index 889a88e61..9095791ca 100644 --- a/src/components/Navbar.js +++ b/src/components/Navbar.js @@ -142,16 +142,10 @@ export class Navbar extends AbstractComponent { /** * @summary Sets the bar caption * @param {string} html - * @throws {PSV.PSVError} when the caption element is not present */ setCaption(html) { const caption = this.getButton('caption', false); - - if (!caption) { - throw new PSVError('Cannot set caption, the navbar caption container is not initialized.'); - } - - caption.setCaption(html); + caption?.setCaption(html); } /** diff --git a/src/index.js b/src/index.js index 08bdf288a..d8e73c3cf 100644 --- a/src/index.js +++ b/src/index.js @@ -107,6 +107,7 @@ export { /** * @typedef {PSV.ExtendedPosition} PSV.PanoramaOptions * @summary Object defining panorama and animation options + * @property {string} [caption] - new navbar caption * @property {boolean|number} [transition=1500] - duration of the transition between all and new panorama * @property {boolean} [showLoader=true] - show the loader while loading the new panorama * @property {number} [zoom] - new zoom level between 0 and 100 diff --git a/src/plugins/markers/index.js b/src/plugins/markers/index.js index 4d388a433..d07661206 100644 --- a/src/plugins/markers/index.js +++ b/src/plugins/markers/index.js @@ -88,8 +88,8 @@ export class MarkersPlugin extends AbstractPlugin { }; if (options?.listButton === false || options?.hideButton === false) { - utils.logWarn('MarkersPlugin: listButton and hideButton options are deprecated. ' + - 'Please define the global navbar options according to your needs.'); + utils.logWarn('MarkersPlugin: listButton and hideButton options are deprecated. ' + + 'Please define the global navbar options according to your needs.'); } /** diff --git a/src/plugins/virtual-tour/index.js b/src/plugins/virtual-tour/index.js index 22fd5f74d..fe10ebb66 100644 --- a/src/plugins/virtual-tour/index.js +++ b/src/plugins/virtual-tour/index.js @@ -110,7 +110,6 @@ import { bearing, distance, setMeshColor } from './utils'; // add markers buttons DEFAULTS.lang[NodesListButton.id] = 'Locations'; -DEFAULTS.lang.loading = 'Loading...'; registerButton(NodesListButton, 'caption:left'); @@ -179,8 +178,8 @@ export class VirtualTourPlugin extends AbstractPlugin { }; if (options?.listButton === false) { - utils.logWarn('VirtualTourPlugin: listButton option is deprecated. ' + - 'Please define the global navbar options according to your needs.'); + utils.logWarn('VirtualTourPlugin: listButton option is deprecated. ' + + 'Please define the global navbar options according to your needs.'); } if (options?.listButton === true && this.config.dataMode === MODE_SERVER) { utils.logWarn('VirtualTourPlugin: the list button is not supported in server mode.'); @@ -424,8 +423,6 @@ export class VirtualTourPlugin extends AbstractPlugin { return Promise.reject(utils.getAbortError()); } - this.psv.navbar.setCaption(`${this.psv.config.lang.loading}`); - this.prop.currentNode = node; if (this.prop.currentTooltip) { @@ -442,6 +439,7 @@ export class VirtualTourPlugin extends AbstractPlugin { return Promise.all([ this.psv.setPanorama(node.panorama, { + caption : node.caption, panoData : node.panoData, sphereCorrection: node.sphereCorrection, }) @@ -471,8 +469,6 @@ export class VirtualTourPlugin extends AbstractPlugin { this.__renderLinks(node); this.__preload(node); - this.psv.navbar.setCaption(node.caption || this.psv.config.caption); - /** * @event node-changed * @memberof PSV.plugins.VirtualTourPlugin diff --git a/types/models.d.ts b/types/models.d.ts index 0ab07a259..8a35526b2 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -75,9 +75,10 @@ export type PanoDataProvider = (image: HTMLImageElement) => PanoData; * Object defining panorama and animation options */ export type PanoramaOptions = (ExtendedPosition | {}) & { - zoom?: number; + caption?: string; transition?: boolean | number; showLoader?: boolean; + zoom?: number; sphereCorrection?: SphereCorrection; panoData?: PanoData | PanoDataProvider; }; From b3fca51b892589988c6d7536b95b4db09a8461d1 Mon Sep 17 00:00:00 2001 From: mistic100 Date: Mon, 21 Feb 2022 21:25:30 +0100 Subject: [PATCH 07/17] Cleanup examples --- example/cubemap.html | 55 -------------- example/equirectangular.html | 12 +-- example/index.html | 67 +++++++++++++++++ example/plugin-gyroscope-stereo.html | 38 ++++++++++ example/plugin-markers-cubemap.html | 106 +++++++++++++++++++++++++++ example/plugin-visible-range.html | 1 - 6 files changed, 212 insertions(+), 67 deletions(-) create mode 100644 example/index.html create mode 100644 example/plugin-gyroscope-stereo.html create mode 100644 example/plugin-markers-cubemap.html diff --git a/example/cubemap.html b/example/cubemap.html index 6b0b63ae2..cc9050c12 100644 --- a/example/cubemap.html +++ b/example/cubemap.html @@ -15,10 +15,8 @@ - - diff --git a/example/equirectangular.html b/example/equirectangular.html index ea3a8338c..87b3d28e3 100644 --- a/example/equirectangular.html +++ b/example/equirectangular.html @@ -14,10 +14,7 @@ - - - diff --git a/example/index.html b/example/index.html new file mode 100644 index 000000000..3121b72c4 --- /dev/null +++ b/example/index.html @@ -0,0 +1,67 @@ + + + + + + PhotoSphereViewer - Demos + + + + + + +
+
+
+ + PhotoSphereViewer - Demos +
+
+ +
+
+ +
+ +
+
+
+
Misc
+
+ +
+
+
+
+ + + diff --git a/example/plugin-gyroscope-stereo.html b/example/plugin-gyroscope-stereo.html new file mode 100644 index 000000000..3d30f7c38 --- /dev/null +++ b/example/plugin-gyroscope-stereo.html @@ -0,0 +1,38 @@ + + + + + + PhotoSphereViewer - gyroscope & stereo demo + + + + + + +
+ + + + + + + + + + + diff --git a/example/plugin-markers-cubemap.html b/example/plugin-markers-cubemap.html new file mode 100644 index 000000000..e21e4e7b2 --- /dev/null +++ b/example/plugin-markers-cubemap.html @@ -0,0 +1,106 @@ + + + + + + PhotoSphereViewer - markers demo (cubemap) + + + + + + + +
+ + + + + + + + + + diff --git a/example/plugin-visible-range.html b/example/plugin-visible-range.html index 366901b5d..4208f711a 100644 --- a/example/plugin-visible-range.html +++ b/example/plugin-visible-range.html @@ -6,7 +6,6 @@ PhotoSphereViewer - visible range demo - From 3e7dbb444699a3feecc60d0dc61d813b7dfc38a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Feb 2022 12:50:12 +0100 Subject: [PATCH 08/17] Bump @rollup/plugin-replace from 3.1.0 to 4.0.0 (#643) Bumps [@rollup/plugin-replace](https://github.com/rollup/plugins/tree/HEAD/packages/replace) from 3.1.0 to 4.0.0. - [Release notes](https://github.com/rollup/plugins/releases) - [Changelog](https://github.com/rollup/plugins/blob/master/packages/replace/CHANGELOG.md) - [Commits](https://github.com/rollup/plugins/commits/url-v4.0.0/packages/replace) --- updated-dependencies: - dependency-name: "@rollup/plugin-replace" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 831c3dbcd..a7bacdf13 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@pixi/jsdoc-template": "^2.5.1", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-replace": "^3.0.0", + "@rollup/plugin-replace": "^4.0.0", "@vuepress/plugin-active-header-links": "^1.8.2", "@vuepress/plugin-back-to-top": "^1.8.2", "@vuepress/plugin-google-analytics": "^1.8.2", From ee07d948aa09bfd7949b1a09dc60db17c891c87e Mon Sep 17 00:00:00 2001 From: Damien Sorel Date: Wed, 23 Feb 2022 12:43:59 +0100 Subject: [PATCH 09/17] fix #642 tiles adapter: bad display when switching panorama --- src/adapters/cubemap-tiles/index.js | 24 ++++++++++----------- src/adapters/equirectangular-tiles/index.js | 22 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/adapters/cubemap-tiles/index.js b/src/adapters/cubemap-tiles/index.js index 80315f398..f21be9496 100644 --- a/src/adapters/cubemap-tiles/index.js +++ b/src/adapters/cubemap-tiles/index.js @@ -196,13 +196,6 @@ export class CubemapTilesAdapter extends CubemapAdapter { return Promise.reject(new PSVError('Panorama nbTiles must be power of 2.')); } - this.prop.tileSize = panorama.faceSize / panorama.nbTiles; - this.prop.facesByTile = CUBE_SEGMENTS / panorama.nbTiles; - - if (this.prop.geom) { - this.prop.geom.setAttribute('uv', this.prop.originalUvs.clone()); - } - if (panorama.baseUrl) { return super.loadTexture(panorama.baseUrl) .then(textureData => ({ @@ -240,18 +233,23 @@ export class CubemapTilesAdapter extends CubemapAdapter { * @override */ setTexture(mesh, textureData) { + const { panorama, texture } = textureData; + this.__cleanup(); - if (textureData.texture) { - for (let i = 0; i < 6; i++) { - const texture = textureData.texture[i]; + this.prop.tileSize = panorama.faceSize / panorama.nbTiles; + this.prop.facesByTile = CUBE_SEGMENTS / panorama.nbTiles; + this.prop.geom.setAttribute('uv', this.prop.originalUvs.clone()); + + if (texture) { + for (let i = 0; i < 6; i++) { if (this.config.flipTopBottom && (i === 2 || i === 3)) { - texture.center = new THREE.Vector2(0.5, 0.5); - texture.rotation = Math.PI; + texture[i].center = new THREE.Vector2(0.5, 0.5); + texture[i].rotation = Math.PI; } - const material = new THREE.MeshBasicMaterial({ map: texture }); + const material = new THREE.MeshBasicMaterial({ map: texture[i] }); for (let j = 0; j < NB_GROUPS_BY_FACE; j++) { this.materials.push(material); diff --git a/src/adapters/equirectangular-tiles/index.js b/src/adapters/equirectangular-tiles/index.js index da8d5bc97..45fcd8c42 100644 --- a/src/adapters/equirectangular-tiles/index.js +++ b/src/adapters/equirectangular-tiles/index.js @@ -235,15 +235,6 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { return Promise.reject(new PSVError('Panorama cols and rows must be powers of 2.')); } - this.prop.colSize = panorama.width / panorama.cols; - this.prop.rowSize = panorama.width / 2 / panorama.rows; - this.prop.facesByCol = this.SPHERE_SEGMENTS / panorama.cols; - this.prop.facesByRow = this.SPHERE_HORIZONTAL_SEGMENTS / panorama.rows; - - if (this.prop.geom) { - this.prop.geom.setAttribute('uv', this.prop.originalUvs.clone()); - } - const panoData = { fullWidth : panorama.width, fullHeight : panorama.width / 2, @@ -300,11 +291,20 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { * @override */ setTexture(mesh, textureData) { + const { panorama, texture } = textureData; + this.__cleanup(); + this.prop.colSize = panorama.width / panorama.cols; + this.prop.rowSize = panorama.width / 2 / panorama.rows; + this.prop.facesByCol = this.SPHERE_SEGMENTS / panorama.cols; + this.prop.facesByRow = this.SPHERE_HORIZONTAL_SEGMENTS / panorama.rows; + + this.prop.geom.setAttribute('uv', this.prop.originalUvs.clone()); + let material; - if (textureData.texture) { - material = new THREE.MeshBasicMaterial({ map: textureData.texture }); + if (texture) { + material = new THREE.MeshBasicMaterial({ map: texture }); } else { material = new THREE.MeshBasicMaterial({ opacity: 0, transparent: true }); From bf3d96008b8b80a3ba060ec574ca6fc03b4d246c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Feb 2022 12:45:01 +0100 Subject: [PATCH 10/17] Bump three from 0.137.5 to 0.138.0 (#645) Bumps [three](https://github.com/mrdoob/three.js) from 0.137.5 to 0.138.0. - [Release notes](https://github.com/mrdoob/three.js/releases) - [Commits](https://github.com/mrdoob/three.js/commits) --- updated-dependencies: - dependency-name: three dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a7bacdf13..1f643daf5 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "url": "git://github.com/mistic100/Photo-Sphere-Viewer.git" }, "dependencies": { - "three": "^0.137.0", + "three": "^0.138.0", "uevent": "^2.1.1" }, "devDependencies": { From 9d777116091cb28ea91ae787710aec76605a89c0 Mon Sep 17 00:00:00 2001 From: mistic100 Date: Sat, 26 Feb 2022 14:38:06 +0100 Subject: [PATCH 11/17] fix: glitch when changing panorama --- example/cubemap.html | 2 -- src/plugins/markers/Marker.js | 2 +- src/services/Renderer.js | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/example/cubemap.html b/example/cubemap.html index cc9050c12..9e80acaa8 100644 --- a/example/cubemap.html +++ b/example/cubemap.html @@ -63,8 +63,6 @@ return () => { i = 1 - i; - markers.clearMarkers(); - viewer.setPanorama(panos[i].url, { longitude: 0, latitude: 0, zoom: 50 }) .then(() => { viewer.navbar.setCaption(panos[i].desc); diff --git a/src/plugins/markers/Marker.js b/src/plugins/markers/Marker.js index 7b35714af..1f6f4ffd0 100644 --- a/src/plugins/markers/Marker.js +++ b/src/plugins/markers/Marker.js @@ -653,7 +653,7 @@ export class Marker { switch (this.type) { case MARKER_TYPES.imageLayer: if (!this.$el) { - const material = new THREE.MeshBasicMaterial({ transparent: true }); + const material = new THREE.MeshBasicMaterial({ transparent: true, depthTest: false }); const geometry = new THREE.PlaneGeometry(1, 1); const mesh = new THREE.Mesh(geometry, material); mesh.userData = { [MARKER_DATA]: this }; diff --git a/src/services/Renderer.js b/src/services/Renderer.js index 9e2f266a9..8d5b7ff79 100644 --- a/src/services/Renderer.js +++ b/src/services/Renderer.js @@ -24,7 +24,6 @@ export class Renderer extends AbstractService { * @protected */ this.renderer = new THREE.WebGLRenderer({ alpha: true }); - this.renderer.context.disable(this.renderer.context.DEPTH_TEST); this.renderer.setPixelRatio(SYSTEM.pixelRatio); this.renderer.domElement.className = 'psv-canvas'; From 747941c6f3a5300f18a95c670f5077fa204cead7 Mon Sep 17 00:00:00 2001 From: mistic100 Date: Sun, 27 Feb 2022 11:09:45 +0100 Subject: [PATCH 12/17] settings: add badge --- docs/plugins/plugin-settings.md | 29 +++++++++++- example/index.html | 1 + example/plugin-settings.html | 63 ++++++++++++++++++++++++++ src/plugins/settings/SettingsButton.js | 19 ++++++++ src/plugins/settings/constants.js | 18 ++++++++ src/plugins/settings/index.js | 36 ++++++++++++++- src/plugins/settings/style.scss | 15 ++++++ types/plugins/settings/index.d.ts | 11 +++++ 8 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 example/plugin-settings.html diff --git a/docs/plugins/plugin-settings.md b/docs/plugins/plugin-settings.md index 10ee87db8..d0e60c78d 100644 --- a/docs/plugins/plugin-settings.md +++ b/docs/plugins/plugin-settings.md @@ -57,8 +57,8 @@ settings.addSetting({ label : 'Options setting', type : 'options', options: () => ([ - { id: 'option-a', label: 'Option A' }, - { id: 'option-b', label: 'Option B' }, + { id: 'A', label: 'Option A' }, + { id: 'B', label: 'Option B' }, ]), current: () => currentOption, apply : (option) => currentOption = option, @@ -66,6 +66,18 @@ settings.addSetting({ ``` +## Button badge + +A setting can also have a `badge` function, which return value will be used as a badge on the settings button itself. **Only one setting can declare a badge.** + +```js +settings.addSetting({ + ..., + badge: () => currentOption, +}); +``` + + ## Configuration #### `lang` @@ -80,6 +92,19 @@ lang: { _Note: this option is not part of the plugin but is merged with the main [`lang`](../guide/config.md#lang) object._ +## Events + +#### `setting-changed(id, value)` + +Triggered when the resolution is changed. + +```js +settingsPlugin.on('resolution-changed', (e, id, value) => { + console.log(`${id}: ${value}`); +}); +``` + + ## Buttons This plugin adds buttons to the default navbar: diff --git a/example/index.html b/example/index.html index 3121b72c4..8dad0acee 100644 --- a/example/index.html +++ b/example/index.html @@ -45,6 +45,7 @@
Plugins
Markers Markers (cubemap) Resolution + Settings Virtual Tour Visible Range diff --git a/example/plugin-settings.html b/example/plugin-settings.html new file mode 100644 index 000000000..21497c73a --- /dev/null +++ b/example/plugin-settings.html @@ -0,0 +1,63 @@ + + + + + + PhotoSphereViewer - settings demo + + + + + + + +
+ + + + + + + + + diff --git a/src/plugins/settings/SettingsButton.js b/src/plugins/settings/SettingsButton.js index 0f6f0c605..a8fe16115 100644 --- a/src/plugins/settings/SettingsButton.js +++ b/src/plugins/settings/SettingsButton.js @@ -25,6 +25,16 @@ export class SettingsButton extends AbstractButton { */ this.plugin = this.psv.getPlugin('settings'); + /** + * @member {HTMLElement} + * @private + * @readonly + */ + this.badge = document.createElement('div'); + this.badge.className = 'psv-settings-badge'; + this.badge.style.display = 'none'; + this.container.appendChild(this.badge); + if (this.plugin) { this.psv.on(CONSTANTS.EVENTS.OPEN_PANEL, this); this.psv.on(CONSTANTS.EVENTS.CLOSE_PANEL, this); @@ -74,4 +84,13 @@ export class SettingsButton extends AbstractButton { this.plugin.toggleSettings(); } + /** + * @summary Changes the badge value + * @param {string} value + */ + setBadge(value) { + this.badge.innerText = value; + this.badge.style.display = value ? '' : 'none'; + } + } diff --git a/src/plugins/settings/constants.js b/src/plugins/settings/constants.js index 87fe9f7f2..c54dafeea 100644 --- a/src/plugins/settings/constants.js +++ b/src/plugins/settings/constants.js @@ -4,6 +4,24 @@ import icon from './settings.svg'; import switchOff from './switch-off.svg'; import switchOn from './switch-on.svg'; +/** + * @summary Available events + * @enum {string} + * @memberof PSV.plugins.ResolutionPlugin + * @constant + */ +export const EVENTS = { + /** + * @event setting-changed + * @memberof PSV.plugins.SettingsPlugin + * @summary Triggered when a setting is changed + * @param {string} settingId + * @param {any} value + */ + SETTING_CHANGED: 'setting-changed', +}; + + /** * @summary Panel identifier for settings content * @type {string} diff --git a/src/plugins/settings/index.js b/src/plugins/settings/index.js index 437308aa6..64a63a962 100644 --- a/src/plugins/settings/index.js +++ b/src/plugins/settings/index.js @@ -1,5 +1,12 @@ import { AbstractPlugin, DEFAULTS, PSVError, registerButton, utils } from '../..'; -import { ID_PANEL, SETTING_DATA, SETTING_OPTIONS_TEMPLATE, SETTINGS_TEMPLATE, SETTINGS_TEMPLATE_ } from './constants'; +import { + EVENTS, + ID_PANEL, + SETTING_DATA, + SETTING_OPTIONS_TEMPLATE, + SETTINGS_TEMPLATE, + SETTINGS_TEMPLATE_ +} from './constants'; import { SettingsButton } from './SettingsButton'; import './style.scss'; @@ -10,6 +17,7 @@ import './style.scss'; * @property {string} id - identifier of the setting * @property {string} label - label of the setting * @property {'options' | 'toggle'} type - type of the setting + * @property {function} [badge] - function which returns the value of the button badge */ /** @@ -42,6 +50,9 @@ DEFAULTS.lang[SettingsButton.id] = 'Settings'; registerButton(SettingsButton, 'fullscreen:left'); +export { EVENTS } from './constants'; + + /** * @summary Adds a button to access various settings. * @extends PSV.plugins.AbstractPlugin @@ -69,6 +80,9 @@ export class SettingsPlugin extends AbstractPlugin { */ init() { super.init(); + + // buttons are initialized just after plugins + setTimeout(() => this.updateBadge()); } /** @@ -95,11 +109,17 @@ export class SettingsPlugin extends AbstractPlugin { throw new PSVError('Unsupported setting type'); } + if (setting.badge && this.settings.some(s => s.badge)) { + utils.logWarn('More than one setting with a badge are declared, the result is unpredictable.'); + } + this.settings.push(setting); if (this.psv.panel.prop.contentId === ID_PANEL) { this.showSettings(); } + + this.updateBadge(); } /** @@ -122,6 +142,8 @@ export class SettingsPlugin extends AbstractPlugin { if (this.psv.panel.prop.contentId === ID_PANEL) { this.showSettings(); } + + this.updateBadge(); } } @@ -170,7 +192,9 @@ export class SettingsPlugin extends AbstractPlugin { switch (setting.type) { case 'toggle': setting.toggle(); + this.trigger(EVENTS.SETTING_CHANGED, setting.id, setting.active()); this.showSettings(); + this.updateBadge(); break; case 'options': @@ -213,10 +237,20 @@ export class SettingsPlugin extends AbstractPlugin { } else { setting.apply(optionId); + this.trigger(EVENTS.SETTING_CHANGED, setting.id, setting.current()); this.hideSettings(); + this.updateBadge(); } }, }); } + /** + * @summary Updates the badge in the button + */ + updateBadge() { + const value = this.settings.find(s => s.badge)?.badge(); + this.psv.navbar.getButton(SettingsButton.id, false)?.setBadge(value); + } + } diff --git a/src/plugins/settings/style.scss b/src/plugins/settings/style.scss index 4d5dd31d5..9e07518df 100644 --- a/src/plugins/settings/style.scss +++ b/src/plugins/settings/style.scss @@ -1,5 +1,9 @@ @import '../../styles/vars'; +$psv-settings-badge-font: 10px / .9 monospace !default; +$psv-settings-badge-background: #111 !default; +$psv-settings-badge-text-color: white !default; + .psv-settings-item { &-label { flex: 1; @@ -38,3 +42,14 @@ } } } + +.psv-settings-badge { + position: absolute; + top: 10%; + right: 10%; + border-radius: .2em; + padding: .2em; + background: $psv-settings-badge-background; + color: $psv-settings-badge-text-color; + font: $psv-settings-badge-font; +} diff --git a/types/plugins/settings/index.d.ts b/types/plugins/settings/index.d.ts index 9fbea11a4..3ff505a8e 100644 --- a/types/plugins/settings/index.d.ts +++ b/types/plugins/settings/index.d.ts @@ -1,3 +1,4 @@ +import { Event } from 'uevent'; import { AbstractPlugin, Viewer } from '../..'; /** @@ -6,6 +7,7 @@ import { AbstractPlugin, Viewer } from '../..'; export type BaseSetting = { id: string; label: string; + badge?: () => string; }; /** @@ -37,6 +39,10 @@ export type SettingOption = { export type Setting = OptionsSetting | ToggleSetting; +export const EVENTS: { + SETTING_CHANGED: 'setting-changed', +}; + /** * @summary Adds a button to access various settings. */ @@ -70,4 +76,9 @@ export class SettingsPlugin extends AbstractPlugin { */ showSettings(); + /** + * @summary Triggered when a setting is changed + */ + on(e: 'setting-changed', cb: (e: Event, settingId: string, value: any) => void): this; + } From 8a1cc2a91358e6594bf1c96afa02aff861c11ecd Mon Sep 17 00:00:00 2001 From: mistic100 Date: Sun, 27 Feb 2022 11:08:01 +0100 Subject: [PATCH 13/17] resolution: add badge --- docs/plugins/plugin-resolution.md | 19 +++++++++++++++++++ example/plugin-resolution.html | 5 +++-- src/plugins/resolution/index.js | 4 ++++ types/plugins/resolution/index.d.ts | 9 +++++---- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/docs/plugins/plugin-resolution.md b/docs/plugins/plugin-resolution.md index 599097ac9..9a596327e 100644 --- a/docs/plugins/plugin-resolution.md +++ b/docs/plugins/plugin-resolution.md @@ -52,6 +52,12 @@ The following example provides two resolutions for the panorama, "small" is load List of available resolutions. Each resolution consist of an object with the properties `id`, `label` and `panorama`. Cubemaps are supported. +#### `showBadge` +- type: `boolean` +- default: `true` + +Show the resolution id as a badge on the settings button. + #### `lang` - type: `object` - default: @@ -62,3 +68,16 @@ lang: { ``` _Note: this option is not part of the plugin but is merged with the main [`lang`](../guide/config.md#lang) object._ + + +## Events + +#### `resolution-changed(id)` + +Triggered when the resolution is changed. + +```js +resolutionPlugin.on('resolution-changed', (e, id) => { + console.log(`Current resolution: ${id}`); +}); +``` diff --git a/example/plugin-resolution.html b/example/plugin-resolution.html index b0599153d..269fea61c 100644 --- a/example/plugin-resolution.html +++ b/example/plugin-resolution.html @@ -28,14 +28,15 @@ plugins : [ PhotoSphereViewer.SettingsPlugin, [PhotoSphereViewer.ResolutionPlugin, { + showBadge : true, resolutions: [ { - id : 'small', + id : 'SD', label : 'Small', panorama: 'sphere_small.jpg', }, { - id : 'normal', + id : 'HD', label : 'Normal', panorama: 'sphere.jpg', }, diff --git a/src/plugins/resolution/index.js b/src/plugins/resolution/index.js index 205871da0..36bad4567 100644 --- a/src/plugins/resolution/index.js +++ b/src/plugins/resolution/index.js @@ -13,6 +13,7 @@ import { deepEqual } from './utils'; /** * @typedef {Object} PSV.plugins.ResolutionPlugin.Options * @property {PSV.plugins.ResolutionPlugin.Resolution[]} resolutions - list of available resolutions + * @property {boolean} [showBadge=true] - show the resolution id as a badge on the settings button */ @@ -76,6 +77,7 @@ export class ResolutionPlugin extends AbstractPlugin { * @type {PSV.plugins.ResolutionPlugin.Options} */ this.config = { + showBadge: true, ...options, }; } @@ -99,6 +101,7 @@ export class ResolutionPlugin extends AbstractPlugin { current: () => this.prop.resolution, options: () => this.__getSettingsOptions(), apply : resolution => this.setResolution(resolution), + badge : !this.config.showBadge ? null : () => this.prop.resolution, }); this.psv.on(CONSTANTS.EVENTS.PANORAMA_LOADED, this); @@ -177,6 +180,7 @@ export class ResolutionPlugin extends AbstractPlugin { const resolution = this.resolutions.find(r => deepEqual(this.psv.config.panorama, r.panorama)); if (this.prop.resolution !== resolution?.id) { this.prop.resolution = resolution?.id; + this.settings?.updateBadge(); this.trigger(EVENTS.RESOLUTION_CHANGED, this.prop.resolution); } } diff --git a/types/plugins/resolution/index.d.ts b/types/plugins/resolution/index.d.ts index 8bf38042a..5ce31703d 100644 --- a/types/plugins/resolution/index.d.ts +++ b/types/plugins/resolution/index.d.ts @@ -9,6 +9,11 @@ export type Resolution = { export type ResolutionPluginOptions = { resolutions: Resolution[]; + showBadge?: boolean; +}; + +export const EVENTS: { + RESOLUTION_CHANGED: 'resolution-changed', }; /** @@ -16,10 +21,6 @@ export type ResolutionPluginOptions = { */ export class ResolutionPlugin extends AbstractPlugin { - static EVENTS: { - RESOLUTION_CHANGED: 'resolution-changed', - }; - constructor(psv: Viewer, options: ResolutionPluginOptions); /** From 8d471c449bef61d2c5fb10c83882188adbee7b4c Mon Sep 17 00:00:00 2001 From: Damien Sorel Date: Thu, 3 Mar 2022 13:13:47 +0100 Subject: [PATCH 14/17] Close #646 marker: imageLayer orientation and opacity --- docs/plugins/plugin-markers.md | 16 +++++++++++++-- example/assets/target.png | Bin 0 -> 6362 bytes example/plugin-markers.html | 14 ++++++++++++- src/plugins/markers/Marker.js | 34 +++++++++++++++++++++++++++---- types/plugins/markers/index.d.ts | 2 ++ 5 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 example/assets/target.png diff --git a/docs/plugins/plugin-markers.md b/docs/plugins/plugin-markers.md index dbf8795b2..ab922fe6f 100644 --- a/docs/plugins/plugin-markers.md +++ b/docs/plugins/plugin-markers.md @@ -129,6 +129,12 @@ _(This option is ignored for polygons and polylines)._ Size of the marker in pixels. _(This option is ignored for polygons and polylines)._ +#### `orientation` (only for `imageLayer`) +- type: `'front' | 'horizontal' | 'vertical-left' | 'vertical-right'` +- default: `'front'` + +Applies a perspective on the image to make it look like placed on the floor or on a wall. + #### `scale` - type: `double[] | { zoom: double[], longitude: [] }` - default: no scalling @@ -163,17 +169,23 @@ scale: { } ``` +#### `opacity` +- type: `number` +- default: `1` + +Opacity of the marker. (Works for `imageLayer` too). + #### `className` - type: `string` CSS class(es) added to the marker element. -_(This option is ignored for imageLayer markers)._ +_(This option is ignored for `imageLayer` markers)._ #### `style` - type: `object` CSS properties to set on the marker (background, border, etc.). -_(This option is ignored for imageLayer markers)._ +_(This option is ignored for `imageLayer` markers)._ ```js style: { diff --git a/example/assets/target.png b/example/assets/target.png new file mode 100644 index 0000000000000000000000000000000000000000..5cc17283b244ae83a9bca6dafc2605836ba4af61 GIT binary patch literal 6362 zcmeHsRZ|=c)9fy;L4vzW7I#=&f=d=>@htA{kl+Le5+t}waCdk2Ad9;sxCTAX`#a7z zRa4Vleb+U8)lnL1^4J(;7ytkOTTww40ssL2gFpZp;(y1kLQm^|%nc$h4O|&z3;K_s zJ1gkB0RWiq|F=LudL{_~fC$h~(USX5eEYxoKL`GQbpTZi6YYP|afhgB0+5)M9HR3Z zduP}8&+ZPYYn4~g_NbJz@RAr+W-~Yyif+Ls7%2?=YwHQ2YqbnP?sj$fW%vJW#4v%IE!rrfd* zg%kH+Yir}l)fdg*l0`rG>0DmjUEbbaR~b~cy=n5>ic@Nods15A4BJ?rEcz7(hFBqr zh4@qAuho3oJx3u%2S-T)NcH)-ONzx6P;3N&^%iz<_c`-TyEOeJoR^8 zSB|vaq^jXkEoFK?oH49%tovzQ;TLq0 zPG@)Syz%e`bL8EFHs~4!->)D3mTlCQDP3*0lj!qT@12(T)eS!x<<1?Iv`FHlE8eAP z^l+DaOXsotj#h?@r2|!8ZSXnu=)5|~AmMS>tAoQ+Xlw7Cnd~BBlkn)*7ZzIxJmh0N z=c-KxdbpiRE8Wq5eFlMb{djjbw4j&LL)}H;j~}0h!pbY#FPF|Y<@2@&-Do>!=3Zv)bC3d@(B%IV-4XaBtM+?E9R4{d-9zC$9-k zA-{_w+z)v}=eY`HE1N#OS5s?MoBg@_92yUW_bBdRFK{VcwUv0}# zx325VA2|&Rn_NkvlZ7Q_*lenrENR5)349Pq(`-Q~1(iuO#&2E`rIkd$(^X8_Kiygm zS&`n~u-O!hnC$;8S?c6K3ihuE1KK+ucgFr|m%l%$L_hBKT=zQ+*UUN7&4-2cBc(^F=M4`=csA4`t=DXabR-YQyZ1%4sJepwlUoBaM zq6t}DIW9Y3 z&X*Pgy| z%0PVD1wS?GsvhWWxp953zScLX@mv=lHDZ>23I4>k(og0>td(7UxGWR%q9eifFv8Km zyc=q*H#}>nhp(CQxGhiM{E1qOXo4?2sbIp$_9J(Ix3Cx-ZDBR|Y&gw+KxP8k->6N} zhDqitT^m0o<)-(V2!9u&{Hf15d_y3JU%g_kNWU}Atev1Cnth=Kr4w{c_zipj%_+o* zwxL^?VU|1{E(!euO_3T``yt=-gw>gIyob)bBdk_{I2eHV51WFnddzAH^7QjEd46Z5 zK5^t}K45^uoEaZH^Y#++e)k`0Wx-hjopI4^O~@na5!=MsppaL#P){p^?!T}=zT=X6 zP*JH7egyuy`!)LfmtrQ>J33FhAC&7n>EkR%Aj3Pz+?^)aveDI6gyxptKnYIZod8$D z1U{%#I{#j7j8MLev z2k{ZHBRH#!AmQpd!6o3|4Vpl?qs^qd@(1Yp+Pe8nmnBUGCaAH$tj`!!W1mt+s<=-h zWC`x96L1!#YIaEUg3yD2R^Se9;8fp6ALKOx!Dd* zs^f3U3^BjD80#64p|jz@U=o>n7JK_X{fgx?! zF_fVE#qTN4*w(%>!Q`n8Er|R-75Jxo%><$-;OA|);HmHlgSbQ#?D#{?(pyXuTpfc} z#95>KfA12?*g2-kzGBEA;WtZVAQfjwFu)P#b6cAqLK$#)w20@2Y6u?*C2ywZMW!Qy zndqXa-URv3+K15vq5@bNM*afLOV5LF^G>5*_20*rfac%z7o%4-d1R+V)gmYSVlU&K zLG^j;E(8u>RtvhN@!@SmAfRA$Lc1liWeqqUxy2nEX5#LczgXsSPENm*^emq(^zqKf zEhl6nmEw^ZqN5Aql4z&aM{0xks#8stl&ju=S!9GF?9o3@fWYqQHNkU!%xXpsnrQ?{ zQ7tW=8LBD_IFpC(^3E$VI@;g|Zey4ac(_N+b2obxlRX7gN%1Wv$Vn8ngZ5*d9inuX zM)N=7;%|QDpyin8F{;U(ZvRfbK**NLM6fAlmZxKaI!Q16AZ#%koKWqX#yta~P4JTv z&&1r4Z@B$6SK~j`Os=8Lx)-CF*@T&FpioUPWI`4+YYv!-w6>nUbQ}~*H*_Q77}GMW z=Y3^jn;7XIzT}iN9zfm=88I{9=*4*7irq9MIil{5mxio**%c|n2We_IKVUWRykPXF z$y4yGWUUV5yD$vs*lf)FAYv&X9xpq<>N@a1V~TQw{xxswl8@eZjc9HXWsg0zP^$+k zmkj}fIGZv$u8@(&v5P?t7?&lEdhctZ!Y$b@I$+ZCQF2Xa7ltmnh%1rbF;$+26>&lQ zr6R;hc@BT8mdM*01~SIB7#R@zTmo<*=UR$V$&O_Nc94yIl|ODo5h(JD_0!FrzkR@5 zo~q2l@@Cm*+JXfpGVlXMUhsl|)*Y<8`&)T5DH>o!={raDT~0|_D-Cq-QeUbX5N1jt zU+%o0bIG-N3x5)@8b=RxB>1i`e=INpJV`xSo=Y*^``KpR*w`SRfgeR=a9XaNB@j^x zAW8(|%}p)*Cs{JStyZA-VJ2Z2Ij)Ffi*!k8E<&=zo3aeKFp-GOK6E${#}LUTzVHoE zWYB9n%B__@$&5fWrT52P(LXb)TAXdQb@81=jqu?My(!>I!?9lcUqR=RIU`4Gi)92H z#*^@l25UtFp!*}+D}wm$q6W%ylhNM5ux270A?vQ!1aK9%gAhkEjgn7LZQ6by`HcLW z3Y8hQc3F}|+xl}AU#R=lSnT3~#zJjK`C=d_S_0tGI)sDlT|pWsEG=3c&eIXw{bd^H z;umv(aIvf}s zjnsHMFg#R5Q19yA6Xy;A?n{Z_=o9|YGB3`#cXx0qBv5^X_2Ujfu&O6xogont{+ESM zGn`5+8+pF~BOsycjPExptt0O-G(Pu^8eulyCJCsJikI-lEs-ID=tClrB9K5U zYWm2*8l+QCobbRQkH7wPsKk-9gGD))VNgbn;M+-`bWSvJWyADIz2T!l z!AnAlxIa2x8RsKZJ$&c_XAZ59F=;+SbeAo4_1LAnesW%$P^ZSAkuQ_mE=ZIUC3Ko*g}(lOl2ns zm4<)xRSY27v+r5D7i}!XIX**tI`Y#1pS}ABn@Z)Ik@SpSiXL!Lh%-l89zZ15xK$r4 zv(bMfu4Rk6A0^yqk`hRfxltd{MQx{lH8ly@SIyK*=`XZD(I`xtSh^$H@d!2UBP_?% z`EYMerE^w+>ca*hsAF$b@7}m+3$9i8P;pC3-UM^7WCWaZ*Xa)M<5G zElh~zNLLuAk1LQuB}VKXZ~aQdg9FaK%eMbrMbu=jYYuuWU%hdrQGJ_@Bjvn!!7ex1 zcM1>|!iPEz)TB7*CAtT@YTH}l{~>*|l(Y8^=WQ$?l+vq0mL}SvOUjMM>F%*L}iD-TYwO>q-@aN4G zLH8PlJ^!^k)v8s{OuY}Zk+wS6fqL0mndWXbvCyumL+&wLB8mEA8+kL^wkfiv6SUQ8 z+xk`)s5>zfF6!trCsA?%jT8d!>KMq|{+1=To=K{N3S`6qpz8QsTD)!gnE6xt(e1Sq zpdy!gwD_A)L^yM&qdWleX;k)rujC{QKVq?Jrm2qJjFqLwE5Sez z8!SDKQMu_ZGW{KGhKvvL^miji%AjDM`{hL)PnZUqYEAPM3 zi#?`m+xXkwCFq+%Rt+U~trLPZWbL1F0j;jE^R4_Tk(Q46UoENwr~#6WXZZ^Az!_BEg)|f-^^w;Ik)C7{Puly@(#J}fi1Fzjm)%(g?*P}9gghP!k!EQ|7%Y9k z4izgNDEKXV;6Q1nbb0HbB*8@(b%0;wYcj*CU@bZt*6sDRLw0axfm!?F1wi2UlWyTY zng83#5!yNiMDzf-yxS6ag96v&q{frGLYhk}Ad-NqeraAr);b+`Q)c2VkBlOfL9fdx zSjW+6jKa`y5vu)ii|yr;HpoDgkG>@6`FQrA<^jijIT#sl*h1pJJcfx`+%(9Xd@i)C zV8n?u7XACkkD32w+QFC<=I`ojo6O?#tH@bK$t|^yi$fjV$RXANodDG;ikNk?6tE^| zsIo;#+nhL_@W#%)+}DDzv%jVEQ>~kYk@=W-`^2E2ohyXOMJvQp5kfnDvwUV9 zd_pfUK^GUCk0?kDRurR_kUkszgL)6U!WHl0#$lp7^`F;VsOD-kPcSjlZlSivoJP`q zG4`~Tay&mW+iu3S7o68%`cua1cGm$zUQputc+h~0hZomriu5XjGojq9PM~+zifD}Q zspwKHBYaib{#tt=tC`=pU_QG9HRyyx)Ho<)cUbVUJy+kFa8kxHIu{y1jn_5Q0>?%o zU@3m+p{R!_bRHeqsW)L^dUc4{o(!pXHLT0XUIl2ROF|1JG8FZQIOD2-jm4d)v0jqwYgxLcMTr&b6{XoU^*B)tZL(rLsir41Erx zSkMpp0HIOj>&4vHHe>ipiR_p}q^c6xLHDC#XNnl$e;my1yXp`zt{OHe z)Y+|Nd)T%hkDJNhejUvP0)Rxev2HYN!Q3JRXzS8^zqM@UbW4+myuk7f>#7qv+{VH; ze!s1PkFd;8HQ0`Z1hsDoVIS+X;CM=U;l7p$d2~fvjpm`8nz@l2k{a__WuZrK8ke$* z4{cXihZ6S$3K;=~JpqAL&wr(DqLDMB2z=eQaJ%1`fVxhtOzjRqYAdvbT7S=ZSTl4Q zj=iHP1{C+l&Y2A_mpGZ8a}yWyy5yX6UoMp+SEmUkQMvY}RjUN9S7^Hw8S}y#V@c9) z*Yf>o>VzPuNjL*`cq4h;m$`;JCVRu2W*g~`!=9KIG=7uDU7EkPpo6n8=EHeader Level 3 PhotoSphereViewer.GyroscopePlugin, PhotoSphereViewer.StereoPlugin, [PhotoSphereViewer.MarkersPlugin, { - markers : (() => { + markers: (() => { const a = []; // add markers all hover the sphere @@ -271,6 +271,18 @@

Header Level 3

tooltip : 'Image embedded in the scene', }); + a.push({ + id : 'imageLayerOrient', + imageLayer : 'assets/target.png', + width : 120, + height : 120, + latitude : -0.2, + longitude : 0.27, + opacity : 0.8, + orientation: 'horizontal', + tooltip : 'Image embedded in the scene with "horizontal" orientation', + }); + // SVG markers a.push({ id : 'svg-demo', diff --git a/src/plugins/markers/Marker.js b/src/plugins/markers/Marker.js index 1f6f4ffd0..492951ac0 100644 --- a/src/plugins/markers/Marker.js +++ b/src/plugins/markers/Marker.js @@ -395,6 +395,7 @@ export class Marker { } // apply style + this.$el.style.opacity = this.config.opacity ?? 1; if (this.config.style) { utils.deepmerge(this.$el.style, this.config.style); } @@ -627,7 +628,7 @@ export class Marker { // compute x/y/z positions this.props.positions3D = this.props.def.map((coord) => { - return this.psv.dataHelper.sphericalCoordsToVector3({ longitude: coord[0], latitude: coord[1] }); + return this.psv.dataHelper.sphericalCoordsToVector3({ longitude: coord[0], latitude : coord[1] }); }); } @@ -653,7 +654,11 @@ export class Marker { switch (this.type) { case MARKER_TYPES.imageLayer: if (!this.$el) { - const material = new THREE.MeshBasicMaterial({ transparent: true, depthTest: false }); + const material = new THREE.MeshBasicMaterial({ + transparent: true, + opacity : this.config.opacity ?? 1, + depthTest : false, + }); const geometry = new THREE.PlaneGeometry(1, 1); const mesh = new THREE.Mesh(geometry, material); mesh.userData = { [MARKER_DATA]: this }; @@ -676,7 +681,10 @@ export class Marker { if (this.psv.config.requestHeaders && typeof this.psv.config.requestHeaders === 'function') { this.loader.setRequestHeader(this.psv.config.requestHeaders(this.config.imageLayer)); } - this.$el.children[0].material.map = this.loader.load(this.config.imageLayer, () => this.psv.needsUpdate()); + this.$el.children[0].material.map = this.loader.load(this.config.imageLayer, (texture) => { + texture.anisotropy = 4; + this.psv.needsUpdate(); + }); this.props.def = this.config.imageLayer; } @@ -687,7 +695,25 @@ export class Marker { ); this.$el.position.copy(this.props.positions3D[0]); - this.$el.lookAt(0, 0, 0); + + switch (this.config.orientation) { + case 'horizontal': + this.$el.lookAt(0, this.$el.position.y, 0); + this.$el.rotateX(this.props.position.latitude < 0 ? -Math.PI / 2 : Math.PI / 2); + break; + case 'vertical-left': + this.$el.lookAt(0, 0, 0); + this.$el.rotateY(-Math.PI * 0.4); + break; + case 'vertical-right': + this.$el.lookAt(0, 0, 0); + this.$el.rotateY(Math.PI * 0.4); + break; + default: + this.$el.lookAt(0, 0, 0); + break; + } + // 100 is magic number that gives a coherent size at default zoom level this.$el.scale.set(this.config.width / 100 * SYSTEM.pixelRatio, this.config.height / 100 * SYSTEM.pixelRatio, 1); break; diff --git a/types/plugins/markers/index.d.ts b/types/plugins/markers/index.d.ts index 29f2559d8..2416c5329 100644 --- a/types/plugins/markers/index.d.ts +++ b/types/plugins/markers/index.d.ts @@ -35,7 +35,9 @@ export type MarkerProperties = Partial & { id: string; width?: number; height?: number; + orientation?: 'front' | 'horizontal' | 'vertical-left' | 'vertical-right'; scale?: number | [number, number] | { zoom?: [number, number], longitude?: [number, number] }; + opacity?: number; className?: string; style?: Record; svgStyle?: Record; From c5b82c1586fb8e167c8f791cfa022054d21e1495 Mon Sep 17 00:00:00 2001 From: mistic100 Date: Thu, 3 Mar 2022 21:35:46 +0100 Subject: [PATCH 15/17] virtual tour: better 3D arrow --- docs/plugins/plugin-virtual-tour.md | 8 +-- src/plugins/virtual-tour/arrow.STL | Bin 384 -> 0 bytes src/plugins/virtual-tour/arrow.json | 10 ++-- src/plugins/virtual-tour/arrow_outline.json | 29 +++++++++++ src/plugins/virtual-tour/constants.js | 30 +++++++----- src/plugins/virtual-tour/index.js | 46 ++++++------------ src/plugins/virtual-tour/src/arrow.stl | Bin 0 -> 1084 bytes .../virtual-tour/src/arrow_outline.stl | Bin 0 -> 1084 bytes src/plugins/virtual-tour/utils.js | 1 - src/services/Renderer.js | 2 +- types/plugins/virtual-tour/index.d.ts | 4 ++ 11 files changed, 74 insertions(+), 56 deletions(-) delete mode 100644 src/plugins/virtual-tour/arrow.STL create mode 100644 src/plugins/virtual-tour/arrow_outline.json create mode 100644 src/plugins/virtual-tour/src/arrow.stl create mode 100644 src/plugins/virtual-tour/src/arrow_outline.stl diff --git a/docs/plugins/plugin-virtual-tour.md b/docs/plugins/plugin-virtual-tour.md index 22a7fe95f..3d5b46a62 100644 --- a/docs/plugins/plugin-virtual-tour.md +++ b/docs/plugins/plugin-virtual-tour.md @@ -271,10 +271,10 @@ Style of the arrow used to display links. Default value is: ```js { - color : 0x0055aa, - hoverColor: 0xaa5500, - opacity : 0.8, - scale : [0.5, 2], + color : 0xaaaaaa, + hoverColor : 0xaa5500, + outlineColor: 0x000000, + scale : [0.5, 2], } ``` diff --git a/src/plugins/virtual-tour/arrow.STL b/src/plugins/virtual-tour/arrow.STL deleted file mode 100644 index df4515c2363282e2bf1984738070b9fe7e99f7f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384 zcmXTU&&f=_t5oPa_=aKaJ7UI3&)Y!Fa@ ziUD=(+bi9_6jR3upl+BdkV!DznU0(G9l021527LFf>ixlj%qHN4m*SnbaP?4(R6_9 g*pILSWIDRrK{`OXK>(-{WCxNCm<+moAk`p~07S4;W&i*H diff --git a/src/plugins/virtual-tour/arrow.json b/src/plugins/virtual-tour/arrow.json index 99d051024..c7bca61ed 100644 --- a/src/plugins/virtual-tour/arrow.json +++ b/src/plugins/virtual-tour/arrow.json @@ -4,26 +4,26 @@ "type": "BufferGeometry", "generator": "BufferGeometry.toJSON" }, - "uuid": "B5839B4F-7D11-4EC3-B454-059BC0185A1B", + "uuid": "8B1A6E5B-A7CC-4471-9CA0-BD64D80ABB79", "type": "BufferGeometry", "data": { "attributes": { "position": { "itemSize": 3, "type": "Float32Array", - "array": [50,0,25,50,20,25,0,10,50,50,0,25,100,10,50,50,20,25,50,10,0,0,10,50,50,20,25,100,10,50,50,10,0,50,20,25,50,0,25,0,10,50,50,10,0,50,10,0,100,10,50,50,0,25], + "array": [-25,-15,-2.5,0,0,-2.5,0,-5,-2.5,0,-5,-2.5,0,0,-2.5,25,-15,-2.5,0,-5,-2.5,25,-15,-2.5,25,-20,-2.5,0,-5,-2.5,-25,-20,-2.5,-25,-15,-2.5,25,-15,2.5,25,-20,2.5,25,-15,-2.5,25,-15,-2.5,25,-20,2.5,25,-20,-2.5,25,-20,2.5,0,-5,2.5,25,-20,-2.5,25,-20,-2.5,0,-5,2.5,0,-5,-2.5,0,-5,2.5,-25,-20,2.5,0,-5,-2.5,0,-5,-2.5,-25,-20,2.5,-25,-20,-2.5,-25,-20,2.5,-25,-15,2.5,-25,-20,-2.5,-25,-20,-2.5,-25,-15,2.5,-25,-15,-2.5,-25,-15,2.5,0,0,2.5,-25,-15,-2.5,-25,-15,-2.5,0,0,2.5,0,0,-2.5,0,0,2.5,25,-15,2.5,0,0,-2.5,0,0,-2.5,25,-15,2.5,25,-15,-2.5,25,-20,2.5,25,-15,2.5,0,-5,2.5,0,-5,2.5,25,-15,2.5,0,0,2.5,0,-5,2.5,0,0,2.5,-25,-15,2.5,-25,-15,2.5,-25,-20,2.5,0,-5,2.5], "normalized": false }, "normal": { "itemSize": 3, "type": "Float32Array", - "array": [0.447214,0,0.894427,0.447214,0,0.894427,0.447214,0,0.894427,-0.447214,0,0.894427,-0.447214,0,0.894427,-0.447214,0,0.894427,-0.348155,0.870388,-0.348155,-0.348155,0.870388,-0.348155,-0.348155,0.870388,-0.348155,0.348155,0.870388,-0.348155,0.348155,0.870388,-0.348155,0.348155,0.870388,-0.348155,-0.348155,-0.870388,-0.348155,-0.348155,-0.870388,-0.348155,-0.348155,-0.870388,-0.348155,0.348155,-0.870388,-0.348155,0.348155,-0.870388,-0.348155,0.348155,-0.870388,-0.348155], + "array": [0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-0.514495,-0.857492,0,-0.514495,-0.857492,0,-0.514495,-0.857492,0,-0.514495,-0.857492,0,-0.514495,-0.857492,0,-0.514495,-0.857492,0,0.514495,-0.857492,0,0.514495,-0.857492,0,0.514495,-0.857492,0,0.514495,-0.857492,0,0.514495,-0.857492,0,0.514495,-0.857492,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-0.514495,0.857492,0,-0.514495,0.857492,0,-0.514495,0.857492,0,-0.514495,0.857492,0,-0.514495,0.857492,0,-0.514495,0.857492,0,0.514495,0.857492,0,0.514495,0.857492,0,0.514495,0.857492,0,0.514495,0.857492,0,0.514495,0.857492,0,0.514495,0.857492,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1], "normalized": false } }, "boundingSphere": { - "center": [50,10,25], - "radius": 55.901699 + "center": [0,-10,0], + "radius": 27.041634 } } } diff --git a/src/plugins/virtual-tour/arrow_outline.json b/src/plugins/virtual-tour/arrow_outline.json new file mode 100644 index 000000000..4d1fc8215 --- /dev/null +++ b/src/plugins/virtual-tour/arrow_outline.json @@ -0,0 +1,29 @@ +{ + "metadata": { + "version": 4.5, + "type": "BufferGeometry", + "generator": "BufferGeometry.toJSON" + }, + "uuid": "B12A1453-6E56-40AC-840B-BA7DF5DB9E2A", + "type": "BufferGeometry", + "data": { + "attributes": { + "position": { + "itemSize": 3, + "type": "Float32Array", + "array": [-26,-21.766189,-3.5,-26,-14.433809,-3.5,0,-6.166190,-3.5,0,-6.166190,-3.5,-26,-14.433809,-3.5,0,1.166190,-3.5,0,-6.166190,-3.5,0,1.166190,-3.5,26,-14.433810,-3.5,26,-14.433810,-3.5,26,-21.766191,-3.5,0,-6.166190,-3.5,-26,-14.433809,3.5,0,1.166190,3.5,-26,-14.433809,-3.5,-26,-14.433809,-3.5,0,1.166190,3.5,0,1.166190,-3.5,0,1.166190,3.5,26,-14.433810,3.5,0,1.166190,-3.5,0,1.166190,-3.5,26,-14.433810,3.5,26,-14.433810,-3.5,26,-14.433810,3.5,26,-21.766191,3.5,26,-14.433810,-3.5,26,-14.433810,-3.5,26,-21.766191,3.5,26,-21.766191,-3.5,26,-21.766191,3.5,0,-6.166190,3.5,26,-21.766191,-3.5,26,-21.766191,-3.5,0,-6.166190,3.5,0,-6.166190,-3.5,0,-6.166190,3.5,-26,-21.766189,3.5,0,-6.166190,-3.5,0,-6.166190,-3.5,-26,-21.766189,3.5,-26,-21.766189,-3.5,-26,-21.766189,3.5,-26,-14.433809,3.5,-26,-21.766189,-3.5,-26,-21.766189,-3.5,-26,-14.433809,3.5,-26,-14.433809,-3.5,-26,-21.766189,3.5,0,-6.166190,3.5,-26,-14.433809,3.5,-26,-14.433809,3.5,0,-6.166190,3.5,0,1.166190,3.5,0,1.166190,3.5,0,-6.166190,3.5,26,-14.433810,3.5,26,-14.433810,3.5,0,-6.166190,3.5,26,-21.766191,3.5], + "normalized": false + }, + "normal": { + "itemSize": 3, + "type": "Float32Array", + "array": [0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0.514495,0.857492,0,-0.514495,0.857492,0,-0.514495,0.857492,0,-0.514495,0.857492,0,-0.514495,0.857492,0,-0.514495,0.857492,0,0.514495,0.857492,0,0.514495,0.857492,0,0.514495,0.857492,0,0.514495,0.857492,0,0.514495,0.857492,0,0.514495,0.857492,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-0.514495,-0.857492,0,-0.514495,-0.857492,0,-0.514495,-0.857492,0,-0.514495,-0.857492,0,-0.514495,-0.857492,0,-0.514495,-0.857492,0,0.514495,-0.857492,0,0.514495,-0.857492,0,0.514495,-0.857492,0,0.514495,-0.857492,0,0.514495,-0.857492,0,0.514495,-0.857492,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1], + "normalized": false + } + }, + "boundingSphere": { + "center": [0,-10.300000,0], + "radius": 28.630814 + } + } +} diff --git a/src/plugins/virtual-tour/constants.js b/src/plugins/virtual-tour/constants.js index 5c2454d93..0bb2900a2 100644 --- a/src/plugins/virtual-tour/constants.js +++ b/src/plugins/virtual-tour/constants.js @@ -1,6 +1,7 @@ import * as THREE from 'three'; import arrowGeometryJson from './arrow.json'; import arrowIconSvg from './arrow.svg'; +import arrowOutlineGeometryJson from './arrow_outline.json'; import nodesList from './nodes-list.svg'; /** @@ -64,7 +65,7 @@ export const EVENTS = { * @summary Triggered when the current node changes * @param {string} nodeId */ - NODE_CHANGED: 'node-changed', + NODE_CHANGED : 'node-changed', /** * @event filter:render-nodes-list * @memberof PSV.plugins.VirtualTourPlugin @@ -108,10 +109,10 @@ export const DEFAULT_MARKER = { * @private */ export const DEFAULT_ARROW = { - color : 0x0055aa, - hoverColor: 0xaa5500, - opacity : 0.8, - scale : [0.5, 2], + color : 0xaaaaaa, + hoverColor : 0xaa5500, + outlineColor: 0x000000, + scale : [0.5, 2], }; /** @@ -119,15 +120,18 @@ export const DEFAULT_ARROW = { * @constant * @private */ -export const ARROW_GEOM = (() => { +export const { ARROW_GEOM, ARROW_OUTLINE_GEOM } = (() => { const loader = new THREE.ObjectLoader(); - const geom = loader.parseGeometries([arrowGeometryJson])[arrowGeometryJson.uuid]; - geom.scale(0.01, 0.015, 0.015); - geom.computeBoundingBox(); - const b = geom.boundingBox; - geom.translate(-(b.max.x - b.min.y) / 2, -(b.max.y - b.min.y) / 2, -(b.max.z - b.min.z) / 2); - geom.rotateX(Math.PI); - return geom; + const geometries = loader.parseGeometries([arrowGeometryJson, arrowOutlineGeometryJson]); + const arrow = geometries[arrowGeometryJson.uuid]; + const arrowOutline = geometries[arrowOutlineGeometryJson.uuid]; + const scale = 0.015; + const rot = Math.PI / 2; + arrow.scale(scale, scale, scale); + arrow.rotateX(rot); + arrowOutline.scale(scale, scale, scale); + arrowOutline.rotateX(rot); + return { ARROW_GEOM: arrow, ARROW_OUTLINE_GEOM: arrowOutline }; })(); /** diff --git a/src/plugins/virtual-tour/index.js b/src/plugins/virtual-tour/index.js index fe10ebb66..723abe91f 100644 --- a/src/plugins/virtual-tour/index.js +++ b/src/plugins/virtual-tour/index.js @@ -3,6 +3,7 @@ import { AbstractPlugin, CONSTANTS, DEFAULTS, PSVError, registerButton, utils } import { ClientSideDatasource } from './ClientSideDatasource'; import { ARROW_GEOM, + ARROW_OUTLINE_GEOM, DEFAULT_ARROW, DEFAULT_MARKER, EVENTS, @@ -76,9 +77,9 @@ import { bearing, distance, setMeshColor } from './utils'; /** * @typedef {Object} PSV.plugins.VirtualTourPlugin.ArrowStyle * @summary Style of the arrow in 3D mode - * @property {string} [color=#0055aa] - * @property {string} [hoverColor=#aa5500] - * @property {number} [opacity=0.8] + * @property {string} [color=0xaaaaaa] + * @property {string} [hoverColor=0xaa5500] + * @property {number} [outlineColor=0x000000] * @property {number[]} [scale=[0.5,2]] */ @@ -212,7 +213,7 @@ export class VirtualTourPlugin extends AbstractPlugin { this.arrowsGroup = new THREE.Group(); const localLight = new THREE.PointLight(0xffffff, 1, 0); - localLight.position.set(2, 0, 0); + localLight.position.set(0, this.config.arrowPosition === 'bottom' ? 2 : -2, 0); this.arrowsGroup.add(localLight); } } @@ -516,15 +517,7 @@ export class VirtualTourPlugin extends AbstractPlugin { positions.push(position); if (this.is3D()) { - const arrow = ARROW_GEOM.clone(); - const mat = new THREE.MeshLambertMaterial({ - transparent: true, - opacity : link.arrowStyle?.opacity || this.config.arrowStyle.opacity, - }); - const mesh = new THREE.Mesh(arrow, mat); - - setMeshColor(mesh, link.arrowStyle?.color || this.config.arrowStyle.color); - + const mesh = new THREE.Mesh(ARROW_GEOM, new THREE.MeshLambertMaterial()); mesh.userData = { [LINK_DATA]: link, longitude: position.longitude }; mesh.rotation.order = 'YXZ'; mesh.rotateY(-position.longitude); @@ -532,7 +525,15 @@ export class VirtualTourPlugin extends AbstractPlugin { .sphericalCoordsToVector3({ longitude: position.longitude, latitude: 0 }, mesh.position) .multiplyScalar(1 / CONSTANTS.SPHERE_RADIUS); + const outlineMesh = new THREE.Mesh(ARROW_OUTLINE_GEOM, new THREE.MeshBasicMaterial({ side: THREE.BackSide })); + outlineMesh.position.copy(mesh.position); + outlineMesh.rotation.copy(mesh.rotation); + + setMeshColor(mesh, link.arrowStyle?.color || this.config.arrowStyle.color); + setMeshColor(outlineMesh, link.arrowStyle?.outlineColor || this.config.arrowStyle.outlineColor); + this.arrowsGroup.add(mesh); + this.arrowsGroup.add(outlineMesh); } else { if (this.isGps()) { @@ -652,25 +653,6 @@ export class VirtualTourPlugin extends AbstractPlugin { const f = s[1] + (s[0] - s[1]) * CONSTANTS.EASINGS.linear(this.psv.getZoomLevel() / 100); this.arrowsGroup.position.y += isBottom ? -1.5 : 1.5; this.arrowsGroup.scale.set(f, f, f); - - // slightly rotates each arrow to make the center ones standing out - const position = this.psv.getPosition(); - if (isBottom ? (position.latitude < Math.PI / 8) : (position.latitude > -Math.PI / 8)) { - this.arrowsGroup.children - .filter(o => o.type === 'Mesh') - .forEach((arrow) => { - const d = Math.abs(utils.getShortestArc(arrow.userData.longitude, position.longitude)); - const x = CONSTANTS.EASINGS.inOutSine(Math.max(0, Math.PI / 4 - d) / (Math.PI / 4)) / 3; // magic ! - arrow.rotation.x = isBottom ? -x : x; - }); - } - else { - this.arrowsGroup.children - .filter(o => o.type === 'Mesh') - .forEach((arrow) => { - arrow.rotation.x = 0; - }); - } } /** diff --git a/src/plugins/virtual-tour/src/arrow.stl b/src/plugins/virtual-tour/src/arrow.stl new file mode 100644 index 0000000000000000000000000000000000000000..ded24e9c7017efeecad2f12c0f57e2a20af4f266 GIT binary patch literal 1084 zcmb7?&rL%y48|j|0s~}%ii&bXO+n&RQ3-lu2_zQCfkAR)0nQD#P6d2l96NrrRAA{n zS+Rfq|F~V>#MSmL&M#*3%Nc%)t3|xstTzwWrIfGuPdN`>@-Bq^M+ndOVjULM3*KUV zxgQo16tRfTKZ*THBb_3Kmk_1pK1sksynMAJ@^VyQag&SNb?NU{qSxMXcp9C5zMt*h zUZ2=?p2o$COd+}VDFF{wrL}v)bRm{K2cIErx(T#)4xOU=Kvlt@TTZR+6G+?@nE%f?fH-xUT#)Gz?;GxHpL{m-Y)H0)i-0- BFI&wHE4)jbs1q;^9j8t~;Dee@76Cxq^Zv1&+!?~S#mdi?}6QbK$`T^o8J zm_%@|bng9hRyzq-5^-+QIsZMIXURmp#!do|RO0XvH^U^pcFr2!!u}Z0amU~b-A}lj z5W1U{++Sve-G#;<2(OV6ar6BqqDnTNvfO3(B<(5Ezlj*h3EiHO&`3_`E|i2uN+SG0 G)6_p)8bbmA literal 0 HcmV?d00001 diff --git a/src/plugins/virtual-tour/utils.js b/src/plugins/virtual-tour/utils.js index d8015a71e..0aa83aa87 100644 --- a/src/plugins/virtual-tour/utils.js +++ b/src/plugins/virtual-tour/utils.js @@ -42,7 +42,6 @@ export function checkLink(node, link, isGps) { */ export function setMeshColor(mesh, color) { mesh.material.color.set(color); - mesh.material.emissive.set(color); } /** diff --git a/src/services/Renderer.js b/src/services/Renderer.js index 8d5b7ff79..5facdbb4d 100644 --- a/src/services/Renderer.js +++ b/src/services/Renderer.js @@ -23,7 +23,7 @@ export class Renderer extends AbstractService { * @readonly * @protected */ - this.renderer = new THREE.WebGLRenderer({ alpha: true }); + this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); this.renderer.setPixelRatio(SYSTEM.pixelRatio); this.renderer.domElement.className = 'psv-canvas'; diff --git a/types/plugins/virtual-tour/index.d.ts b/types/plugins/virtual-tour/index.d.ts index 22ce336ee..6869883b9 100644 --- a/types/plugins/virtual-tour/index.d.ts +++ b/types/plugins/virtual-tour/index.d.ts @@ -34,6 +34,10 @@ export type VirtualTourNodeLink = { export type VirtualTourArrowStyle = { color?: string; hoverColor?: string; + outlineColor?: number; + /** + * @deprecated + */ opacity?: number; scale?: [number, number]; }; From ad2379bf494effd04d02299eb85e3b10c86c1614 Mon Sep 17 00:00:00 2001 From: mistic100 Date: Wed, 2 Mar 2022 18:08:58 +0100 Subject: [PATCH 16/17] Fix #644 supports preload of base image for tiles adapters --- docs/plugins/plugin-virtual-tour.md | 6 + example/plugin-virtual-tour.html | 5 +- src/Animation.js | 128 ++++++++++---------- src/Viewer.js | 32 +++-- src/adapters/AbstractAdapter.js | 46 ++++--- src/adapters/cubemap-tiles/index.js | 108 +++++++++++------ src/adapters/cubemap/index.js | 28 ++++- src/adapters/equirectangular-tiles/index.js | 104 +++++++++++----- src/adapters/equirectangular/index.js | 30 +++-- src/data/constants.js | 16 +++ src/plugins/virtual-tour/index.js | 40 +++--- src/services/DataHelper.js | 4 +- src/services/EventsHandler.js | 6 +- src/services/Renderer.js | 44 ++++--- src/services/TextureLoader.js | 2 +- tests/types/index.ts | 21 +++- types/Animation.d.ts | 38 +++--- types/Viewer.d.ts | 4 +- types/adapters/AbstractAdapter.d.ts | 26 ++-- types/plugins/markers/index.d.ts | 2 +- types/plugins/virtual-tour/index.d.ts | 1 + 21 files changed, 438 insertions(+), 253 deletions(-) diff --git a/docs/plugins/plugin-virtual-tour.md b/docs/plugins/plugin-virtual-tour.md index 3d5b46a62..8168fde13 100644 --- a/docs/plugins/plugin-virtual-tour.md +++ b/docs/plugins/plugin-virtual-tour.md @@ -227,6 +227,12 @@ Enable the preloading of linked nodes, can be a function that returns true or fa When a link is clicked, adds a panorama rotation to face it before actually changing the node. If `false` the viewer won't rotate at all and keep the current orientation. +#### `transition` +- type: `boolean | number` +- default: `1500` + +Duration of the transition between nodes. + #### `linksOnCompass` - type: `boolean` - default: `true` if markers render mode diff --git a/example/plugin-virtual-tour.html b/example/plugin-virtual-tour.html index e86aeef60..a5af814ec 100644 --- a/example/plugin-virtual-tour.html +++ b/example/plugin-virtual-tour.html @@ -128,8 +128,9 @@ [PhotoSphereViewer.VirtualTourPlugin, { positionMode : PhotoSphereViewer.VirtualTourPlugin.MODE_GPS, renderMode : PhotoSphereViewer.VirtualTourPlugin.MODE_3D, - startNodeId : nodes[0].id, + startNodeId : nodes[1].id, preload : true, + // transition : false, // rotateSpeed : false, arrowPosition: 'bottom', @@ -139,7 +140,7 @@ // server mode (mock) // dataMode: PhotoSphereViewer.VirtualTourPlugin.MODE_SERVER, - getNode : (nodeId) => { + getNode: (nodeId) => { console.log('GET node ' + nodeId); return Promise.resolve({ ...nodesById[nodeId], diff --git a/src/Animation.js b/src/Animation.js index 4bca0398c..c61ced057 100644 --- a/src/Animation.js +++ b/src/Animation.js @@ -1,5 +1,5 @@ import { EASINGS } from './data/constants'; -import { each } from './utils'; +import { each, logWarn } from './utils'; /** * @callback OnTick @@ -13,16 +13,20 @@ import { each } from './utils'; * @summary Interpolation helper for animations * @memberOf PSV * @description - * Implements the Promise API with an additional "cancel" and "finally" methods. - * The promise is resolved when the animation is complete and rejected if the animation is cancelled. + * Implements the Promise API with an additional "cancel" method. + * The promise is resolved with `true` when the animation is completed and `false` if the animation is cancelled. * @example - * new Animation({ + * const anim = new Animation({ * properties: { * width: {start: 100, end: 200} * }, * duration: 5000, * onTick: (properties) => element.style.width = `${properties.width}px`; - * }) + * }); + * + * anim.then((completed) => ...); + * + * anim.cancel() */ export class Animation { @@ -37,31 +41,29 @@ export class Animation { * @param {PSV.Animation.OnTick} options.onTick - called on each frame */ constructor(options) { - this.__cancelled = false; - this.__resolved = false; - - this.__promise = new Promise((resolve, reject) => { - this.__resolve = resolve; - this.__reject = reject; - }); + this.__callbacks = []; if (options) { if (!options.easing || typeof options.easing === 'string') { options.easing = EASINGS[options.easing || 'linear']; } + this.__start = null; this.options = options; if (options.delay) { this.__delayTimeout = setTimeout(() => { this.__delayTimeout = null; - window.requestAnimationFrame(t => this.__run(t)); + this.__animationFrame = window.requestAnimationFrame(t => this.__run(t)); }, options.delay); } else { - window.requestAnimationFrame(t => this.__run(t)); + this.__animationFrame = window.requestAnimationFrame(t => this.__run(t)); } } + else { + this.__resolved = true; + } } /** @@ -70,11 +72,6 @@ export class Animation { * @private */ __run(timestamp) { - // the animation has been cancelled - if (this.__cancelled) { - return; - } - // first iteration if (this.__start === null) { this.__start = timestamp; @@ -91,10 +88,9 @@ export class Animation { current[name] = prop.start + (prop.end - prop.start) * this.options.easing(progress); } }); - this.options.onTick(current, progress); - window.requestAnimationFrame(t => this.__run(t)); + this.__animationFrame = window.requestAnimationFrame(t => this.__run(t)); } else { // call onTick one last time with final values @@ -103,52 +99,43 @@ export class Animation { current[name] = prop.end; } }); - this.options.onTick(current, 1.0); - window.requestAnimationFrame(() => { + this.__animationFrame = window.requestAnimationFrame(() => { this.__resolved = true; - this.__resolve(); + this.__resolve(true); }); } } /** - * @summary Animation chaining - * @param {Function} [onFulfilled] - Called when the animation is complete, can return a new animation - * @param {Function} [onRejected] - Called when the animation is cancelled - * @returns {PSV.Animation} + * @private */ - then(onFulfilled = null, onRejected = null) { - const p = new Animation(); - - // Allow cancellation to climb up the promise chain - p.__promise.then(null, () => this.cancel()); - - this.__promise.then( - () => p.__resolve(onFulfilled ? onFulfilled() : undefined), - () => p.__reject(onRejected ? onRejected() : undefined) - ); - - return p; + __resolve(value) { + this.__callbacks.forEach(cb => cb(value)); + this.__callbacks.length = 0; } /** - * @summary Alias to `.then(null, onRejected)` - * @param {Function} onRejected - Called when the animation has been cancelled - * @returns {PSV.Animation} + * @summary Promise chaining + * @param {Function} [onFulfilled] - Called when the animation is complete (true) or cancelled (false) + * @param {Function} [onRejected] - deprecated + * @returns {PSV.Promise} */ - catch(onRejected) { - return this.then(undefined, onRejected); - } + then(onFulfilled = null, onRejected = null) { + if (onRejected) { + logWarn('Animation#then does not accept a rejection handler anymore'); + } - /** - * @summary Alias to `.then(onFinally, onFinally)` - * @param {Function} onFinally - Called when the animation is either complete or cancelled - * @returns {PSV.Animation} - */ - finally(onFinally) { - return this.then(onFinally, onFinally); + if (this.__resolved || this.__cancelled) { + return Promise.resolve(this.__resolved) + .then(onFulfilled); + } + + return new Promise((resolve) => { + this.__callbacks.push(resolve); + }) + .then(onFulfilled); } /** @@ -157,27 +144,40 @@ export class Animation { cancel() { if (!this.__cancelled && !this.__resolved) { this.__cancelled = true; - this.__reject(); + this.__resolve(false); if (this.__delayTimeout) { - window.cancelAnimationFrame(this.__delayTimeout); + window.clearTimeout(this.__delayTimeout); this.__delayTimeout = null; } + if (this.__animationFrame) { + window.cancelAnimationFrame(this.__animationFrame); + this.__animationFrame = null; + } } } /** - * @summary Returns a resolved animation promise - * @returns {PSV.Animation} + * @deprecated not supported anymore + */ + catch() { + logWarn('Animation#catch is not supported anymore'); + return this.then(); + } + + /** + * @deprecated not supported anymore + */ + finally(onFinally) { + logWarn('Animation#finally is not supported anymore'); + return this.then(onFinally); + } + + /** + * @deprecated not supported anymore */ static resolve() { - const p = Promise.resolve(); - p.cancel = () => { - }; - p.finally = (onFinally) => { - return p.then(onFinally, onFinally); - }; - return p; + logWarn('Animation.resolve is not supported anymore'); } } diff --git a/src/Viewer.js b/src/Viewer.js index 22a433d88..f8fc5bf10 100644 --- a/src/Viewer.js +++ b/src/Viewer.js @@ -7,7 +7,7 @@ import { Notification } from './components/Notification'; import { Overlay } from './components/Overlay'; import { Panel } from './components/Panel'; import { CONFIG_PARSERS, DEFAULTS, DEPRECATED_OPTIONS, getConfig, READONLY_OPTIONS } from './data/config'; -import { CHANGE_EVENTS, EVENTS, IDS, SPHERE_RADIUS, VIEWER_DATA } from './data/constants'; +import { CHANGE_EVENTS, DEFAULT_TRANSITION, EVENTS, IDS, SPHERE_RADIUS, VIEWER_DATA } from './data/constants'; import { SYSTEM } from './data/system'; import errorIcon from './icons/error.svg'; import { AbstractPlugin } from './plugins/AbstractPlugin'; @@ -451,9 +451,8 @@ export class Viewer extends EventEmitter { * @returns {Promise} resolves false if the loading was aborted by another call */ setPanorama(path, options = {}) { - if (this.prop.loadingPromise !== null) { - this.textureLoader.abortLoading(); - } + this.textureLoader.abortLoading(); + this.prop.transitionAnimation?.cancel(); // apply default parameters on first load if (!this.prop.ready) { @@ -466,7 +465,7 @@ export class Viewer extends EventEmitter { } if (options.transition === undefined || options.transition === true) { - options.transition = 1500; + options.transition = DEFAULT_TRANSITION; } if (options.showLoader === undefined) { options.showLoader = true; @@ -489,19 +488,17 @@ export class Viewer extends EventEmitter { const done = (err) => { this.loader.hide(); - this.renderer.show(); this.prop.loadingPromise = null; if (isAbortError(err)) { - console.warn(err); return false; } else if (err) { this.navbar.setCaption(''); this.showError(this.config.lang.loadError); console.error(err); - return Promise.reject(err); + throw err; } else { this.navbar.setCaption(this.config.caption); @@ -518,14 +515,16 @@ export class Viewer extends EventEmitter { .then((textureData) => { // check if another panorama was requested if (textureData.panorama !== this.config.panorama) { - return Promise.reject(getAbortError()); + this.adapter.disposeTexture(textureData); + throw getAbortError(); } return textureData; }); - if (!options.transition || !this.prop.ready || !this.adapter.constructor.supportsTransition) { + if (!options.transition || !this.prop.ready || !this.adapter.supportsTransition(this.config.panorama)) { this.prop.loadingPromise = loadingPromise .then((textureData) => { + this.renderer.show(); this.renderer.setTexture(textureData); this.renderer.setPanoramaPose(textureData.panoData); this.renderer.setSphereCorrection(options.sphereCorrection); @@ -544,7 +543,14 @@ export class Viewer extends EventEmitter { .then((textureData) => { this.loader.hide(); - return this.renderer.transition(textureData, options); + this.prop.transitionAnimation = this.renderer.transition(textureData, options); + return this.prop.transitionAnimation + .then((completed) => { + this.prop.transitionAnimation = null; + if (!completed) { + throw getAbortError(); + } + }); }) .then(done, done); } @@ -780,7 +786,7 @@ export class Viewer extends EventEmitter { this.zoom(options.zoom); } - return Animation.resolve(); + return new Animation(); } this.prop.animationPromise = new Animation({ @@ -808,7 +814,7 @@ export class Viewer extends EventEmitter { stopAnimation() { if (this.prop.animationPromise) { return new Promise((resolve) => { - this.prop.animationPromise.finally(resolve); + this.prop.animationPromise.then(resolve); this.prop.animationPromise.cancel(); this.prop.animationPromise = null; }); diff --git a/src/adapters/AbstractAdapter.js b/src/adapters/AbstractAdapter.js index 608e4f112..93bfc06a8 100644 --- a/src/adapters/AbstractAdapter.js +++ b/src/adapters/AbstractAdapter.js @@ -19,22 +19,6 @@ export class AbstractAdapter { */ static id = null; - /** - * @summary Indicates if the adapter supports transitions between panoramas - * @member {boolean} - * @readonly - * @static - */ - static supportsTransition = false; - - /** - * @summary Indicates if the adapter supports preload - * @type {boolean} - * @readonly - * @static - */ - static supportsPreload = false; - /** * @summary Indicates if the adapter supports panorama download natively * @type {boolean} @@ -62,6 +46,24 @@ export class AbstractAdapter { delete this.psv; } + /** + * @summary Indicates if the adapter supports transitions between panoramas + * @param {*} panorama + * @return {boolean} + */ + supportsTransition(panorama) { // eslint-disable-line no-unused-vars + return false; + } + + /** + * @summary Indicates if the adapter supports preload of a panorama + * @param {*} panorama + * @return {boolean} + */ + supportsPreload(panorama) { // eslint-disable-line no-unused-vars + return false; + } + /** * @abstract * @summary Loads the panorama texture(s) @@ -88,8 +90,9 @@ export class AbstractAdapter { * @summary Applies the texture to the mesh * @param {external:THREE.Mesh} mesh * @param {PSV.TextureData} textureData + * @param {boolean} [transition=false] */ - setTexture(mesh, textureData) { // eslint-disable-line no-unused-vars + setTexture(mesh, textureData, transition = false) { // eslint-disable-line no-unused-vars throw new PSVError('setTexture not implemented'); } @@ -103,4 +106,13 @@ export class AbstractAdapter { throw new PSVError('setTextureOpacity not implemented'); } + /** + * @abstract + * @summary Clear a loaded texture from memory + * @param {PSV.TextureData} textureData + */ + disposeTexture(textureData) { // eslint-disable-line no-unused-vars + throw new PSVError('disposeTexture not implemented'); + } + } diff --git a/src/adapters/cubemap-tiles/index.js b/src/adapters/cubemap-tiles/index.js index f21be9496..37a78472e 100644 --- a/src/adapters/cubemap-tiles/index.js +++ b/src/adapters/cubemap-tiles/index.js @@ -52,6 +52,10 @@ const NB_VERTICES_BY_PLANE = NB_VERTICES_BY_FACE * CUBE_SEGMENTS * CUBE_SEGMENTS const NB_VERTICES = 6 * NB_VERTICES_BY_PLANE; const NB_GROUPS_BY_FACE = CUBE_SEGMENTS * CUBE_SEGMENTS; +const ATTR_UV = 'uv'; +const ATTR_ORIGINAL_UV = 'originaluv'; +const ATTR_POSITION = 'position'; + function tileId(tile) { return `${tile.face}:${tile.col}x${tile.row}`; } @@ -67,8 +71,6 @@ const vertexPosition = new THREE.Vector3(); export class CubemapTilesAdapter extends CubemapAdapter { static id = 'cubemap-tiles'; - static supportsTransition = false; - static supportsPreload = false; static supportsDownload = false; /** @@ -89,12 +91,6 @@ export class CubemapTilesAdapter extends CubemapAdapter { ...options, }; - /** - * @member {external:THREE.MeshBasicMaterial[]} - * @private - */ - this.materials = []; - /** * @member {PSV.adapters.Queue} * @private @@ -107,7 +103,7 @@ export class CubemapTilesAdapter extends CubemapAdapter { * @property {int} facesByTile - number of mesh faces by tile * @property {Record} tiles - loaded tiles * @property {external:THREE.BoxGeometry} geom - * @property {*} originalUvs + * @property {external:THREE.MeshBasicMaterial[]} materials * @property {external:THREE.MeshBasicMaterial} errorMaterial * @private */ @@ -116,7 +112,7 @@ export class CubemapTilesAdapter extends CubemapAdapter { facesByTile : 0, tiles : {}, geom : null, - originalUvs : null, + materials : [], errorMaterial: null, }; @@ -136,6 +132,9 @@ export class CubemapTilesAdapter extends CubemapAdapter { this.psv.on(CONSTANTS.EVENTS.ZOOM_UPDATED, this); } + /** + * @override + */ destroy() { this.psv.off(CONSTANTS.EVENTS.POSITION_UPDATED, this); this.psv.off(CONSTANTS.EVENTS.ZOOM_UPDATED, this); @@ -148,12 +147,14 @@ export class CubemapTilesAdapter extends CubemapAdapter { delete this.queue; delete this.loader; delete this.prop.geom; - delete this.prop.originalUvs; delete this.prop.errorMaterial; super.destroy(); } + /** + * @private + */ handleEvent(e) { /* eslint-disable */ switch (e.type) { @@ -173,11 +174,25 @@ export class CubemapTilesAdapter extends CubemapAdapter { this.queue.clear(); this.prop.tiles = {}; - this.materials.forEach((mat) => { + this.prop.materials.forEach((mat) => { mat?.map?.dispose(); mat?.dispose(); }); - this.materials.length = 0; + this.prop.materials.length = 0; + } + + /** + * @override + */ + supportsTransition(panorama) { + return !!panorama.baseUrl; + } + + /** + * @override + */ + supportsPreload(panorama) { + return !!panorama.baseUrl; } /** @@ -222,53 +237,70 @@ export class CubemapTilesAdapter extends CubemapAdapter { geometry.addGroup(i, NB_VERTICES_BY_FACE, k++); } - this.prop.geom = geometry; - this.prop.originalUvs = geometry.getAttribute('uv').clone(); + geometry.setAttribute(ATTR_ORIGINAL_UV, geometry.getAttribute(ATTR_UV).clone()); - return new THREE.Mesh(geometry, this.materials); + return new THREE.Mesh(geometry, []); } /** * @summary Applies the base texture and starts the loading of tiles * @override */ - setTexture(mesh, textureData) { + setTexture(mesh, textureData, transition) { const { panorama, texture } = textureData; + if (transition) { + this.__setTexture(mesh, texture); + return; + } + this.__cleanup(); + this.__setTexture(mesh, texture); + + this.prop.materials = mesh.material; + this.prop.geom = mesh.geometry; + this.prop.geom.setAttribute(ATTR_UV, this.prop.geom.getAttribute(ATTR_ORIGINAL_UV).clone()); this.prop.tileSize = panorama.faceSize / panorama.nbTiles; this.prop.facesByTile = CUBE_SEGMENTS / panorama.nbTiles; - this.prop.geom.setAttribute('uv', this.prop.originalUvs.clone()); + // this.psv.renderer.scene.add(createWireFrame(this.prop.geom)); + + setTimeout(() => this.__refresh(true)); + } - if (texture) { - for (let i = 0; i < 6; i++) { + /** + * @private + */ + __setTexture(mesh, texture) { + for (let i = 0; i < 6; i++) { + let material; + if (texture) { if (this.config.flipTopBottom && (i === 2 || i === 3)) { texture[i].center = new THREE.Vector2(0.5, 0.5); texture[i].rotation = Math.PI; } - const material = new THREE.MeshBasicMaterial({ map: texture[i] }); - - for (let j = 0; j < NB_GROUPS_BY_FACE; j++) { - this.materials.push(material); - } + material = new THREE.MeshBasicMaterial({ map: texture[i] }); + } + else { + material = new THREE.MeshBasicMaterial({ opacity: 0, transparent: true }); } - } - else { - const material = new THREE.MeshBasicMaterial({ opacity: 0, transparent: true }); - for (let i = 0; i < 6; i++) { - for (let j = 0; j < NB_GROUPS_BY_FACE; j++) { - this.materials.push(material); - } + for (let j = 0; j < NB_GROUPS_BY_FACE; j++) { + mesh.material.push(material); } } + } - // this.psv.renderer.scene.add(createWireFrame(this.prop.geom)); - - setTimeout(() => this.__refresh(true)); + /** + * @override + */ + setTextureOpacity(mesh, opacity) { + for (let i = 0; i < 6; i++) { + mesh.material[i * NB_GROUPS_BY_FACE].opacity = opacity; + mesh.material[i * NB_GROUPS_BY_FACE].transparent = opacity < 1; + } } /** @@ -287,7 +319,7 @@ export class CubemapTilesAdapter extends CubemapAdapter { projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); frustum.setFromProjectionMatrix(projScreenMatrix); - const verticesPosition = this.prop.geom.getAttribute('position'); + const verticesPosition = this.prop.geom.getAttribute(ATTR_POSITION); const tilesToLoad = []; for (let face = 0; face < 6; face++) { @@ -438,7 +470,7 @@ export class CubemapTilesAdapter extends CubemapAdapter { * @private */ __swapMaterial(face, col, row, material) { - const uvs = this.prop.geom.getAttribute('uv'); + const uvs = this.prop.geom.getAttribute(ATTR_UV); for (let c = 0; c < this.prop.facesByTile; c++) { for (let r = 0; r < this.prop.facesByTile; r++) { @@ -451,7 +483,7 @@ export class CubemapTilesAdapter extends CubemapAdapter { // swap material const matIndex = this.prop.geom.groups.find(g => g.start === firstVertex).materialIndex; - this.materials[matIndex] = material; + this.prop.materials[matIndex] = material; // define new uvs let top = 1 - r / this.prop.facesByTile; diff --git a/src/adapters/cubemap/index.js b/src/adapters/cubemap/index.js index 30c32b0fb..c5228c000 100644 --- a/src/adapters/cubemap/index.js +++ b/src/adapters/cubemap/index.js @@ -32,8 +32,6 @@ export const CUBE_HASHMAP = ['left', 'right', 'top', 'bottom', 'back', 'front']; export class CubemapAdapter extends AbstractAdapter { static id = 'cubemap'; - static supportsTransition = true; - static supportsPreload = true; static supportsDownload = false; /** @@ -53,6 +51,20 @@ export class CubemapAdapter extends AbstractAdapter { }; } + /** + * @override + */ + supportsTransition() { + return true; + } + + /** + * @override + */ + supportsPreload() { + return true; + } + /** * @override * @param {string[] | PSV.adapters.CubemapAdapter.Cubemap} panorama @@ -157,15 +169,12 @@ export class CubemapAdapter extends AbstractAdapter { const { texture } = textureData; for (let i = 0; i < 6; i++) { - if (mesh.material[i].map) { - mesh.material[i].map.dispose(); - } - if (this.config.flipTopBottom && (i === 2 || i === 3)) { texture[i].center = new THREE.Vector2(0.5, 0.5); texture[i].rotation = Math.PI; } + mesh.material[i].map?.dispose(); mesh.material[i].map = texture[i]; } } @@ -180,4 +189,11 @@ export class CubemapAdapter extends AbstractAdapter { } } + /** + * @override + */ + disposeTexture(textureData) { + textureData.texture?.forEach(texture => texture.dispose()); + } + } diff --git a/src/adapters/equirectangular-tiles/index.js b/src/adapters/equirectangular-tiles/index.js index 45fcd8c42..bb169d42b 100644 --- a/src/adapters/equirectangular-tiles/index.js +++ b/src/adapters/equirectangular-tiles/index.js @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { AbstractAdapter, CONSTANTS, PSVError, utils } from '../..'; +import { CONSTANTS, EquirectangularAdapter, PSVError, utils } from '../..'; import { Queue } from '../tiles-shared/Queue'; import { Task } from '../tiles-shared/Task'; import { buildErrorMaterial, createBaseTexture } from '../tiles-shared/utils'; @@ -36,7 +36,7 @@ import { buildErrorMaterial, createBaseTexture } from '../tiles-shared/utils'; * @private * @property {int} col * @property {int} row - * @property {int} angle + * @property {float} angle */ /* the faces of the top and bottom rows are made of a single triangle (3 vertices) @@ -71,6 +71,10 @@ import { buildErrorMaterial, createBaseTexture } from '../tiles-shared/utils'; * ⋁ */ +const ATTR_UV = 'uv'; +const ATTR_ORIGINAL_UV = 'originaluv'; +const ATTR_POSITION = 'position'; + function tileId(tile) { return `${tile.col}x${tile.row}`; } @@ -84,11 +88,9 @@ const vertexPosition = new THREE.Vector3(); * @summary Adapter for tiled panoramas * @memberof PSV.adapters */ -export class EquirectangularTilesAdapter extends AbstractAdapter { +export class EquirectangularTilesAdapter extends EquirectangularAdapter { static id = 'equirectangular-tiles'; - static supportsTransition = false; - static supportsPreload = false; static supportsDownload = false; /** @@ -98,6 +100,8 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { constructor(psv, options) { super(psv); + this.psv.config.useXmpData = false; + /** * @member {PSV.adapters.EquirectangularTilesAdapter.Options} * @private @@ -121,12 +125,6 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { + (this.SPHERE_HORIZONTAL_SEGMENTS - 2) * this.SPHERE_SEGMENTS * this.NB_VERTICES_BY_FACE; this.NB_GROUPS = this.SPHERE_SEGMENTS * this.SPHERE_HORIZONTAL_SEGMENTS; - /** - * @member {external:THREE.MeshBasicMaterial[]} - * @private - */ - this.materials = []; - /** * @member {PSV.adapters.Queue} * @private @@ -141,7 +139,7 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { * @property {int} facesByRow - number of mesh faces by row * @property {Record} tiles - loaded tiles * @property {external:THREE.SphereGeometry} geom - * @property {*} originalUvs + * @property {external:THREE.MeshBasicMaterial[]} materials * @property {external:THREE.MeshBasicMaterial} errorMaterial * @private */ @@ -152,7 +150,7 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { facesByRow : 0, tiles : {}, geom : null, - originalUvs : null, + materials : [], errorMaterial: null, }; @@ -172,6 +170,9 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { this.psv.on(CONSTANTS.EVENTS.ZOOM_UPDATED, this); } + /** + * @override + */ destroy() { this.psv.off(CONSTANTS.EVENTS.POSITION_UPDATED, this); this.psv.off(CONSTANTS.EVENTS.ZOOM_UPDATED, this); @@ -184,12 +185,14 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { delete this.queue; delete this.loader; delete this.prop.geom; - delete this.prop.originalUvs; delete this.prop.errorMaterial; super.destroy(); } + /** + * @private + */ handleEvent(e) { /* eslint-disable */ switch (e.type) { @@ -209,11 +212,25 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { this.queue.clear(); this.prop.tiles = {}; - this.materials.forEach((mat) => { + this.prop.materials.forEach((mat) => { mat?.map?.dispose(); mat?.dispose(); }); - this.materials.length = 0; + this.prop.materials.length = 0; + } + + /** + * @override + */ + supportsTransition(panorama) { + return !!panorama.baseUrl; + } + + /** + * @override + */ + supportsPreload(panorama) { + return !!panorama.baseUrl; } /** @@ -245,11 +262,12 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { }; if (panorama.baseUrl) { - return this.psv.textureLoader.loadImage(panorama.baseUrl, p => this.psv.loader.setProgress(p)) - .then((img) => { - const texture = this.__createBaseTexture(img); - return { panorama, texture, panoData }; - }); + return super.loadTexture(panorama.baseUrl) + .then(textureData => ({ + panorama: panorama, + texture : textureData.texture, + panoData: panoData, + })); } else { return Promise.resolve({ panorama, panoData }); @@ -280,28 +298,44 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { geometry.addGroup(i, this.NB_VERTICES_BY_SMALL_FACE, k++); } - this.prop.geom = geometry; - this.prop.originalUvs = geometry.getAttribute('uv').clone(); + geometry.setAttribute(ATTR_ORIGINAL_UV, geometry.getAttribute(ATTR_UV).clone()); - return new THREE.Mesh(geometry, this.materials); + return new THREE.Mesh(geometry, []); } /** * @summary Applies the base texture and starts the loading of tiles * @override */ - setTexture(mesh, textureData) { + setTexture(mesh, textureData, transition) { const { panorama, texture } = textureData; + if (transition) { + this.__setTexture(mesh, texture); + return; + } + this.__cleanup(); + this.__setTexture(mesh, texture); + + this.prop.materials = mesh.material; + this.prop.geom = mesh.geometry; + this.prop.geom.setAttribute(ATTR_UV, this.prop.geom.getAttribute(ATTR_ORIGINAL_UV).clone()); this.prop.colSize = panorama.width / panorama.cols; this.prop.rowSize = panorama.width / 2 / panorama.rows; this.prop.facesByCol = this.SPHERE_SEGMENTS / panorama.cols; this.prop.facesByRow = this.SPHERE_HORIZONTAL_SEGMENTS / panorama.rows; - this.prop.geom.setAttribute('uv', this.prop.originalUvs.clone()); + // this.psv.renderer.scene.add(createWireFrame(this.prop.geom)); + + setTimeout(() => this.__refresh(true)); + } + /** + * @private + */ + __setTexture(mesh, texture) { let material; if (texture) { material = new THREE.MeshBasicMaterial({ map: texture }); @@ -311,12 +345,16 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { } for (let i = 0; i < this.NB_GROUPS; i++) { - this.materials.push(material); + mesh.material.push(material); } + } - // this.psv.renderer.scene.add(createWireFrame(this.prop.geom)); - - setTimeout(() => this.__refresh(true)); + /** + * @override + */ + setTextureOpacity(mesh, opacity) { + mesh.material[0].opacity = opacity; + mesh.material[0].transparent = opacity < 1; } /** @@ -336,7 +374,7 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); frustum.setFromProjectionMatrix(projScreenMatrix); - const verticesPosition = this.prop.geom.getAttribute('position'); + const verticesPosition = this.prop.geom.getAttribute(ATTR_POSITION); const tilesToLoad = []; for (let col = 0; col < panorama.cols; col++) { @@ -564,7 +602,7 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { * @private */ __swapMaterial(col, row, material) { - const uvs = this.prop.geom.getAttribute('uv'); + const uvs = this.prop.geom.getAttribute(ATTR_UV); for (let c = 0; c < this.prop.facesByCol; c++) { for (let r = 0; r < this.prop.facesByRow; r++) { @@ -592,7 +630,7 @@ export class EquirectangularTilesAdapter extends AbstractAdapter { // swap material const matIndex = this.prop.geom.groups.find(g => g.start === firstVertex).materialIndex; - this.materials[matIndex] = material; + this.prop.materials[matIndex] = material; // define new uvs const top = 1 - r / this.prop.facesByRow; diff --git a/src/adapters/equirectangular/index.js b/src/adapters/equirectangular/index.js index 9473e3b5e..56795194e 100644 --- a/src/adapters/equirectangular/index.js +++ b/src/adapters/equirectangular/index.js @@ -19,8 +19,6 @@ import { AbstractAdapter } from '../AbstractAdapter'; export class EquirectangularAdapter extends AbstractAdapter { static id = 'equirectangular'; - static supportsTransition = true; - static supportsPreload = true; static supportsDownload = true; /** @@ -47,6 +45,20 @@ export class EquirectangularAdapter extends AbstractAdapter { this.SPHERE_HORIZONTAL_SEGMENTS = this.SPHERE_SEGMENTS / 2; } + /** + * @override + */ + supportsTransition() { + return true; + } + + /** + * @override + */ + supportsPreload() { + return true; + } + /** * @override * @param {string} panorama @@ -89,7 +101,7 @@ export class EquirectangularAdapter extends AbstractAdapter { logWarn(`Invalid panoData, croppedWidth and/or croppedHeight is not coherent with loaded image. panoData: ${panoData.croppedWidth}x${panoData.croppedHeight}, image: ${img.width}x${img.height}`); } - if (panoData.fullWidth !== panoData.fullHeight * 2) { + if ((newPanoData || xmpPanoData) && panoData.fullWidth !== panoData.fullHeight * 2) { logWarn('Invalid panoData, fullWidth should be twice fullHeight'); } @@ -207,10 +219,7 @@ export class EquirectangularAdapter extends AbstractAdapter { setTexture(mesh, textureData) { const { texture } = textureData; - if (mesh.material.map) { - mesh.material.map.dispose(); - } - + mesh.material.map?.dispose(); mesh.material.map = texture; } @@ -222,4 +231,11 @@ export class EquirectangularAdapter extends AbstractAdapter { mesh.material.transparent = opacity < 1; } + /** + * @override + */ + disposeTexture(textureData) { + textureData.texture?.dispose(); + } + } diff --git a/src/data/constants.js b/src/data/constants.js index 35a904fab..cd5006ba3 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -2,6 +2,14 @@ * @namespace PSV.constants */ +/** + * @summary Default duration of the transition between panoramas + * @memberOf PSV.constants + * @type {number} + * @constant + */ +export const DEFAULT_TRANSITION = 1500; + /** * @summary Number of pixels bellow which a mouse move will be considered as a click * @memberOf PSV.constants @@ -66,6 +74,14 @@ export const SPHERE_RADIUS = 10; */ export const VIEWER_DATA = 'photoSphereViewer'; +/** + * @summary Property added the the main Mesh object + * @memberOf PSV.constants + * @type {string} + * @constant + */ +export const MESH_USER_DATA = 'psvSphere'; + /** * @summary Available actions * @memberOf PSV.constants diff --git a/src/plugins/virtual-tour/index.js b/src/plugins/virtual-tour/index.js index 723abe91f..053c34a71 100644 --- a/src/plugins/virtual-tour/index.js +++ b/src/plugins/virtual-tour/index.js @@ -94,6 +94,7 @@ import { bearing, distance, setMeshColor } from './utils'; * @property {string} [startNodeId] - id of the initial node, if not defined the first node will be used * @property {boolean|PSV.plugins.VirtualTourPlugin.Preload} [preload=false] - preload linked panoramas * @property {boolean|string|number} [rotateSpeed='20rpm'] - speed of rotation when clicking on a link, if 'false' the viewer won't rotate at all + * @property {boolean|number} [transition=1500] - duration of the transition between nodes * @property {boolean} [linksOnCompass] - if the Compass plugin is enabled, displays the links on the compass, defaults to `true` on in markers render mode * @property {PSV.plugins.MarkersPlugin.Properties} [markerStyle] - global marker style * @property {PSV.plugins.VirtualTourPlugin.ArrowStyle} [arrowStyle] - global arrow style @@ -164,6 +165,7 @@ export class VirtualTourPlugin extends AbstractPlugin { renderMode : MODE_3D, preload : false, rotateSpeed : '20rpm', + transition : CONSTANTS.DEFAULT_TRANSITION, markerLatOffset: -0.1, arrowPosition : 'bottom', linksOnCompass : options?.renderMode === MODE_MARKERS, @@ -400,18 +402,14 @@ export class VirtualTourPlugin extends AbstractPlugin { Promise.resolve(this.preload[nodeId]) .then(() => { if (this.prop.loadingNode !== nodeId) { - return Promise.reject(utils.getAbortError()); + throw utils.getAbortError(); } - this.psv.textureLoader.abortLoading(); return this.datasource.loadNode(nodeId); }), Promise.resolve(fromLinkPosition ? this.config.rotateSpeed : false) - .then((speed) => { - if (!speed) { - return Promise.resolve(); - } - else { + .then((speed) => { // eslint-disable-line consistent-return + if (speed) { return this.psv.animate({ ...fromLinkPosition, speed }); } }) @@ -421,7 +419,7 @@ export class VirtualTourPlugin extends AbstractPlugin { ]) .then(([node]) => { if (this.prop.loadingNode !== nodeId) { - return Promise.reject(utils.getAbortError()); + throw utils.getAbortError(); } this.prop.currentNode = node; @@ -440,20 +438,22 @@ export class VirtualTourPlugin extends AbstractPlugin { return Promise.all([ this.psv.setPanorama(node.panorama, { + transition : this.config.transition, caption : node.caption, panoData : node.panoData, sphereCorrection: node.sphereCorrection, }) - .catch((err) => { - // the error is already displayed by the core - return Promise.reject(utils.isAbortError(err) ? err : null); + .then((completed) => { + if (!completed) { + throw utils.getAbortError(); + } }), this.datasource.loadLinkedNodes(nodeId), ]); }) .then(() => { if (this.prop.loadingNode !== nodeId) { - return Promise.reject(utils.getAbortError()); + throw utils.getAbortError(); } const node = this.prop.currentNode; @@ -489,18 +489,17 @@ export class VirtualTourPlugin extends AbstractPlugin { }) .catch((err) => { if (utils.isAbortError(err)) { - return Promise.resolve(false); - } - else if (err) { - this.psv.showError(this.psv.config.lang.loadError); + return false; } + this.psv.showError(this.psv.config.lang.loadError); + this.psv.loader.hide(); this.psv.navbar.setCaption(''); this.prop.loadingNode = null; - return Promise.reject(err); + throw err; }); } @@ -646,12 +645,11 @@ export class VirtualTourPlugin extends AbstractPlugin { * @private */ __positionArrows() { - const isBottom = this.config.arrowPosition === 'bottom'; - this.arrowsGroup.position.copy(this.psv.prop.direction).multiplyScalar(0.5); const s = this.config.arrowStyle.scale; - const f = s[1] + (s[0] - s[1]) * CONSTANTS.EASINGS.linear(this.psv.getZoomLevel() / 100); - this.arrowsGroup.position.y += isBottom ? -1.5 : 1.5; + const f = s[1] + (s[0] - s[1]) * (this.psv.getZoomLevel() / 100); + const y = 2.5 - (this.psv.getZoomLevel() / 100) * 1.5; + this.arrowsGroup.position.y += this.config.arrowPosition === 'bottom' ? -y : y; this.arrowsGroup.scale.set(f, f, f); } diff --git a/src/services/DataHelper.js b/src/services/DataHelper.js index 8f5d7aaad..a07e75858 100644 --- a/src/services/DataHelper.js +++ b/src/services/DataHelper.js @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { SPHERE_RADIUS } from '../data/constants'; +import { MESH_USER_DATA, SPHERE_RADIUS } from '../data/constants'; import { PSVError } from '../PSVError'; import { applyEulerInverse, parseAngle, parseSpeed } from '../utils'; import { AbstractService } from './AbstractService'; @@ -166,7 +166,7 @@ export class DataHelper extends AbstractService { * @returns {external:THREE.Vector3} */ viewerCoordsToVector3(viewerPoint) { - const sphereIntersect = this.getIntersections(viewerPoint).filter(i => i.object.userData.psvSphere); + const sphereIntersect = this.getIntersections(viewerPoint).filter(i => i.object.userData[MESH_USER_DATA]); if (sphereIntersect) { return sphereIntersect.point; diff --git a/src/services/EventsHandler.js b/src/services/EventsHandler.js index bac8b3611..9b2dffd00 100644 --- a/src/services/EventsHandler.js +++ b/src/services/EventsHandler.js @@ -640,8 +640,10 @@ export class EventsHandler extends AbstractService { onTick : (properties) => { this.__move(properties, false); }, - }) - .finally(() => { + }); + + this.prop.animationPromise + .then(() => { this.state.moving = false; }); } diff --git a/src/services/Renderer.js b/src/services/Renderer.js index 5facdbb4d..65baa2281 100644 --- a/src/services/Renderer.js +++ b/src/services/Renderer.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; import { Animation } from '../Animation'; -import { EVENTS, SPHERE_RADIUS } from '../data/constants'; +import { EVENTS, MESH_USER_DATA, SPHERE_RADIUS } from '../data/constants'; import { SYSTEM } from '../data/system'; import { each, isExtendedPosition } from '../utils'; import { AbstractService } from './AbstractService'; @@ -47,7 +47,7 @@ export class Renderer extends AbstractService { * @protected */ this.mesh = this.psv.adapter.createMesh(); - this.mesh.userData = { psvSphere: true }; + this.mesh.userData = { [MESH_USER_DATA]: true }; /** * @member {external:THREE.Group} @@ -292,10 +292,10 @@ export class Renderer extends AbstractService { const positionProvided = isExtendedPosition(options); const zoomProvided = 'zoom' in options; + // create temp group and new mesh, half size to be in "front" of the first one const group = new THREE.Group(); - const mesh = this.psv.adapter.createMesh(0.5); - this.psv.adapter.setTexture(mesh, textureData); + this.psv.adapter.setTexture(mesh, textureData, true); this.psv.adapter.setTextureOpacity(mesh, 0); this.setPanoramaPose(textureData.panoData, mesh); this.setSphereCorrection(options.sphereCorrection, group); @@ -316,9 +316,8 @@ export class Renderer extends AbstractService { group.add(mesh); this.scene.add(group); - this.psv.needsUpdate(); - return new Animation({ + const animation = new Animation({ properties: { opacity: { start: 0.0, end: 1.0 }, zoom : zoomProvided ? { start: this.psv.getZoomLevel(), end: options.zoom } : undefined, @@ -335,23 +334,32 @@ export class Renderer extends AbstractService { this.psv.needsUpdate(); }, - }) - .then(() => { - // remove temp sphere and transfer the texture to the main sphere - this.setTexture(textureData); - this.psv.adapter.setTextureOpacity(this.mesh, 1); - this.setPanoramaPose(textureData.panoData); - this.setSphereCorrection(options.sphereCorrection); + }); + + animation + .then((completed) => { + if (completed) { + // remove temp sphere and transfer the texture to the main mesh + this.setTexture(textureData); + this.psv.adapter.setTextureOpacity(this.mesh, 1); + this.setPanoramaPose(textureData.panoData); + this.setSphereCorrection(options.sphereCorrection); + + // actually rotate the camera + if (positionProvided) { + this.psv.rotate(options); + } + } + else { + this.psv.adapter.disposeTexture(textureData); + } this.scene.remove(group); mesh.geometry.dispose(); mesh.geometry = null; - - // actually rotate the camera - if (positionProvided) { - this.psv.rotate(options); - } }); + + return animation; } /** diff --git a/src/services/TextureLoader.js b/src/services/TextureLoader.js index 018eed57d..52515b87b 100644 --- a/src/services/TextureLoader.js +++ b/src/services/TextureLoader.js @@ -121,7 +121,7 @@ export class TextureLoader extends AbstractService { * @returns {Promise} */ preloadPanorama(panorama) { - if (this.psv.adapter.constructor.supportsPreload) { + if (this.psv.adapter.supportsPreload(panorama)) { return this.psv.adapter.loadTexture(panorama); } else { diff --git a/tests/types/index.ts b/tests/types/index.ts index dee222e4e..baf21e933 100644 --- a/tests/types/index.ts +++ b/tests/types/index.ts @@ -1,6 +1,9 @@ -import { CONSTANTS, Viewer } from 'photo-sphere-viewer'; -import { EquirectangularTilesAdapter, EquirectangularTilesPanorama } from 'photo-sphere-viewer/dist/adapters/equirectangular-tiles'; -import { MarkersPlugin, MarkersPluginOptions, EVENTS as MAKER_EVENTS } from 'photo-sphere-viewer/dist/plugins/markers'; +import { Animation, CONSTANTS, Viewer } from 'photo-sphere-viewer'; +import { + EquirectangularTilesAdapter, + EquirectangularTilesPanorama +} from 'photo-sphere-viewer/dist/adapters/equirectangular-tiles'; +import { EVENTS as MAKER_EVENTS, MarkersPlugin, MarkersPluginOptions } from 'photo-sphere-viewer/dist/plugins/markers'; import { CustomPlugin } from './CustomPlugin'; const viewer = new Viewer({ @@ -75,3 +78,15 @@ customPlugin.doSomething(); const customPluginAgain = viewer.getPlugin('custom'); customPluginAgain.doSomething(); + +const anim = new Animation({ + duration: 1000, + properties: { + foo: {start: 0, end: 1}, + }, + onTick: (properties) => { + console.log(properties.foo); + } +}); + +anim.then(completed => console.log(completed)); diff --git a/types/Animation.d.ts b/types/Animation.d.ts index af6f5cff0..1a9a97fd5 100644 --- a/types/Animation.d.ts +++ b/types/Animation.d.ts @@ -1,35 +1,43 @@ -export type AnimationOptions = { - properties: { [K: string]: { start: number, end: number } }; +export type AnimationOptions = { + properties: { [key in keyof T]: { start: number, end: number } }; duration: number; delay?: number; easing?: string | ((progress: number) => number); - onTick: (properties: { [K: string]: number }, progress: number) => void; + onTick: (properties: { [key in keyof T]: number }, progress: number) => void; }; /** * @summary Interpolation helper for animations * @description - * Implements the Promise API with an additional "cancel" and "finally" methods. + * Implements the Promise API with an additional "cancel" method. * The promise is resolved when the animation is complete and rejected if the animation is cancelled. */ -export class Animation implements Promise { +export class Animation implements PromiseLike { - constructor(options: AnimationOptions); + constructor(options: AnimationOptions); - // @ts-ignore - then(onFulfilled?: (() => void | Animation | PromiseLike) | undefined | null, onRejected?: (() => void | Animation | PromiseLike) | undefined | null): Animation; + then(onFulfilled?: ((completed: boolean) => TResult | PromiseLike) | undefined | null): PromiseLike; - // @ts-ignore - catch(onRejected?: (() => void | Animation | PromiseLike) | undefined | null): Animation; + cancel(); - // @ts-ignore - finally(onFinally?: (() => void | Animation | PromiseLike) | undefined | null): Animation; + /** + * @deprecated does not accept a rejection handler anymore + */ + then(onFulfilled?: (() => void | Animation | PromiseLike) | undefined | null, onRejected?: (() => void | Animation | PromiseLike) | undefined | null): Animation; - cancel(); + /** + * @deprecated not supported anymore + */ + catch(onRejected?: (() => void | Animation | PromiseLike) | undefined | null): Animation; + + /** + * @deprecated not supported anymore + */ + finally(onFinally?: (() => void | Animation | PromiseLike) | undefined | null): Animation; /** - * @summary Returns a resolved animation promise + * @deprecated not supported anymore */ - static resolve(): Animation; + static resolve(): Animation; } diff --git a/types/Viewer.d.ts b/types/Viewer.d.ts index 458f2552d..9fb400e46 100644 --- a/types/Viewer.d.ts +++ b/types/Viewer.d.ts @@ -79,7 +79,7 @@ export type ViewerProps = { hFov: number; aspect: number; autorotateEnabled: boolean; - animationPromise: Animation; + animationPromise: Animation; loadingPromise: Promise; startTimeout: any; size: Size; @@ -236,7 +236,7 @@ export class Viewer extends EventEmitter { /** * @summary Rotates and zooms the view with a smooth animation */ - animate(options: AnimateOptions): Animation; + animate(options: AnimateOptions): Animation; /** * @summary Stops the ongoing animation diff --git a/types/adapters/AbstractAdapter.d.ts b/types/adapters/AbstractAdapter.d.ts index cdc1ed3df..3742cbf69 100644 --- a/types/adapters/AbstractAdapter.d.ts +++ b/types/adapters/AbstractAdapter.d.ts @@ -14,14 +14,9 @@ export abstract class AbstractAdapter { static id: string; /** - * @summary Indicates if the adapter supports transitions between panoramas - */ - static supportsTransition: boolean; - - /** - * @summary Indicates if the adapter supports preload + * @summary Indicates if the adapter supports panorama download natively */ - static supportsPreload: boolean; + static supportsDownload: boolean; constructor(parent: Viewer); @@ -30,6 +25,16 @@ export abstract class AbstractAdapter { */ destroy(); + /** + * @summary Indicates if the adapter supports transitions between panoramas + */ + supportsTransition(panorama: T): boolean; + + /** + * @summary Indicates if the adapter supports preload of a panorama + */ + supportsPreload(panorama: T): boolean; + /** * @summary Loads the panorama texture(s) */ @@ -44,13 +49,18 @@ export abstract class AbstractAdapter { /** * @summary Applies the texture to the mesh */ - setTexture(mesh: Mesh, textureData: TextureData); + setTexture(mesh: Mesh, textureData: TextureData, transition?: boolean); /** * @summary Changes the opacity of the mesh */ setTextureOpacity(mesh: Mesh, opacity: number); + /** + * @abstract + */ + disposeTexture(textureData: TextureData); + } export type AdapterConstructor> = new (psv: Viewer, options?: any) => T; diff --git a/types/plugins/markers/index.d.ts b/types/plugins/markers/index.d.ts index 2416c5329..c8b2009f7 100644 --- a/types/plugins/markers/index.d.ts +++ b/types/plugins/markers/index.d.ts @@ -209,7 +209,7 @@ export class MarkersPlugin extends AbstractPlugin { /** * @summary Rotate the view to face the marker */ - gotoMarker(markerId: string, speed: string | number): Animation; + gotoMarker(markerId: string, speed: string | number): Animation; /** * @summary Hides a marker diff --git a/types/plugins/virtual-tour/index.d.ts b/types/plugins/virtual-tour/index.d.ts index 6869883b9..74ad3e747 100644 --- a/types/plugins/virtual-tour/index.d.ts +++ b/types/plugins/virtual-tour/index.d.ts @@ -64,6 +64,7 @@ export type VirtualTourPluginPluginOptions = { startNodeId?: string; preload?: boolean | ((node: VirtualTourNode, link: VirtualTourNodeLink) => boolean); rotateSpeed?: boolean | string | number; + transition?: boolean | number; markerStyle?: MarkerProperties; arrowStyle?: VirtualTourArrowStyle; markerLatOffset?: number; From e62e66782c8178abdb3c47e745e2a0e90a3c7ce0 Mon Sep 17 00:00:00 2001 From: Damien Sorel Date: Fri, 4 Mar 2022 13:11:27 +0100 Subject: [PATCH 17/17] doc: improve playground --- docs/.vuepress/components/Playground.vue | 54 ++++++++++++++++++------ 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/docs/.vuepress/components/Playground.vue b/docs/.vuepress/components/Playground.vue index 0e6bb537c..ae14e945e 100644 --- a/docs/.vuepress/components/Playground.vue +++ b/docs/.vuepress/components/Playground.vue @@ -297,16 +297,24 @@
- + - + + +
@@ -342,14 +350,14 @@
-
+
-
+
-
+
+ + + + front + horizontal + vertical-left + vertical-tight + + +
+ +
@@ -376,14 +396,14 @@
-
+
-
+
@@ -547,6 +567,7 @@ polylineRad: null, width : null, height : null, + orientation: null, anchor : null, listContent: null, content : null, @@ -575,6 +596,7 @@ this.CLIPBOARD_AVAILABLE = false; // SSR rendering dissallow "window" usage here this.PIN_RED_URL = 'https://photo-sphere-viewer.js.org/assets/pin-red.png'; this.PIN_BLUE_URL = 'https://photo-sphere-viewer.js.org/assets/pin-blue.png'; + this.TARGET_URL = 'https://photo-sphere-viewer.js.org/assets/target.png'; this.FONT_SIZES = range(10, 31).map(i => `${i}px`); this.FONT_SIZES_2 = range(10, 31, 5).map(i => `${i}px`); }, @@ -734,6 +756,7 @@ this.cancelMarker(); this.markerForm.id = 'marker-' + Math.random().toString(36).slice(2); this.markerForm.type = type; + this.markerForm.orientation = 'front'; this.markerForm.anchor = 'center center'; this.markerForm.tooltip.position = 'top center'; @@ -745,10 +768,9 @@ this.markerForm.anchor = 'bottom center'; break; case 'imageLayer': - this.markerForm.imageLayer = this.PIN_RED_URL; - this.markerForm.width = 48; - this.markerForm.height = 48; - this.markerForm.anchor = 'bottom center'; + this.markerForm.imageLayer = this.TARGET_URL; + this.markerForm.width = 120; + this.markerForm.height = 120; break; case 'html': this.markerForm.html = 'Test content'; @@ -958,6 +980,12 @@ if (marker.config.height) { m.height = marker.config.height; } + if (marker.config.anchor !== 'center center') { + m.anchor = marker.config.anchor; + } + if (marker.config.orientation !== 'front') { + m.orientation = marker.config.orientation; + } if (marker.config.tooltip.content) { m.tooltip = marker.config.tooltip; }