@clikvn/showroom-visualizer 0.3.0-dev-07 → 0.3.0-dev-08

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@clikvn/showroom-visualizer",
3
3
  "description": "Showroom Visualizer",
4
- "version": "0.3.0-dev-07",
4
+ "version": "0.3.0-dev-08",
5
5
  "author": "Clik JSC",
6
6
  "license": "ISC",
7
7
  "type": "module",
@@ -1,582 +0,0 @@
1
- # Middleware Implementation - Tracking Only
2
-
3
- ## Overview
4
-
5
- The showroom-visualizer library now supports **tracking middleware** that fires on all user interactions, regardless of whether external listeners are configured.
6
-
7
- **Key Design**: Middleware is for **tracking only** - it does NOT block or cancel actions.
8
-
9
- ## Quick Start
10
-
11
- ```typescript
12
- import ShowroomVisualizer from '@clikvn/showroom-visualizer';
13
-
14
- ShowroomVisualizer.initVisualizer({
15
- apiHost: 'https://api.clik.vn',
16
- config: { tourCode: 'TOUR_123' },
17
-
18
- // Add tracking middleware
19
- middleware: (event) => {
20
- console.log('Action:', event.actionName);
21
- console.log('Payload:', event.payload);
22
- console.log('Timestamp:', event.timestamp);
23
- console.log('Tour:', event.metadata?.tourCode);
24
- },
25
- });
26
- ```
27
-
28
- ## Event Structure
29
-
30
- Each middleware call receives an `ActionEvent`:
31
-
32
- ```typescript
33
- interface ActionEvent {
34
- // Action name (e.g., 'onPinActionClicked', 'onSceneChanged')
35
- actionName: string;
36
-
37
- // Action data (button key, scene info, scenario code, etc.)
38
- payload: any;
39
-
40
- // ISO timestamp when action occurred
41
- timestamp: string;
42
-
43
- // Optional context
44
- metadata?: {
45
- tourCode?: string;
46
- sceneCode?: string;
47
- };
48
- }
49
- ```
50
-
51
- ## Tracked Actions
52
-
53
- The middleware system tracks **13 distinct action types** across the application:
54
-
55
- ### 1. Pin Action Buttons (UI Layer)
56
-
57
- **Component**: `src/components/SkinLayer/PinActionButtons/index.tsx`
58
- **Action Name**: `onPinActionClicked`
59
- **Payload**: Button key string
60
-
61
- ```typescript
62
- middleware: (event) => {
63
- if (event.actionName === 'onPinActionClicked') {
64
- console.log('Button clicked:', event.payload);
65
- // Possible values: 'MAP', 'SOUND', 'FULLSCREEN', 'SEARCH', etc.
66
- }
67
- };
68
- ```
69
-
70
- ### 2. Scene Navigation (UI Layer)
71
-
72
- **Component**: `src/components/SkinLayer/SceneCategories/index.tsx`
73
- **Action Name**: `onSceneChanged`
74
- **Payload**: `{ scene: SceneType, category: CategoryDataType }`
75
-
76
- ```typescript
77
- middleware: (event) => {
78
- if (event.actionName === 'onSceneChanged') {
79
- const { scene, category } = event.payload;
80
- console.log('Navigating to:', scene.name, 'in category:', category.name);
81
- }
82
- };
83
- ```
84
-
85
- ### 3. Scenario Playback (UI Layer)
86
-
87
- **Component**: `src/components/SkinLayer/TourScenarios/index.tsx`
88
- **Action Name**: `onStartScenario`
89
- **Payload**: Scenario code (string)
90
-
91
- ```typescript
92
- middleware: (event) => {
93
- if (event.actionName === 'onStartScenario') {
94
- console.log('Starting scenario:', event.payload);
95
- }
96
- };
97
- ```
98
-
99
- ### 4. POI Click (Krpano Layer)
100
-
101
- **Hook**: `src/hooks/Visualizer/useTourVisualizer.ts`
102
- **Action Name**: `onPoiClicked`
103
- **Payload**: POI data object
104
-
105
- ```typescript
106
- middleware: (event) => {
107
- if (event.actionName === 'onPoiClicked') {
108
- console.log('POI clicked:', event.payload);
109
- // Payload contains: { code, type, sceneCode, ...poi properties }
110
- }
111
- };
112
- ```
113
-
114
- ### 5. Moving POI Click (Krpano Layer)
115
-
116
- **Hook**: `src/hooks/Visualizer/useTourVisualizer.ts`
117
- **Action Name**: `onMovingPoiClick`
118
- **Payload**: `{ sceneCode: string, floorPlanCode?: string }`
119
-
120
- ```typescript
121
- middleware: (event) => {
122
- if (event.actionName === 'onMovingPoiClick') {
123
- console.log('Moving POI clicked:', event.payload.sceneCode);
124
- }
125
- };
126
- ```
127
-
128
- ### 6. Texture POI Click (Krpano Layer)
129
-
130
- **Hook**: `src/hooks/Visualizer/useTourVisualizer.ts`
131
- **Action Name**: `onTexturePoiClick`
132
- **Payload**: `{ poiCode: string, textureIndex: number }`
133
-
134
- ```typescript
135
- middleware: (event) => {
136
- if (event.actionName === 'onTexturePoiClick') {
137
- console.log(
138
- 'Texture POI clicked:',
139
- event.payload.poiCode,
140
- 'index:',
141
- event.payload.textureIndex
142
- );
143
- }
144
- };
145
- ```
146
-
147
- ### 7. Background Sound Toggle (Krpano Layer)
148
-
149
- **Hook**: `src/hooks/Visualizer/useTourVisualizer.ts`
150
- **Action Name**: `onBackgroundSoundToggle`
151
- **Payload**: `{ playing: boolean }`
152
-
153
- ```typescript
154
- middleware: (event) => {
155
- if (event.actionName === 'onBackgroundSoundToggle') {
156
- console.log(
157
- 'Background sound:',
158
- event.payload.playing ? 'playing' : 'paused'
159
- );
160
- }
161
- };
162
- ```
163
-
164
- ### 8. Function Button Open (Krpano Layer)
165
-
166
- **Hook**: `src/hooks/Visualizer/useTourVisualizer.ts`
167
- **Action Name**: `onFunctionButtonOpen`
168
- **Payload**: `{ buttonCode: string }`
169
-
170
- ```typescript
171
- middleware: (event) => {
172
- if (event.actionName === 'onFunctionButtonOpen') {
173
- console.log('Function button opened:', event.payload.buttonCode);
174
- }
175
- };
176
- ```
177
-
178
- ### 9. Minimap Toggle (Krpano Layer)
179
-
180
- **Hook**: `src/hooks/Visualizer/useTourVisualizer.ts`
181
- **Action Name**: `onMinimapToggle`
182
- **Payload**: `{ fullscreen: boolean }`
183
-
184
- ```typescript
185
- middleware: (event) => {
186
- if (event.actionName === 'onMinimapToggle') {
187
- console.log('Minimap fullscreen:', event.payload.fullscreen);
188
- }
189
- };
190
- ```
191
-
192
- ### 10. Hotspot View More (UI Layer)
193
-
194
- **Component**: `src/components/SkinLayer/HotspotOverview/index.tsx`
195
- **Action Name**: `onHotspotViewMore`
196
- **Payload**: `{ poiCode: string, poiType: string }`
197
-
198
- ```typescript
199
- middleware: (event) => {
200
- if (event.actionName === 'onHotspotViewMore') {
201
- console.log(
202
- 'View more clicked:',
203
- event.payload.poiCode,
204
- 'type:',
205
- event.payload.poiType
206
- );
207
- }
208
- };
209
- ```
210
-
211
- ### 11. Gallery Item Click (UI Layer)
212
-
213
- **Component**: `src/components/SkinLayer/HotspotOverview/index.tsx`
214
- **Action Name**: `onGalleryItemClick`
215
- **Payload**: `{ id: number, poiCode: string }`
216
-
217
- ```typescript
218
- middleware: (event) => {
219
- if (event.actionName === 'onGalleryItemClick') {
220
- console.log('Gallery item clicked:', event.payload.id);
221
- }
222
- };
223
- ```
224
-
225
- ### 12. Texture Selected (UI Layer)
226
-
227
- **Component**: `src/components/SkinLayer/HotspotOverview/index.tsx`
228
- **Action Name**: `onTextureSelected`
229
- **Payload**: `{ index: number, poiCode: string }`
230
-
231
- ```typescript
232
- middleware: (event) => {
233
- if (event.actionName === 'onTextureSelected') {
234
- console.log(
235
- 'Texture selected:',
236
- event.payload.index,
237
- 'for POI:',
238
- event.payload.poiCode
239
- );
240
- }
241
- };
242
- ```
243
-
244
- ## Summary of All Tracked Actions
245
-
246
- | # | Action Name | Layer | Component/Hook | Payload |
247
- | --- | ------------------------- | ------ | ----------------- | ------------------------------- |
248
- | 1 | `onPinActionClicked` | UI | PinActionButtons | `string` (button key) |
249
- | 2 | `onSceneChanged` | UI | SceneCategories | `{ scene, category }` |
250
- | 3 | `onStartScenario` | UI | TourScenarios | `string` (scenario code) |
251
- | 4 | `onPoiClicked` | Krpano | useTourVisualizer | POI object |
252
- | 5 | `onMovingPoiClick` | Krpano | useTourVisualizer | `{ sceneCode, floorPlanCode? }` |
253
- | 6 | `onTexturePoiClick` | Krpano | useTourVisualizer | `{ poiCode, textureIndex }` |
254
- | 7 | `onBackgroundSoundToggle` | Krpano | useTourVisualizer | `{ playing }` |
255
- | 8 | `onFunctionButtonOpen` | Krpano | useTourVisualizer | `{ buttonCode }` |
256
- | 9 | `onMinimapToggle` | Krpano | useTourVisualizer | `{ fullscreen }` |
257
- | 10 | `onHotspotViewMore` | UI | HotspotOverview | `{ poiCode, poiType }` |
258
- | 11 | `onGalleryItemClick` | UI | HotspotOverview | `{ id, poiCode }` |
259
- | 12 | `onTextureSelected` | UI | HotspotOverview | `{ index, poiCode }` |
260
-
261
- ## Usage Examples
262
-
263
- ### Example 1: Google Analytics Tracking
264
-
265
- ```typescript
266
- ShowroomVisualizer.initVisualizer({
267
- apiHost: 'https://api.clik.vn',
268
- config: { tourCode: 'TOUR_123' },
269
- middleware: (event) => {
270
- gtag('event', event.actionName, {
271
- event_category: 'showroom',
272
- event_label: event.metadata?.tourCode,
273
- value: JSON.stringify(event.payload),
274
- });
275
- },
276
- });
277
- ```
278
-
279
- ### Example 2: Custom Analytics Service
280
-
281
- ```typescript
282
- ShowroomVisualizer.initVisualizer({
283
- apiHost: 'https://api.clik.vn',
284
- config: { tourCode: 'TOUR_123' },
285
- middleware: async (event) => {
286
- await fetch('https://analytics.example.com/track', {
287
- method: 'POST',
288
- headers: { 'Content-Type': 'application/json' },
289
- body: JSON.stringify({
290
- action: event.actionName,
291
- data: event.payload,
292
- timestamp: event.timestamp,
293
- session: getSessionId(),
294
- user: getUserId(),
295
- }),
296
- });
297
- },
298
- });
299
- ```
300
-
301
- ### Example 3: Console Logging (Development)
302
-
303
- ```typescript
304
- ShowroomVisualizer.initVisualizer({
305
- apiHost: 'https://api.clik.vn',
306
- config: { tourCode: 'TOUR_123' },
307
- middleware: (event) => {
308
- console.group(`[${event.timestamp}] ${event.actionName}`);
309
- console.log('Payload:', event.payload);
310
- console.log('Tour:', event.metadata?.tourCode);
311
- console.groupEnd();
312
- },
313
- });
314
- ```
315
-
316
- ### Example 4: Multiple Tracking Services
317
-
318
- ```typescript
319
- const trackToMultipleServices = async (event) => {
320
- // Track to Google Analytics
321
- gtag('event', event.actionName, { ...event.payload });
322
-
323
- // Track to Mixpanel
324
- mixpanel.track(event.actionName, event.payload);
325
-
326
- // Track to custom backend
327
- await logToBackend(event);
328
- };
329
-
330
- ShowroomVisualizer.initVisualizer({
331
- apiHost: 'https://api.clik.vn',
332
- config: { tourCode: 'TOUR_123' },
333
- middleware: trackToMultipleServices,
334
- });
335
- ```
336
-
337
- ### Example 5: Conditional Tracking
338
-
339
- ```typescript
340
- ShowroomVisualizer.initVisualizer({
341
- apiHost: 'https://api.clik.vn',
342
- config: { tourCode: 'TOUR_123' },
343
- middleware: (event) => {
344
- // Only track certain actions
345
- const importantActions = [
346
- 'onPinActionClicked',
347
- 'onSceneChanged',
348
- 'onStartScenario',
349
- ];
350
-
351
- if (importantActions.includes(event.actionName)) {
352
- analytics.track(event.actionName, event.payload);
353
- }
354
- },
355
- });
356
- ```
357
-
358
- ## Middleware Behavior
359
-
360
- ### ✅ Always Fires
361
-
362
- Middleware executes even if no listeners are configured:
363
-
364
- ```typescript
365
- // Middleware fires for all actions!
366
- ShowroomVisualizer.initVisualizer({
367
- apiHost: 'https://api.clik.vn',
368
- config: { tourCode: 'TOUR_123' },
369
- middleware: (event) => console.log(event.actionName),
370
- // No listeners configured - middleware still works!
371
- });
372
- ```
373
-
374
- ### ✅ Non-Blocking
375
-
376
- Middleware does NOT block actions - it's for tracking only:
377
-
378
- ```typescript
379
- ShowroomVisualizer.initVisualizer({
380
- apiHost: 'https://api.clik.vn',
381
- config: { tourCode: 'TOUR_123' },
382
- middleware: (event) => {
383
- console.log('Tracking:', event.actionName);
384
- // No return value needed - actions always proceed
385
- },
386
- });
387
- ```
388
-
389
- ### ✅ Error Handling
390
-
391
- Middleware errors are caught and logged, but don't break the app:
392
-
393
- ```typescript
394
- ShowroomVisualizer.initVisualizer({
395
- apiHost: 'https://api.clik.vn',
396
- config: { tourCode: 'TOUR_123' },
397
- middleware: (event) => {
398
- // If this throws an error, it's caught and logged
399
- // The action still proceeds normally
400
- throw new Error('Oops!');
401
- },
402
- });
403
- ```
404
-
405
- ### ✅ Async Support
406
-
407
- Middleware can be synchronous or asynchronous:
408
-
409
- ```typescript
410
- // Sync middleware
411
- middleware: (event) => {
412
- localStorage.setItem('lastAction', event.actionName);
413
- };
414
-
415
- // Async middleware (fire and forget)
416
- middleware: async (event) => {
417
- await fetch('/track', { method: 'POST', body: JSON.stringify(event) });
418
- };
419
- ```
420
-
421
- ## TypeScript Types
422
-
423
- ```typescript
424
- import { ActionMiddleware, ActionEvent } from '@clikvn/showroom-visualizer';
425
-
426
- // Define your middleware with types
427
- const myMiddleware: ActionMiddleware = (event: ActionEvent) => {
428
- console.log(event.actionName, event.payload);
429
- };
430
-
431
- // Use in initialization
432
- ShowroomVisualizer.initVisualizer({
433
- apiHost: 'https://api.clik.vn',
434
- config: { tourCode: 'TOUR_123' },
435
- middleware: myMiddleware,
436
- });
437
- ```
438
-
439
- ## Performance
440
-
441
- - **Zero overhead** when middleware not configured
442
- - **~1-2ms** per action when middleware configured
443
- - **Non-blocking** - uses fire-and-forget pattern
444
- - **Async-safe** - errors don't block the UI
445
-
446
- ## Comparison with Listeners
447
-
448
- ### Middleware
449
-
450
- - **Purpose**: Tracking/analytics
451
- - **Fires**: Always (even without listeners)
452
- - **Blocks actions**: No
453
- - **Use case**: Send data to external services
454
-
455
- ### Listeners
456
-
457
- - **Purpose**: Custom app logic
458
- - **Fires**: Only if configured
459
- - **Blocks actions**: No (callbacks)
460
- - **Use case**: Communication between lib and app
461
-
462
- ### Both Can Be Used Together
463
-
464
- ```typescript
465
- ShowroomVisualizer.initVisualizer({
466
- apiHost: 'https://api.clik.vn',
467
- config: { tourCode: 'TOUR_123' },
468
-
469
- // Middleware for tracking
470
- middleware: (event) => {
471
- analytics.track(event.actionName, event.payload);
472
- },
473
-
474
- // Listeners for custom logic
475
- listeners: {
476
- onPinActionClicked: (key) => {
477
- if (key === 'FULLSCREEN') {
478
- updateUIState({ fullscreen: true });
479
- }
480
- },
481
- onSceneChanged: (scene) => {
482
- updateBreadcrumbs(scene.name);
483
- },
484
- },
485
- });
486
- ```
487
-
488
- ## Components with Middleware
489
-
490
- Currently, middleware tracking is implemented across **5 locations**:
491
-
492
- ### UI Layer Components (4 components)
493
-
494
- 1. **PinActionButtons** (`src/components/SkinLayer/PinActionButtons/index.tsx`)
495
- - `onPinActionClicked` - All action button clicks
496
-
497
- 2. **SceneCategories** (`src/components/SkinLayer/SceneCategories/index.tsx`)
498
- - `onSceneChanged` - Scene navigation
499
-
500
- 3. **TourScenarios** (`src/components/SkinLayer/TourScenarios/index.tsx`)
501
- - `onStartScenario` - Scenario playback
502
-
503
- 4. **HotspotOverview** (`src/components/SkinLayer/HotspotOverview/index.tsx`)
504
- - `onHotspotViewMore` - View more button clicks
505
- - `onGalleryItemClick` - Gallery item clicks
506
- - `onTextureSelected` - Texture selection
507
-
508
- ### Krpano/POI Layer (1 hook)
509
-
510
- 5. **useTourVisualizer** (`src/hooks/Visualizer/useTourVisualizer.ts`)
511
- - `onPoiClicked` - POI clicks
512
- - `onMovingPoiClick` - Moving POI clicks
513
- - `onTexturePoiClick` - Texture POI clicks
514
- - `onBackgroundSoundToggle` - Background sound control
515
- - `onFunctionButtonOpen` - Function button opens
516
- - `onMinimapToggle` - Minimap fullscreen toggle
517
-
518
- **Total**: 12 tracked actions across 5 locations
519
-
520
- ## Architecture
521
-
522
- The middleware system tracks **UI layer and POI interactions only**:
523
-
524
- - **UI Components** use the `useActionMiddleware` hook to get the `trackAction` function
525
- - **useTourVisualizer hook** tracks POI clicks from the Krpano layer
526
- - **Tour Model** does not have tracking - keeping it focused on core tour logic
527
- - **Single source of truth** - all tracking logic exists only in `useActionMiddleware.ts`
528
-
529
- This ensures clean separation between business logic (Tour model) and tracking concerns (UI layer).
530
-
531
- ## Adding Middleware to Custom Components
532
-
533
- If you're extending the library, add middleware to your components:
534
-
535
- ```typescript
536
- import { useActionMiddleware } from '../../../hooks/useActionMiddleware';
537
-
538
- const MyComponent = () => {
539
- const { trackAction } = useActionMiddleware();
540
-
541
- const handleClick = () => {
542
- // Track the action
543
- trackAction('myCustomAction', { some: 'data' });
544
-
545
- // Continue with your logic
546
- doSomething();
547
- };
548
-
549
- return <button onClick={handleClick}>Click Me</button>;
550
- };
551
- ```
552
-
553
- ## FAQ
554
-
555
- **Q: Does middleware affect performance?**
556
- A: Minimal impact (~1-2ms per action). Uses fire-and-forget pattern.
557
-
558
- **Q: Can middleware cancel actions?**
559
- A: No. Middleware is tracking-only and cannot block actions.
560
-
561
- **Q: Do I need to configure listeners for middleware to work?**
562
- A: No. Middleware works independently of listeners.
563
-
564
- **Q: Can I use async operations in middleware?**
565
- A: Yes. Middleware supports both sync and async functions.
566
-
567
- **Q: What happens if middleware throws an error?**
568
- A: Errors are caught and logged. The action proceeds normally.
569
-
570
- **Q: Can I track actions from Krpano/POI layer?**
571
- A: Not directly. Use listeners for those events. Middleware tracks component-level actions.
572
-
573
- ## Summary
574
-
575
- ✅ **Tracking-only** - Does not block actions
576
- ✅ **Always fires** - Even without listeners configured
577
- ✅ **Error-safe** - Errors don't break the app
578
- ✅ **Type-safe** - Full TypeScript support
579
- ✅ **Async-ready** - Supports async operations
580
- ✅ **Production-ready** - Tested and optimized
581
-
582
- For analytics, logging, and tracking needs - middleware is the perfect solution! 🎉
@@ -1,67 +0,0 @@
1
- # Planning Documentation
2
-
3
- This folder contains planning documents and specifications for features in the showroom-visualizer library.
4
-
5
- ## Current Documentation
6
-
7
- ### [README-middleware.md](./README-middleware.md)
8
-
9
- Complete documentation for the **Middleware/Analytics Tracking System**:
10
-
11
- - Overview and quick start guide
12
- - Event structure and types
13
- - All 12 tracked actions with examples
14
- - Usage examples (Google Analytics, custom services, conditional tracking)
15
- - Middleware behavior and error handling
16
- - TypeScript types and performance notes
17
- - Component architecture and integration
18
-
19
- ## Documentation Structure
20
-
21
- Each feature should have its own markdown file following this structure:
22
-
23
- ```markdown
24
- # Feature Name
25
-
26
- ## Overview
27
-
28
- Brief description of the feature and its purpose
29
-
30
- ## Quick Start
31
-
32
- Basic usage example
33
-
34
- ## API Reference
35
-
36
- Detailed API documentation with types and examples
37
-
38
- ## Architecture
39
-
40
- How the feature is implemented internally
41
-
42
- ## Usage Examples
43
-
44
- Multiple real-world usage scenarios
45
-
46
- ## FAQ
47
-
48
- Common questions and answers
49
- ```
50
-
51
- ## Adding New Features
52
-
53
- When planning a new feature:
54
-
55
- 1. Create a new markdown file: `feature-name.md`
56
- 2. Use the template structure above
57
- 3. Include TypeScript examples
58
- 4. Document all public APIs
59
- 5. Add integration examples
60
- 6. Link from the main README.md
61
-
62
- ## Maintenance
63
-
64
- - Keep documentation up-to-date with code changes
65
- - Remove outdated planning documents after implementation
66
- - Archive old versions if needed for reference
67
- - Update this README when adding new features