senangwebs-tour 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +653 -0
- package/dist/swt-editor.css +869 -0
- package/dist/swt-editor.css.map +1 -0
- package/dist/swt-editor.js +2853 -0
- package/dist/swt-editor.js.map +1 -0
- package/dist/swt-editor.min.css +1 -0
- package/dist/swt-editor.min.js +1 -0
- package/dist/swt.js +873 -0
- package/dist/swt.js.map +1 -0
- package/dist/swt.min.js +1 -0
- package/package.json +39 -0
- package/src/AssetManager.js +153 -0
- package/src/HotspotManager.js +193 -0
- package/src/SceneManager.js +162 -0
- package/src/components/hotspot-listener.js +114 -0
- package/src/editor/css/main.css +1002 -0
- package/src/editor/editor-entry.css +4 -0
- package/src/editor/editor-entry.js +30 -0
- package/src/editor/js/editor.js +629 -0
- package/src/editor/js/export-manager.js +286 -0
- package/src/editor/js/hotspot-editor.js +237 -0
- package/src/editor/js/preview-controller.js +556 -0
- package/src/editor/js/scene-manager.js +202 -0
- package/src/editor/js/storage-manager.js +193 -0
- package/src/editor/js/ui-controller.js +387 -0
- package/src/editor/js/ui-init.js +164 -0
- package/src/editor/js/utils.js +217 -0
- package/src/index.js +245 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SceneManager - Handles scene transitions and <a-sky> management
|
|
3
|
+
*/
|
|
4
|
+
export class SceneManager {
|
|
5
|
+
constructor(sceneEl, assetManager) {
|
|
6
|
+
this.sceneEl = sceneEl;
|
|
7
|
+
this.assetManager = assetManager;
|
|
8
|
+
this.skyEl = null;
|
|
9
|
+
this.currentSceneId = null;
|
|
10
|
+
this.transitionDuration = 500; // milliseconds
|
|
11
|
+
this.init();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
init() {
|
|
15
|
+
// Find or create <a-sky> element
|
|
16
|
+
this.skyEl = this.sceneEl.querySelector('a-sky');
|
|
17
|
+
if (!this.skyEl) {
|
|
18
|
+
this.skyEl = document.createElement('a-sky');
|
|
19
|
+
this.skyEl.setAttribute('id', 'swt-sky');
|
|
20
|
+
this.sceneEl.appendChild(this.skyEl);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Load a scene by its ID
|
|
26
|
+
* @param {string} sceneId - The scene ID
|
|
27
|
+
* @param {Object} sceneData - The scene configuration object
|
|
28
|
+
* @returns {Promise} - Resolves when the scene is loaded
|
|
29
|
+
*/
|
|
30
|
+
async loadScene(sceneId, sceneData) {
|
|
31
|
+
this.currentSceneId = sceneId;
|
|
32
|
+
|
|
33
|
+
// Preload the panorama
|
|
34
|
+
const panoramaUrl = sceneData.panorama;
|
|
35
|
+
const assetId = `scene-${sceneId}`;
|
|
36
|
+
|
|
37
|
+
const isVideo = this.assetManager.isVideo(panoramaUrl);
|
|
38
|
+
|
|
39
|
+
if (isVideo) {
|
|
40
|
+
await this.assetManager.preloadVideo(panoramaUrl, assetId);
|
|
41
|
+
} else {
|
|
42
|
+
await this.assetManager.preloadImage(panoramaUrl, assetId);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Set the sky source
|
|
46
|
+
this.skyEl.setAttribute('src', `#${assetId}`);
|
|
47
|
+
|
|
48
|
+
// If it's a video, play it
|
|
49
|
+
if (isVideo) {
|
|
50
|
+
const videoEl = this.assetManager.getAsset(assetId);
|
|
51
|
+
if (videoEl && videoEl.play) {
|
|
52
|
+
videoEl.play().catch(err => {
|
|
53
|
+
console.warn('Video autoplay failed:', err);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return Promise.resolve();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Transition to a new scene with fade effect
|
|
63
|
+
* @param {string} sceneId - The target scene ID
|
|
64
|
+
* @param {Object} sceneData - The scene configuration object
|
|
65
|
+
* @returns {Promise} - Resolves when the transition is complete
|
|
66
|
+
*/
|
|
67
|
+
async transitionTo(sceneId, sceneData) {
|
|
68
|
+
// Fade out
|
|
69
|
+
await this.fadeOut();
|
|
70
|
+
|
|
71
|
+
// Load new scene
|
|
72
|
+
await this.loadScene(sceneId, sceneData);
|
|
73
|
+
|
|
74
|
+
// Fade in
|
|
75
|
+
await this.fadeIn();
|
|
76
|
+
|
|
77
|
+
return Promise.resolve();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Fade out the scene
|
|
82
|
+
* @returns {Promise}
|
|
83
|
+
*/
|
|
84
|
+
fadeOut() {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
// Create a fade overlay
|
|
87
|
+
const fadeEl = document.createElement('a-entity');
|
|
88
|
+
fadeEl.setAttribute('id', 'swt-fade');
|
|
89
|
+
fadeEl.setAttribute('geometry', 'primitive: sphere; radius: 1');
|
|
90
|
+
fadeEl.setAttribute('material', 'color: black; opacity: 0; shader: flat; side: back');
|
|
91
|
+
fadeEl.setAttribute('scale', '0.1 0.1 0.1');
|
|
92
|
+
|
|
93
|
+
// Position it at the camera
|
|
94
|
+
const cameraEl = this.sceneEl.querySelector('[camera]');
|
|
95
|
+
if (cameraEl && cameraEl.tagName) {
|
|
96
|
+
fadeEl.setAttribute('position', '0 0 0');
|
|
97
|
+
cameraEl.appendChild(fadeEl);
|
|
98
|
+
} else {
|
|
99
|
+
// Fallback: append to scene
|
|
100
|
+
fadeEl.setAttribute('position', '0 1.6 0');
|
|
101
|
+
this.sceneEl.appendChild(fadeEl);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Animate opacity
|
|
105
|
+
fadeEl.setAttribute('animation', {
|
|
106
|
+
property: 'material.opacity',
|
|
107
|
+
to: 1,
|
|
108
|
+
dur: this.transitionDuration,
|
|
109
|
+
easing: 'easeInQuad'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
resolve(fadeEl);
|
|
114
|
+
}, this.transitionDuration);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Fade in the scene
|
|
120
|
+
* @returns {Promise}
|
|
121
|
+
*/
|
|
122
|
+
fadeIn() {
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
const fadeEl = document.getElementById('swt-fade');
|
|
125
|
+
if (fadeEl) {
|
|
126
|
+
fadeEl.setAttribute('animation', {
|
|
127
|
+
property: 'material.opacity',
|
|
128
|
+
to: 0,
|
|
129
|
+
dur: this.transitionDuration,
|
|
130
|
+
easing: 'easeOutQuad'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
if (fadeEl.parentNode) {
|
|
135
|
+
fadeEl.parentNode.removeChild(fadeEl);
|
|
136
|
+
}
|
|
137
|
+
resolve();
|
|
138
|
+
}, this.transitionDuration);
|
|
139
|
+
} else {
|
|
140
|
+
resolve();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get the current scene ID
|
|
147
|
+
* @returns {string}
|
|
148
|
+
*/
|
|
149
|
+
getCurrentSceneId() {
|
|
150
|
+
return this.currentSceneId;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Clean up the scene manager
|
|
155
|
+
*/
|
|
156
|
+
destroy() {
|
|
157
|
+
if (this.skyEl && this.skyEl.parentNode) {
|
|
158
|
+
this.skyEl.setAttribute('src', '');
|
|
159
|
+
}
|
|
160
|
+
this.currentSceneId = null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A-Frame component that listens for clicks on hotspots and emits a custom event
|
|
3
|
+
*/
|
|
4
|
+
if (typeof AFRAME !== 'undefined') {
|
|
5
|
+
AFRAME.registerComponent('swt-hotspot-listener', {
|
|
6
|
+
schema: {
|
|
7
|
+
hotspotData: { type: 'string', default: '{}' }
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
init: function () {
|
|
11
|
+
this.onClick = this.onClick.bind(this);
|
|
12
|
+
this.onMouseEnter = this.onMouseEnter.bind(this);
|
|
13
|
+
this.onMouseLeave = this.onMouseLeave.bind(this);
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
play: function () {
|
|
17
|
+
this.el.addEventListener('click', this.onClick);
|
|
18
|
+
this.el.addEventListener('mouseenter', this.onMouseEnter);
|
|
19
|
+
this.el.addEventListener('mouseleave', this.onMouseLeave);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
pause: function () {
|
|
23
|
+
this.el.removeEventListener('click', this.onClick);
|
|
24
|
+
this.el.removeEventListener('mouseenter', this.onMouseEnter);
|
|
25
|
+
this.el.removeEventListener('mouseleave', this.onMouseLeave);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
onClick: function (evt) {
|
|
29
|
+
const hotspotData = JSON.parse(this.data.hotspotData);
|
|
30
|
+
|
|
31
|
+
// Emit custom event that the Tour class can listen to
|
|
32
|
+
this.el.sceneEl.emit('swt-hotspot-clicked', {
|
|
33
|
+
hotspotData: hotspotData
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
onMouseEnter: function (evt) {
|
|
38
|
+
const hotspotData = JSON.parse(this.data.hotspotData);
|
|
39
|
+
|
|
40
|
+
// Show tooltip if exists
|
|
41
|
+
if (hotspotData.tooltip) {
|
|
42
|
+
this.el.sceneEl.emit('swt-hotspot-hover', {
|
|
43
|
+
hotspotData: hotspotData,
|
|
44
|
+
isHovering: true
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add visual feedback
|
|
49
|
+
this.el.setAttribute('scale', this.getScaledSize(1.2));
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
onMouseLeave: function (evt) {
|
|
53
|
+
const hotspotData = JSON.parse(this.data.hotspotData);
|
|
54
|
+
|
|
55
|
+
// Hide tooltip
|
|
56
|
+
if (hotspotData.tooltip) {
|
|
57
|
+
this.el.sceneEl.emit('swt-hotspot-hover', {
|
|
58
|
+
hotspotData: hotspotData,
|
|
59
|
+
isHovering: false
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Reset scale
|
|
64
|
+
this.el.setAttribute('scale', this.getOriginalScale());
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
getOriginalScale: function () {
|
|
68
|
+
const hotspotData = JSON.parse(this.data.hotspotData);
|
|
69
|
+
return hotspotData.appearance?.scale || '1 1 1';
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
getScaledSize: function (multiplier) {
|
|
73
|
+
const scale = this.getOriginalScale();
|
|
74
|
+
const parts = scale.split(' ').map(Number);
|
|
75
|
+
return `${parts[0] * multiplier} ${parts[1] * multiplier} ${parts[2] * multiplier}`;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Billboard component - Makes an entity always face the camera
|
|
81
|
+
*/
|
|
82
|
+
AFRAME.registerComponent('swt-billboard', {
|
|
83
|
+
schema: {
|
|
84
|
+
enabled: { type: 'boolean', default: true }
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
init: function () {
|
|
88
|
+
this.camera = null;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
tick: function () {
|
|
92
|
+
if (!this.data.enabled) return;
|
|
93
|
+
|
|
94
|
+
// Find camera if not cached
|
|
95
|
+
if (!this.camera) {
|
|
96
|
+
this.camera = this.el.sceneEl.camera;
|
|
97
|
+
if (!this.camera) return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Get camera world position
|
|
101
|
+
const cameraPosition = new THREE.Vector3();
|
|
102
|
+
this.camera.getWorldPosition(cameraPosition);
|
|
103
|
+
|
|
104
|
+
// Get this element's world position
|
|
105
|
+
const elementPosition = new THREE.Vector3();
|
|
106
|
+
this.el.object3D.getWorldPosition(elementPosition);
|
|
107
|
+
|
|
108
|
+
// Make the element look at the camera
|
|
109
|
+
this.el.object3D.lookAt(cameraPosition);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default 'swt-hotspot-listener';
|