@qispace/vue3-player 0.0.5

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.
Files changed (160) hide show
  1. package/README.md +18 -0
  2. package/dist/components/apartmentChooser/QiApartmentChooser.vue.d.ts +4 -0
  3. package/dist/components/apartmentChooser/QiApartmentChooser.vue.d.ts.map +1 -0
  4. package/dist/components/apartmentChooser/QiApartmentChooserNavigation.vue.d.ts +4 -0
  5. package/dist/components/apartmentChooser/QiApartmentChooserNavigation.vue.d.ts.map +1 -0
  6. package/dist/components/apartmentChooser/QiApartmentChooserRaster.vue.d.ts +4 -0
  7. package/dist/components/apartmentChooser/QiApartmentChooserRaster.vue.d.ts.map +1 -0
  8. package/dist/components/apartmentChooser/QiApartmentChooserRotator.vue.d.ts +4 -0
  9. package/dist/components/apartmentChooser/QiApartmentChooserRotator.vue.d.ts.map +1 -0
  10. package/dist/components/apartmentChooser/QiApartmentChooserSvg.vue.d.ts +4 -0
  11. package/dist/components/apartmentChooser/QiApartmentChooserSvg.vue.d.ts.map +1 -0
  12. package/dist/components/buildingPicker/QiBuildingChooser.vue.d.ts +3 -0
  13. package/dist/components/buildingPicker/QiBuildingChooser.vue.d.ts.map +1 -0
  14. package/dist/components/buildingPicker/QiBuildingChooserRaster.vue.d.ts +4 -0
  15. package/dist/components/buildingPicker/QiBuildingChooserRaster.vue.d.ts.map +1 -0
  16. package/dist/components/buildingPicker/QiBuildingChooserRasterOverlay.vue.d.ts +4 -0
  17. package/dist/components/buildingPicker/QiBuildingChooserRasterOverlay.vue.d.ts.map +1 -0
  18. package/dist/components/buildingPicker/QiBuildingChooserSceneImageStack.vue.d.ts +4 -0
  19. package/dist/components/buildingPicker/QiBuildingChooserSceneImageStack.vue.d.ts.map +1 -0
  20. package/dist/components/buildingPicker/QiBuildingChooserSunSimControl.vue.d.ts +5 -0
  21. package/dist/components/buildingPicker/QiBuildingChooserSunSimControl.vue.d.ts.map +1 -0
  22. package/dist/components/buildingPicker/QiBuildingChooserSvg.vue.d.ts +4 -0
  23. package/dist/components/buildingPicker/QiBuildingChooserSvg.vue.d.ts.map +1 -0
  24. package/dist/components/index.d.ts +4 -0
  25. package/dist/components/shared/QiFloatingCard.vue.d.ts +4 -0
  26. package/dist/components/shared/QiFloatingCard.vue.d.ts.map +1 -0
  27. package/dist/components/shared/QiHoverProbe.vue.d.ts +4 -0
  28. package/dist/components/shared/QiHoverProbe.vue.d.ts.map +1 -0
  29. package/dist/components/shared/QiNorthDirection.vue.d.ts +3 -0
  30. package/dist/components/shared/QiNorthDirection.vue.d.ts.map +1 -0
  31. package/dist/components/shared/QiZoomBox.vue.d.ts +4 -0
  32. package/dist/components/shared/QiZoomBox.vue.d.ts.map +1 -0
  33. package/dist/components/virtualTourV2/Disclaimer.vue.d.ts +4 -0
  34. package/dist/components/virtualTourV2/Disclaimer.vue.d.ts.map +1 -0
  35. package/dist/components/virtualTourV2/FullScreenButton.vue.d.ts +3 -0
  36. package/dist/components/virtualTourV2/FullScreenButton.vue.d.ts.map +1 -0
  37. package/dist/components/virtualTourV2/ManualSlideShow.vue.d.ts +4 -0
  38. package/dist/components/virtualTourV2/ManualSlideShow.vue.d.ts.map +1 -0
  39. package/dist/components/virtualTourV2/ManualSlideShowPlayer.vue.d.ts +4 -0
  40. package/dist/components/virtualTourV2/ManualSlideShowPlayer.vue.d.ts.map +1 -0
  41. package/dist/components/virtualTourV2/SingleImage.vue.d.ts +4 -0
  42. package/dist/components/virtualTourV2/SingleImage.vue.d.ts.map +1 -0
  43. package/dist/components/virtualTourV2/SocialShare.vue.d.ts +4 -0
  44. package/dist/components/virtualTourV2/SocialShare.vue.d.ts.map +1 -0
  45. package/dist/components/virtualTourV2/WaypointCarousel.vue.d.ts +4 -0
  46. package/dist/components/virtualTourV2/WaypointCarousel.vue.d.ts.map +1 -0
  47. package/dist/lib/virtualTour__/ApartmentConfig.d.ts +27 -0
  48. package/dist/lib/virtualTour__/ApartmentFiles.d.ts +7 -0
  49. package/dist/lib/virtualTour__/MinimapConfig.d.ts +23 -0
  50. package/dist/lib/virtualTour__/impl/HttpApartmentFiles.d.ts +19 -0
  51. package/dist/vue3-player.cjs.js +2 -0
  52. package/dist/vue3-player.esm.js +2992 -0
  53. package/dist/vue3-player.iife.js +3 -0
  54. package/dist/vue3-player.umd.js +3 -0
  55. package/package.json +65 -0
  56. package/src/components/apartmentChooser/QiApartmentChooser.vue +319 -0
  57. package/src/components/apartmentChooser/QiApartmentChooserNavigation.vue +88 -0
  58. package/src/components/apartmentChooser/QiApartmentChooserRaster.vue +408 -0
  59. package/src/components/apartmentChooser/QiApartmentChooserRotator.vue +202 -0
  60. package/src/components/apartmentChooser/QiApartmentChooserSvg.vue +257 -0
  61. package/src/components/apartmentChooser/index.js +5 -0
  62. package/src/components/buildingPicker/QiBuildingChooser.vue +61 -0
  63. package/src/components/buildingPicker/QiBuildingChooserRaster.vue +312 -0
  64. package/src/components/buildingPicker/QiBuildingChooserRasterOverlay.vue +96 -0
  65. package/src/components/buildingPicker/QiBuildingChooserSceneImageStack.vue +89 -0
  66. package/src/components/buildingPicker/QiBuildingChooserSunSimControl.vue +257 -0
  67. package/src/components/buildingPicker/QiBuildingChooserSvg.vue +414 -0
  68. package/src/components/buildingPicker/index.js +6 -0
  69. package/src/components/index.ts +4 -0
  70. package/src/components/shared/QiFloatingCard.vue +76 -0
  71. package/src/components/shared/QiHoverProbe.vue +22 -0
  72. package/src/components/shared/QiNorthDirection.vue +27 -0
  73. package/src/components/shared/QiZoomBox.vue +322 -0
  74. package/src/components/shared/index.js +4 -0
  75. package/src/components/virtualTourV2/Compass.vue +37 -0
  76. package/src/components/virtualTourV2/Disclaimer.vue +41 -0
  77. package/src/components/virtualTourV2/FloorLevel.vue +73 -0
  78. package/src/components/virtualTourV2/FullScreenButton.vue +23 -0
  79. package/src/components/virtualTourV2/InteriorSelector.vue +90 -0
  80. package/src/components/virtualTourV2/ManualSlideShow.vue +198 -0
  81. package/src/components/virtualTourV2/ManualSlideShowPlayer.vue +159 -0
  82. package/src/components/virtualTourV2/PlayerV2.vue +300 -0
  83. package/src/components/virtualTourV2/ScrollHelper.vue +74 -0
  84. package/src/components/virtualTourV2/SettingsUI.vue +355 -0
  85. package/src/components/virtualTourV2/SingleImage.vue +36 -0
  86. package/src/components/virtualTourV2/SocialShare.vue +163 -0
  87. package/src/components/virtualTourV2/TimeOfDay.vue +50 -0
  88. package/src/components/virtualTourV2/Tutorial.vue +690 -0
  89. package/src/components/virtualTourV2/ViewModeToggle.vue +24 -0
  90. package/src/components/virtualTourV2/VirtualTourV2.vue +876 -0
  91. package/src/components/virtualTourV2/WaypointCarousel.vue +162 -0
  92. package/src/components/virtualTourV2/index.js +1 -0
  93. package/src/components/virtualTourV2/minimapv2/MiniMapMap.vue +262 -0
  94. package/src/components/virtualTourV2/minimapv2/MiniMapV2.vue +110 -0
  95. package/src/components/virtualTourV2/minimapv2/MinimapCompass.vue +39 -0
  96. package/src/components/virtualTourV2/minimapv2/Moveable.vue +208 -0
  97. package/src/components/virtualTourV2/minimapv2/RotationMarkerV2.vue +79 -0
  98. package/src/components/virtualTourV2/minimapv2/SunsimulationSlider.vue +203 -0
  99. package/src/components/virtualTourV2/minimapv2/index.js +1 -0
  100. package/src/entry.esm.js +17 -0
  101. package/src/entry.js +13 -0
  102. package/src/entry.ts_ +17 -0
  103. package/src/lib/apartmentChooser/BuildingViewerModel.js +60 -0
  104. package/src/lib/apartmentChooser/CircularSlideshow.js +66 -0
  105. package/src/lib/apartmentChooser/RotationStep.js +31 -0
  106. package/src/lib/apartmentChooser/SceneRotator.js +25 -0
  107. package/src/lib/apartmentChooser/index.js +3 -0
  108. package/src/lib/apartmentChooser/throttle.js +15 -0
  109. package/src/lib/buildingPicker/BuildingMap.js +24 -0
  110. package/src/lib/buildingPicker/BuildingPickerResourceProvider.js +97 -0
  111. package/src/lib/buildingPicker/CanvasRaster.js +29 -0
  112. package/src/lib/buildingPicker/DayOfYearSelector.js +36 -0
  113. package/src/lib/buildingPicker/SampleRaster.js +14 -0
  114. package/src/lib/buildingPicker/index.js +5 -0
  115. package/src/lib/index.js +4 -0
  116. package/src/lib/shared/BatchLoadTracker.js +52 -0
  117. package/src/lib/shared/I18N.js +65 -0
  118. package/src/lib/shared/ResourceLoader.js +33 -0
  119. package/src/lib/shared/index.js +3 -0
  120. package/src/lib/virtualTour/CameraSnapshot.js +42 -0
  121. package/src/lib/virtualTour/FullscreenModel.js +69 -0
  122. package/src/lib/virtualTour/textures/arrow.png +0 -0
  123. package/src/lib/virtualTour/textures/compass-bg.png +0 -0
  124. package/src/lib/virtualTour/textures/compass-needle.png +0 -0
  125. package/src/lib/virtualTour/textures/compass-north.png +0 -0
  126. package/src/lib/virtualTour/textures/floor-1.svg +4 -0
  127. package/src/lib/virtualTour/textures/floor-2.svg +4 -0
  128. package/src/lib/virtualTour/textures/marker.png +0 -0
  129. package/src/lib/virtualTour/textures/tod-sun.png +0 -0
  130. package/src/lib/virtualTour__/ApartmentConfig.ts +80 -0
  131. package/src/lib/virtualTour__/ApartmentFiles.ts +8 -0
  132. package/src/lib/virtualTour__/CameraNavigator.js_ +74 -0
  133. package/src/lib/virtualTour__/CameraSnapshot.js +42 -0
  134. package/src/lib/virtualTour__/CoordConversions.js +43 -0
  135. package/src/lib/virtualTour__/FullscreenModel.js +69 -0
  136. package/src/lib/virtualTour__/MinimapConfig.ts +46 -0
  137. package/src/lib/virtualTour__/PlayerViewModel.js +423 -0
  138. package/src/lib/virtualTour__/config/ApartmentConfig.js +92 -0
  139. package/src/lib/virtualTour__/config/CameraConfig.js +97 -0
  140. package/src/lib/virtualTour__/config/Interaction.js +393 -0
  141. package/src/lib/virtualTour__/config/Panorama.js +78 -0
  142. package/src/lib/virtualTour__/config/PlayerConfig.js +812 -0
  143. package/src/lib/virtualTour__/config/rawinflate.export.js +833 -0
  144. package/src/lib/virtualTour__/config/shaders.js +24 -0
  145. package/src/lib/virtualTour__/impl/HttpApartmentFiles.ts +57 -0
  146. package/src/lib/virtualTour__/index.js +1 -0
  147. package/src/lib/virtualTour__/textures/arrow.png +0 -0
  148. package/src/lib/virtualTour__/textures/compass-bg.png +0 -0
  149. package/src/lib/virtualTour__/textures/compass-needle.png +0 -0
  150. package/src/lib/virtualTour__/textures/compass-north.png +0 -0
  151. package/src/lib/virtualTour__/textures/floor-1.svg +4 -0
  152. package/src/lib/virtualTour__/textures/floor-2.svg +4 -0
  153. package/src/lib/virtualTour__/textures/marker.png +0 -0
  154. package/src/lib/virtualTour__/textures/tod-sun.png +0 -0
  155. package/src/main.ts_ +24 -0
  156. package/src/shims-png.d.ts +4 -0
  157. package/src/shims-tsx.d.ts +11 -0
  158. package/src/shims-vue.d.ts +4 -0
  159. package/src/style.css +0 -0
  160. package/src/vite-env.d.ts +5 -0
@@ -0,0 +1,408 @@
1
+ <template>
2
+ <div id="canvas">
3
+ <canvas
4
+ v-show="!viewModel.isRotating"
5
+ id="canvas-mask"
6
+ ref="canvasMask"
7
+ :style="canvasStyle"
8
+ @click="clickCanvas"
9
+ ></canvas>
10
+ <qi-hover-probe ref="hoverProbe" />
11
+ </div>
12
+ </template>
13
+
14
+ <script>
15
+ import { ResourceLoader } from "@/lib/shared/ResourceLoader";
16
+ import QiHoverProbe from "@/components/shared/QiHoverProbe.vue";
17
+ import throttle from "@/lib/apartmentChooser/throttle";
18
+
19
+ export default {
20
+ name: "QiApartmentChooserRaster",
21
+ components: { QiHoverProbe },
22
+ mixins: [throttle],
23
+ props: {
24
+ cdnFileResolver: { type: Function, required: true },
25
+ viewModel: { type: Object, required: true },
26
+ selectedUnitId: { type: String, default: null },
27
+ unitCallback: { type: Function, default: (aptId) => aptId },
28
+ canGoToUnitCallback: { type: Function, default: () => true },
29
+ sceneData: { type: Array, default: [] },
30
+ colorCallback: {
31
+ type: Function,
32
+ default: () => ({ r: 108, g: 0, b: 108, a: 144, s: 2 }),
33
+ },
34
+ drawObserver: {
35
+ type: [Object, Array, String, Number, Boolean],
36
+ default: null,
37
+ },
38
+ },
39
+ emits: ["select-unit", "go-to-unit", "hover-over", "hover-out"],
40
+ data() {
41
+ return {
42
+ pos: { x: 0, y: 0, width: 0, height: 0 },
43
+ canvas: null,
44
+ canvasBg: null,
45
+ canvasWidth: 0,
46
+ canvasHeight: 0,
47
+ context: null,
48
+ contextBg: null,
49
+ image: null,
50
+ imageData: null,
51
+ originalImageData: null,
52
+ hoverApartmentIndex: -1,
53
+ showApartmentCard: false,
54
+ cursorX: null,
55
+ cursorY: null,
56
+ rasterImages: {},
57
+ vueCanvasCursor: "auto",
58
+ timeout: null,
59
+ windowListener: undefined,
60
+ resourceLoader: new ResourceLoader(),
61
+ apartmentIndexRasterMap: {},
62
+ };
63
+ },
64
+ computed: {
65
+ isHoverable() {
66
+ return this.$refs.hoverProbe.isHoverEnabled;
67
+ },
68
+ rasterUrl() {
69
+ return this.cdnFileResolver(
70
+ `/scenedata${this.viewModel.currentScene}.png`
71
+ );
72
+ },
73
+ rasterImage() {
74
+ return this.rasterUrl in this.rasterImages
75
+ ? this.rasterImages[this.rasterUrl]
76
+ : null;
77
+ },
78
+ hoverApartment() {
79
+ if (this.hoverApartmentIndex < 0) {
80
+ return false;
81
+ }
82
+
83
+ const apartment = this.sceneData.find(
84
+ (x) => x.index === this.hoverApartmentIndex
85
+ );
86
+
87
+ if (apartment) {
88
+ return this.unitCallback
89
+ ? this.unitCallback(apartment.apartmentId)
90
+ : apartment.apartmentId;
91
+ } else {
92
+ return false;
93
+ }
94
+ },
95
+ canvasStyle() {
96
+ return {
97
+ width: this.canvasWidth + "px",
98
+ height: this.canvasHeight + "px",
99
+ cursor: this.vueCanvasCursor,
100
+ };
101
+ },
102
+ hoverBehaviourIdentifier() {
103
+ return `hover-${this.showApartmentCard ? "over" : "out"}-${
104
+ this.hoverApartment
105
+ ? typeof this.hoverApartment === "object" &&
106
+ "unitId" in this.hoverApartment
107
+ ? this.hoverApartment.unitId
108
+ : this.hoverApartment
109
+ : ""
110
+ }`;
111
+ },
112
+ },
113
+ watch: {
114
+ rasterUrl: {
115
+ handler() {
116
+ if (!this.rasterImages[this.rasterUrl]) {
117
+ this.resourceLoader
118
+ .loadImageData(this.rasterUrl)
119
+ .then((objectURL) => {
120
+ this.rasterImages = {
121
+ ...this.rasterImages,
122
+ [this.rasterUrl]: objectURL,
123
+ };
124
+ });
125
+ }
126
+ },
127
+ immediate: true,
128
+ },
129
+ rasterImage() {
130
+ if (this.rasterImage) {
131
+ this.init();
132
+ }
133
+ },
134
+ "viewModel.currentScene": {
135
+ handler() {
136
+ this.draw();
137
+ },
138
+ immediate: true,
139
+ },
140
+ "viewModel.isRotating"() {
141
+ this.draw();
142
+ },
143
+ drawObserver() {
144
+ // This is here to trigger redraws on change, nothing else
145
+ this.draw();
146
+ },
147
+ selectedUnitId() {
148
+ this.draw();
149
+ },
150
+ hoverApartment() {
151
+ this.draw();
152
+ if (this.hoverApartment) {
153
+ this.vueCanvasCursor = "pointer";
154
+ } else {
155
+ this.vueCanvasCursor = "auto";
156
+ }
157
+ },
158
+ hoverBehaviourIdentifier() {
159
+ this.$emit(this.showApartmentCard ? "hover-over" : "hover-out", {
160
+ hoverApartment: this.hoverApartment,
161
+ closeHandler: () => this.closeDialog(),
162
+ });
163
+ },
164
+ pos() {
165
+ this.$emit("pos", this.pos);
166
+ },
167
+ },
168
+ mounted() {
169
+ this.windowListener = () => {
170
+ clearTimeout(this.timeout);
171
+ this.updateCanvasSize();
172
+ // resize needs to happen one more time after last event to make sure everything is synced
173
+ // could check if widths align document.getElementById('canvas').offsetWidth and rerun if needed
174
+ this.timeout = setTimeout(() => {
175
+ this.updateCanvasSize();
176
+ }, 50);
177
+ };
178
+ window.addEventListener("resize", this.windowListener);
179
+ this.updateCanvasSize();
180
+ },
181
+ beforeUnmount() {
182
+ window.removeEventListener("resize", this.windowListener);
183
+ for (const key in this.rasterImages) {
184
+ URL.revokeObjectURL(this.rasterImages[key]);
185
+ }
186
+ },
187
+ methods: {
188
+ init() {
189
+ this.canvas = this.$refs.canvasMask;
190
+ this.context = this.canvas.getContext("2d", { willReadFrequently: true });
191
+ this.image = new Image();
192
+ this.image.onload = () => {
193
+ this.setupDraw();
194
+ this.canvas.addEventListener(
195
+ "mousemove",
196
+ this.throttle(this.handleMouseMove, 50)
197
+ );
198
+ };
199
+ this.image.src = this.rasterImage;
200
+ },
201
+ handleMouseMove(e) {
202
+ const index = this.getApartmentIndexByCursorPosition(
203
+ e.offsetX,
204
+ e.offsetY
205
+ );
206
+ this.showApartmentCard = index >= 0;
207
+ this.hoverApartmentIndex = index;
208
+ this.pos = {
209
+ x: e.offsetX,
210
+ y: e.offsetY,
211
+ width: this.canvasWidth,
212
+ height: this.canvasHeight,
213
+ };
214
+ },
215
+ clickCanvas(e) {
216
+ const index = this.getApartmentIndexByCursorPosition(
217
+ e.offsetX,
218
+ e.offsetY
219
+ );
220
+ if (index < 0) return;
221
+ if (this.isHoverable) {
222
+ this.goToApartmentByIndex(index);
223
+ }
224
+ },
225
+ setImageData() {
226
+ if (this.image && this.image.width) {
227
+ this.canvas.width = this.image.width;
228
+ this.canvas.height = this.image.height;
229
+ this.context.drawImage(
230
+ this.image,
231
+ 0,
232
+ 0,
233
+ this.image.width,
234
+ this.image.height
235
+ );
236
+ this.originalImageData = this.context.getImageData(
237
+ 0,
238
+ 0,
239
+ this.canvas.width,
240
+ this.canvas.height
241
+ );
242
+ this.imageData = this.context.getImageData(
243
+ 0,
244
+ 0,
245
+ this.canvas.width,
246
+ this.canvas.height
247
+ );
248
+
249
+ const data = this.originalImageData.data;
250
+ this.apartmentIndexRasterMap = {};
251
+ for (let i = 0; i < data.length; i += 4) {
252
+ this.apartmentIndexRasterMap[data[i]] = [];
253
+ }
254
+ for (let i = 3; i < data.length; i += 4) {
255
+ if (data[i] === 255 && data[i - 3] in this.apartmentIndexRasterMap) {
256
+ this.apartmentIndexRasterMap[data[i - 3]].push(i - 3);
257
+ }
258
+ }
259
+ }
260
+ },
261
+ getApartmentIndexByCursorPosition(x, y) {
262
+ this.cursorX = x;
263
+ this.cursorY = y;
264
+
265
+ const widthScale = this.canvas.offsetWidth / this.canvas.width;
266
+ const heightScale = this.canvas.offsetHeight / this.canvas.height;
267
+ const rasterCursorX = (x / widthScale).toFixed();
268
+ const rasterCursorY = (y / heightScale).toFixed();
269
+
270
+ const index = rasterCursorY * (this.canvas.width * 4) + rasterCursorX * 4;
271
+ const apartmentIndex = this.originalImageData.data[index];
272
+ const isApartment = this.originalImageData.data[index + 3] > 0;
273
+ return isApartment ? apartmentIndex : -1;
274
+ },
275
+ getApartmentIndexById(id) {
276
+ const apartment = this.sceneData.find((x) => x.apartmentId === id);
277
+ return !apartment ? -1 : apartment.index;
278
+ },
279
+ highlightApartments() {
280
+ const apartments = this.getApartmentsInRaster();
281
+ apartments.forEach((apartment) => {
282
+ if (apartment.index >= 0) {
283
+ const pixels = this.getApartmentPixelsByIndex(apartment.index);
284
+ const aptId = this.getApartmentIdByIndex(apartment.index);
285
+ let type = "idle";
286
+ if (
287
+ !this.hoverApartment &&
288
+ this.selectedUnitId &&
289
+ aptId === this.selectedUnitId
290
+ ) {
291
+ type = "selected";
292
+ } else if (
293
+ this.hoverApartment &&
294
+ this.hoverApartmentIndex === apartment.index
295
+ ) {
296
+ type = "hover";
297
+ }
298
+ const unit = this.unitCallback ? this.unitCallback(aptId) : aptId;
299
+ if (unit) {
300
+ const color = this.colorCallback(unit, type);
301
+ if (color) {
302
+ for (let i = 0; i < pixels.length; i++) {
303
+ this.imageData.data[pixels[i]] = color.r; // R
304
+ this.imageData.data[pixels[i] + 1] = color.g; // G
305
+ this.imageData.data[pixels[i] + 2] = color.b; // B
306
+ this.imageData.data[pixels[i] + 3] = color.a; // A
307
+ }
308
+ }
309
+ }
310
+ }
311
+ });
312
+ },
313
+ // GETTING APARTMENT DATA
314
+ // Returns the index of each RGBA value of a given apartment
315
+ getApartmentPixelsByIndex(apartmentIndex) {
316
+ return !this.originalImageData
317
+ ? false
318
+ : this.apartmentIndexRasterMap[apartmentIndex];
319
+ },
320
+ getApartmentIdByIndex(apartmentIndex) {
321
+ const apartment = this.sceneData.find((x) => x.index === apartmentIndex);
322
+ return apartment && apartment.apartmentId;
323
+ },
324
+ getApartmentsInRaster() {
325
+ if (this.originalImageData) {
326
+ const uniqueIndices = Object.keys(this.apartmentIndexRasterMap).map(
327
+ (i) => parseInt(i)
328
+ );
329
+ // Filter scenedata down to only one object per unique apartment
330
+ const filteredSceneData = this.sceneData.filter((x) =>
331
+ uniqueIndices.includes(x.index)
332
+ );
333
+ return filteredSceneData;
334
+ } else {
335
+ return [];
336
+ }
337
+ },
338
+ goToApartmentByIndex(index) {
339
+ const apartmentId = this.getApartmentIdByIndex(index);
340
+ this.goToApartmentById(apartmentId);
341
+ },
342
+ goToApartmentById(apartmentId) {
343
+ const arg = this.unitCallback
344
+ ? this.unitCallback(apartmentId)
345
+ : apartmentId;
346
+ this.$emit("select-unit", arg);
347
+ if (arg && this.canGoToUnitCallback(arg)) {
348
+ this.$emit("go-to-unit", arg);
349
+ }
350
+ },
351
+ setAlpha(alpha = 0) {
352
+ if (!this.imageData) {
353
+ return;
354
+ }
355
+
356
+ const data = this.imageData.data;
357
+ for (let i = 3; i < data.length; i += 4) {
358
+ data[i] = alpha;
359
+ }
360
+ },
361
+ updateCanvasSize() {
362
+ if (!this.$el) {
363
+ return false;
364
+ }
365
+
366
+ this.canvasWidth = this.$el.offsetWidth;
367
+ this.canvasHeight = this.$el.offsetHeight;
368
+ },
369
+ setupDraw() {
370
+ this.setImageData();
371
+ this.draw();
372
+ },
373
+ draw() {
374
+ this.setAlpha();
375
+ this.highlightApartments();
376
+ if (this.context && this.imageData) {
377
+ this.context.putImageData(this.imageData, 0, 0);
378
+ }
379
+ },
380
+ closeDialog() {
381
+ this.showApartmentCard = false;
382
+ },
383
+ },
384
+ };
385
+ </script>
386
+
387
+ <style scoped>
388
+ #canvas {
389
+ position: absolute;
390
+ top: 0;
391
+ width: 100%;
392
+ user-select: none;
393
+ touch-action: none;
394
+ padding-top: 56.25%;
395
+ }
396
+ #canvas-mask {
397
+ position: absolute;
398
+ top: 0;
399
+ max-width: 100%;
400
+ z-index: 4;
401
+ user-select: none;
402
+ }
403
+ @media only screen and (min-width: 1280px) {
404
+ #canvas {
405
+ border-radius: 10px;
406
+ }
407
+ }
408
+ </style>
@@ -0,0 +1,202 @@
1
+ <template>
2
+ <div>
3
+ <div
4
+ v-if="loadTracker.isError"
5
+ class="fill-parent d-flex justify-center align-center blue-grey lighten-5"
6
+ >
7
+ <v-icon x-large>mdi-alert-circle-outline</v-icon>
8
+ </div>
9
+ <div v-else-if="!isPreloaded" class="frame-container">
10
+ <picture class="frame-image">
11
+ <source :srcset="activeFrame.sourceSet" :type="activeFrame.type" />
12
+ <img
13
+ :src="activeFrame.defaultPath"
14
+ @load="isPreloaded = true"
15
+ @error="loadTracker.errorDetected()"
16
+ />
17
+ </picture>
18
+ </div>
19
+ <div v-else id="image-stack" class="frame-container">
20
+ <picture
21
+ v-for="img in images"
22
+ :key="img.index"
23
+ :alt="img.index"
24
+ :class="{ 'frame-image': true, show: img.show }"
25
+ >
26
+ <source :srcset="img.sourceSet" type="image/jpeg" />
27
+ <img
28
+ :src="img.defaultPath"
29
+ alt="Building image"
30
+ @load="loadTracker.partLoaded()"
31
+ @error="loadTracker.errorDetected()"
32
+ />
33
+ </picture>
34
+ </div>
35
+ </div>
36
+ </template>
37
+
38
+ <script>
39
+ import { BatchLoadTracker } from '@/lib/shared/BatchLoadTracker'
40
+ import { SceneRotator } from '@/lib/apartmentChooser/SceneRotator'
41
+
42
+ const totalFrameCount = 64
43
+ const totalScenesCount = 4
44
+ const fps = 120
45
+
46
+ export default {
47
+ name: 'QiApartmentChooserRotator',
48
+ props: {
49
+ viewModel: { type: Object, required: true },
50
+ build: { type: String, default: '' },
51
+ cdnFileResolver: { type: Function, required: true },
52
+ cardinalFrames: { type: Array, required: true },
53
+ },
54
+ emits: ['loaded', 'error', 'progress'],
55
+ data() {
56
+ const frames = this.createFrames()
57
+ return {
58
+ loadTracker: new BatchLoadTracker(frames.length)
59
+ .onLoadFinished(() => this.$emit('loaded'))
60
+ .onLoadProgress((percent) => this.$emit('progress', percent))
61
+ .onLoadError(() => this.$emit('error')),
62
+ sceneRotator: new SceneRotator(this.cardinalFrames, totalFrameCount),
63
+ images: frames,
64
+ isPreloaded: false,
65
+ }
66
+ },
67
+ computed: {
68
+ activeFrame() {
69
+ return this.images.find((f) => f.show)
70
+ },
71
+ },
72
+ watch: {
73
+ 'viewModel.currentScene'(newVal, oldVal) {
74
+ const direction = this.getDirection(oldVal, newVal)
75
+ const sceneCount = this.getSceneCountBetween(oldVal, newVal)
76
+ this.rotateInDirection(oldVal, direction, sceneCount)
77
+ },
78
+ },
79
+ created() {
80
+ this.rotateComplete()
81
+ },
82
+ methods: {
83
+ rotateInDirection(fromScene, direction, sceneCount) {
84
+ if (this.viewModel.isRotating) {
85
+ return
86
+ }
87
+
88
+ this.viewModel.isRotating = true
89
+ this.rotationStep(
90
+ this.sceneRotator.start(
91
+ direction,
92
+ fromScene % totalScenesCount,
93
+ sceneCount
94
+ )
95
+ )
96
+ },
97
+ rotationStep(step) {
98
+ setTimeout(() => {
99
+ this.images[step.frameIndex].show = false
100
+ const nextStep = step.nextFrame()
101
+ this.images[nextStep.frameIndex].show = true
102
+ if (nextStep.atEnd) {
103
+ this.rotateComplete()
104
+ } else {
105
+ window.requestAnimationFrame(() => this.rotationStep(nextStep))
106
+ }
107
+ }, 1000 / fps)
108
+ },
109
+ rotateComplete() {
110
+ const finalFrameIndex = this.sceneRotator.startFrameIndexForScene(
111
+ this.viewModel.currentScene % totalScenesCount
112
+ )
113
+ if (finalFrameIndex in this.images) {
114
+ this.images[finalFrameIndex].show = true
115
+ }
116
+ this.viewModel.isRotating = false
117
+ },
118
+ createFrames() {
119
+ const arr = []
120
+ for (let i = 0; i < totalFrameCount; i++) {
121
+ const { sourceSet, defaultPath } = this.frameImageSource(i)
122
+ arr.push({
123
+ index: i,
124
+ sourceSet,
125
+ defaultPath,
126
+ show: false,
127
+ })
128
+ }
129
+ return arr
130
+ },
131
+ frameImageSource(frameIndex) {
132
+ const filename = ('0000' + (frameIndex + 1)).slice(-4)
133
+ const buildSuffix = this.build ? `?v=${this.build}` : ''
134
+ const sourceSet = [
135
+ `${this.cdnFileResolver(`/480/${filename}.jpg${buildSuffix}`)} 480w`,
136
+ `${this.cdnFileResolver(`/960/${filename}.jpg${buildSuffix}`)} 960w`,
137
+ `${this.cdnFileResolver(`/1280/${filename}.jpg${buildSuffix}`)} 1280w`,
138
+ ].join(',')
139
+ const defaultPath = this.cdnFileResolver(
140
+ `/480/${filename}.jpg${buildSuffix}`
141
+ )
142
+ return { sourceSet, defaultPath }
143
+ },
144
+ getSceneCountBetween(fromScene, toScene) {
145
+ const halfCircle = totalScenesCount / 2
146
+ const absDistance = Math.abs(fromScene - toScene)
147
+ return absDistance > halfCircle
148
+ ? totalScenesCount - absDistance
149
+ : absDistance
150
+ },
151
+ getDirection(fromScene, toScene) {
152
+ if (fromScene === toScene) {
153
+ return 0
154
+ }
155
+
156
+ const distance = Math.abs(fromScene - toScene)
157
+ const internal = distance < totalScenesCount / 2
158
+ return (internal && fromScene < toScene) ||
159
+ (!internal && fromScene > toScene)
160
+ ? 1
161
+ : -1
162
+ },
163
+ },
164
+ }
165
+ </script>
166
+
167
+ <style scoped>
168
+ .frame-container {
169
+ position: relative;
170
+ max-width: 100%;
171
+ top: 0;
172
+ left: 0;
173
+ bottom: 185px;
174
+ padding-top: 56%;
175
+ z-index: 1;
176
+ overflow: hidden;
177
+ }
178
+ .frame-image {
179
+ position: absolute;
180
+ top: 0;
181
+ width: 100%;
182
+ height: 100%;
183
+ }
184
+ .frame-image img {
185
+ min-width: 100%;
186
+ max-width: 100%;
187
+ z-index: 0;
188
+ }
189
+ #image-stack picture {
190
+ visibility: hidden;
191
+ }
192
+ #image-stack picture.show {
193
+ visibility: visible;
194
+ }
195
+ .fill-parent {
196
+ position: absolute;
197
+ top: 0;
198
+ bottom: 0;
199
+ left: 0;
200
+ right: 0;
201
+ }
202
+ </style>