saltfish 0.1.0
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 +34 -0
- package/README.md +633 -0
- package/dist/components/buttons/ExitButton.d.ts +9 -0
- package/dist/components/buttons/ExitButton.d.ts.map +1 -0
- package/dist/components/buttons/MinimizeButton.d.ts +11 -0
- package/dist/components/buttons/MinimizeButton.d.ts.map +1 -0
- package/dist/components/buttons/PlayPauseButton.d.ts +13 -0
- package/dist/components/buttons/PlayPauseButton.d.ts.map +1 -0
- package/dist/core/SaltfishPlayer.d.ts +154 -0
- package/dist/core/SaltfishPlayer.d.ts.map +1 -0
- package/dist/core/stateMachine.d.ts +119 -0
- package/dist/core/stateMachine.d.ts.map +1 -0
- package/dist/core/stateMachineConfig.d.ts +7 -0
- package/dist/core/stateMachineConfig.d.ts.map +1 -0
- package/dist/core/store.d.ts +27 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/managers/AnalyticsManager.d.ts +107 -0
- package/dist/managers/AnalyticsManager.d.ts.map +1 -0
- package/dist/managers/ButtonManager.d.ts +40 -0
- package/dist/managers/ButtonManager.d.ts.map +1 -0
- package/dist/managers/CursorManager.d.ts +143 -0
- package/dist/managers/CursorManager.d.ts.map +1 -0
- package/dist/managers/EventManager.d.ts +127 -0
- package/dist/managers/EventManager.d.ts.map +1 -0
- package/dist/managers/InteractionManager.d.ts +58 -0
- package/dist/managers/InteractionManager.d.ts.map +1 -0
- package/dist/managers/PlaylistManager.d.ts +42 -0
- package/dist/managers/PlaylistManager.d.ts.map +1 -0
- package/dist/managers/SessionManager.d.ts +56 -0
- package/dist/managers/SessionManager.d.ts.map +1 -0
- package/dist/managers/SessionRecordingManager.d.ts +52 -0
- package/dist/managers/SessionRecordingManager.d.ts.map +1 -0
- package/dist/managers/ShadowDOMManager.d.ts +35 -0
- package/dist/managers/ShadowDOMManager.d.ts.map +1 -0
- package/dist/managers/StepTimeoutManager.d.ts +38 -0
- package/dist/managers/StepTimeoutManager.d.ts.map +1 -0
- package/dist/managers/TransitionManager.d.ts +73 -0
- package/dist/managers/TransitionManager.d.ts.map +1 -0
- package/dist/managers/VideoManager.d.ts +231 -0
- package/dist/managers/VideoManager.d.ts.map +1 -0
- package/dist/observers/EventObserver.d.ts +52 -0
- package/dist/observers/EventObserver.d.ts.map +1 -0
- package/dist/observers/UIObserver.d.ts +43 -0
- package/dist/observers/UIObserver.d.ts.map +1 -0
- package/dist/patterns/Observer.d.ts +63 -0
- package/dist/patterns/Observer.d.ts.map +1 -0
- package/dist/patterns/PlayerStateSubject.d.ts +34 -0
- package/dist/patterns/PlayerStateSubject.d.ts.map +1 -0
- package/dist/player.js +8 -0
- package/dist/player.min.js +6 -0
- package/dist/saltfish-playlist-player.es.js +7202 -0
- package/dist/saltfish-playlist-player.umd.js +1 -0
- package/dist/styles/index.d.ts +14 -0
- package/dist/styles/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +337 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/ErrorHandler.d.ts +106 -0
- package/dist/utils/ErrorHandler.d.ts.map +1 -0
- package/dist/utils/PositionCalculator.d.ts +90 -0
- package/dist/utils/PositionCalculator.d.ts.map +1 -0
- package/dist/utils/deviceDetection.d.ts +90 -0
- package/dist/utils/deviceDetection.d.ts.map +1 -0
- package/dist/utils/dimensions.d.ts +26 -0
- package/dist/utils/dimensions.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +35 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/mockManifest.d.ts +27 -0
- package/dist/utils/mockManifest.d.ts.map +1 -0
- package/package.json +87 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
SALTFISH PROPRIETARY LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Saltfish. All rights reserved.
|
|
4
|
+
|
|
5
|
+
NOTICE: This software and associated documentation files (the "Software") are
|
|
6
|
+
proprietary and confidential information of Saltfish.
|
|
7
|
+
|
|
8
|
+
RESTRICTIONS:
|
|
9
|
+
1. The Software is licensed, not sold, to authorized customers only.
|
|
10
|
+
2. Unauthorized copying, distribution, modification, public display, or use is
|
|
11
|
+
strictly prohibited.
|
|
12
|
+
3. Commercial use is permitted only by authorized licensees with valid commercial
|
|
13
|
+
licenses from Saltfish.
|
|
14
|
+
4. Reverse engineering, decompilation, or disassembly is prohibited.
|
|
15
|
+
5. The Software may not be redistributed or sublicensed without explicit written
|
|
16
|
+
permission from Saltfish.
|
|
17
|
+
|
|
18
|
+
PERMITTED USE:
|
|
19
|
+
- Licensed customers may use the Software solely for their internal business
|
|
20
|
+
purposes as specified in their commercial agreement with Saltfish.
|
|
21
|
+
- Evaluation use is permitted for up to 30 days for potential customers.
|
|
22
|
+
|
|
23
|
+
WARRANTY DISCLAIMER:
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
27
|
+
SALTFISH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
28
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
29
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
30
|
+
|
|
31
|
+
For licensing inquiries, contact: licensing@saltfish.ai
|
|
32
|
+
|
|
33
|
+
This license is effective as of the date of first use and remains in effect
|
|
34
|
+
until terminated by Saltfish or upon breach of these terms.
|
package/README.md
ADDED
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
# Saltfish Player
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Saltfish Player is an interactive, video-guided tour system designed to provide engaging walkthroughs and feature introductions within web applications. It utilizes a core player component that manages video playback, user interactions, state, and transitions between steps defined in a playlist manifest. The player is designed to be embedded via a script tag and controlled through a simple JavaScript API.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
* **playlist-Based Guidance:** Delivers interactive tours defined by JSON manifests, outlining steps, videos, and transitions
|
|
10
|
+
* **Video Playback:** Manages loading, playing, pausing, and seeking videos for each step
|
|
11
|
+
* **Interactive Elements:** Supports overlay buttons and interactions with existing DOM elements to guide users
|
|
12
|
+
* **State Management:** Uses Zustand for managing player state (playing, paused, minimized, etc.) and playlist progress
|
|
13
|
+
* **Shadow DOM Encapsulation:** Renders the player UI within a Shadow DOM to prevent style conflicts with the host application
|
|
14
|
+
* **Drag & Minimize:** Allows users to reposition the player via dragging (if enabled) and minimize it
|
|
15
|
+
* **Analytics:** Includes an `AnalyticsManager` for tracking events like playlist start/complete, step views, interactions, and errors.
|
|
16
|
+
* **Transitions:** Manages transitions between steps based on timeouts, DOM clicks, URL path changes, or other interactions
|
|
17
|
+
|
|
18
|
+
## Technology Stack
|
|
19
|
+
|
|
20
|
+
* **Language:** TypeScript
|
|
21
|
+
* **Bundler:** Vite
|
|
22
|
+
* **State Management:** Zustand
|
|
23
|
+
* **Testing:**
|
|
24
|
+
* Unit/Integration: Jest
|
|
25
|
+
* E2E: Playwright
|
|
26
|
+
* **Build:** Node.js script (`scripts/build.js`)
|
|
27
|
+
|
|
28
|
+
## Project Structure
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
.
|
|
32
|
+
├── e2e/ # End-to-end tests
|
|
33
|
+
├── scripts/ # Build scripts
|
|
34
|
+
├── src/
|
|
35
|
+
│ ├── core/ # Core player logic and state management
|
|
36
|
+
│ ├── managers/ # Manages specific functionalities (Video, UI, State, etc.)
|
|
37
|
+
│ ├── styles/ # CSS styles
|
|
38
|
+
│ ├── tests/ # Unit and integration tests
|
|
39
|
+
│ ├── types/ # TypeScript type definitions
|
|
40
|
+
│ ├── utils/ # Utility functions (logger, mocks)
|
|
41
|
+
│ └── index.ts # Main entry point, exposes public API
|
|
42
|
+
├── .gitignore
|
|
43
|
+
├── index.html # Development environment HTML
|
|
44
|
+
├── jest.config.js # Jest configuration
|
|
45
|
+
├── package.json # Project dependencies and scripts
|
|
46
|
+
├── playwright.config.ts # Playwright configuration
|
|
47
|
+
├── tsconfig.json # TypeScript configuration
|
|
48
|
+
├── vite.config.ts # Vite configuration for main build
|
|
49
|
+
└── vite.test.config.ts # Vite configuration for test server
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Core Components
|
|
53
|
+
|
|
54
|
+
* **`SaltfishPlayer` (`src/core/SaltfishPlayer.ts`)**: The main orchestrator class. Initializes managers, handles state changes, and manages the player lifecycle.
|
|
55
|
+
* **`saltfishStore` (`src/core/store.ts`)**: Zustand-based store for managing global player state.
|
|
56
|
+
* **`ShadowDOMManager` (`src/managers/ShadowDOMManager.ts`)**: Creates and manages the Shadow DOM container for UI encapsulation.
|
|
57
|
+
* **`VideoManager` (`src/managers/VideoManager.ts`)**: Handles video loading, playback control (play, pause, seek), progress tracking, and completion policies.
|
|
58
|
+
* **`TransitionManager` (`src/managers/TransitionManager.ts`)**: Manages the logic for transitioning between steps based on defined conditions (e.g., DOM clicks, timeouts).
|
|
59
|
+
* **`InteractionManager` (`src/managers/InteractionManager.ts`)**: Handles the creation and events for interactive overlay buttons and DOM interaction listeners defined in steps.
|
|
60
|
+
* **`CursorManager` (`src/managers/CursorManager.ts`)**: Manages the display and animation of a virtual cursor to guide the user.
|
|
61
|
+
* **`ButtonManager` (`src/managers/ButtonManager.ts`)**: Manages the visibility and state of core player controls like play/pause buttons.
|
|
62
|
+
* **`PlaylistManager` (`src/managers/PlaylistManager.ts`)**: Handles playlist-related functionality including tracking playlist progress, updating watched status, and managing playlist state in the backend.
|
|
63
|
+
* **`AnalyticsManager` (`src/managers/AnalyticsManager.ts`)**: Handles queueing and sending analytics events.
|
|
64
|
+
|
|
65
|
+
## Getting Started
|
|
66
|
+
|
|
67
|
+
### Prerequisites
|
|
68
|
+
|
|
69
|
+
* Node.js and npm
|
|
70
|
+
|
|
71
|
+
### Setup
|
|
72
|
+
|
|
73
|
+
1. Clone the repository.
|
|
74
|
+
2. Install dependencies:
|
|
75
|
+
```bash
|
|
76
|
+
npm install
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Build
|
|
80
|
+
|
|
81
|
+
To create a production build (outputs to `dist/` directory):
|
|
82
|
+
```bash
|
|
83
|
+
npm run build
|
|
84
|
+
```
|
|
85
|
+
This script cleans the dist folder, compiles TypeScript, bundles with Vite, and generates CDN-ready player.js and player.min.js files.
|
|
86
|
+
|
|
87
|
+
### Development
|
|
88
|
+
|
|
89
|
+
#### Main Dev Server
|
|
90
|
+
Runs Vite dev server for index.html.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run dev
|
|
94
|
+
```
|
|
95
|
+
Access at http://localhost:3000.
|
|
96
|
+
|
|
97
|
+
#### Test Dev Server
|
|
98
|
+
Runs Vite dev server specifically for the E2E test environment (e2e/test.html).
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm run dev:test
|
|
102
|
+
```
|
|
103
|
+
Access at http://localhost:3010.
|
|
104
|
+
|
|
105
|
+
## Usage (API)
|
|
106
|
+
|
|
107
|
+
The Saltfish Player is exposed via the global `saltfish` object.
|
|
108
|
+
|
|
109
|
+
## Installation
|
|
110
|
+
|
|
111
|
+
### Option 1: npm Package (Recommended)
|
|
112
|
+
|
|
113
|
+
Install the package using npm:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npm install saltfish-playlist-player
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Then import and use in your application:
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
// ES modules
|
|
123
|
+
import saltfish from 'saltfish-playlist-player';
|
|
124
|
+
|
|
125
|
+
// CommonJS
|
|
126
|
+
const saltfish = require('saltfish-playlist-player');
|
|
127
|
+
|
|
128
|
+
// Initialize and use
|
|
129
|
+
saltfish.init('YOUR_API_TOKEN');
|
|
130
|
+
saltfish.startPlaylist('your_playlist_id');
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Option 2: CDN/Script Tag
|
|
134
|
+
|
|
135
|
+
Add the built player.js script to your HTML:
|
|
136
|
+
|
|
137
|
+
```html
|
|
138
|
+
<!-- Production version (minified) -->
|
|
139
|
+
<script src="https://unpkg.com/saltfish-playlist-player@latest/dist/player.min.js"></script>
|
|
140
|
+
|
|
141
|
+
<!-- Or development version -->
|
|
142
|
+
<script src="https://unpkg.com/saltfish-playlist-player@latest/dist/player.js"></script>
|
|
143
|
+
|
|
144
|
+
<!-- Or from your own hosting -->
|
|
145
|
+
<script src="/path/to/dist/player.js"></script>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Include the script
|
|
149
|
+
Add the built player.js script to your HTML.
|
|
150
|
+
|
|
151
|
+
```html
|
|
152
|
+
<script src="/path/to/dist/player.js"></script>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Simplified API (No Await Required)
|
|
156
|
+
|
|
157
|
+
The Saltfish API now features an improved design that eliminates the need to use `await` keywords with its asynchronous methods. The API internally manages the sequencing of operations, making your code cleaner and more straightforward.
|
|
158
|
+
|
|
159
|
+
#### Key Benefits:
|
|
160
|
+
- Call methods directly in sequence without `await`
|
|
161
|
+
- No need for async functions or promise chains
|
|
162
|
+
- Methods still return promises for backward compatibility
|
|
163
|
+
- Built-in command queueing handles proper execution order
|
|
164
|
+
- Error handling is maintained
|
|
165
|
+
|
|
166
|
+
### Initialize
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
// Basic initialization with token - no await needed!
|
|
170
|
+
saltfish.init('YOUR_API_TOKEN');
|
|
171
|
+
|
|
172
|
+
// Or with configuration object
|
|
173
|
+
saltfish.init({
|
|
174
|
+
token: 'YOUR_API_TOKEN',
|
|
175
|
+
persistence: true,
|
|
176
|
+
sessionRecording: false,
|
|
177
|
+
enableAnalytics: true // Default: true, set to false to disable analytics in development
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Disable analytics for development/testing environments
|
|
181
|
+
saltfish.init({
|
|
182
|
+
token: 'YOUR_API_TOKEN',
|
|
183
|
+
enableAnalytics: false // No analytics events will be sent
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Configuration Options
|
|
188
|
+
|
|
189
|
+
- **`token`** (string, required): Your Saltfish API token for authentication
|
|
190
|
+
- **`persistence`** (boolean, optional): Whether to persist user progress between sessions (default: `false`)
|
|
191
|
+
- **`sessionRecording`** (boolean, optional): Whether to enable session recording for analytics (default: `false`). When enabled, records one continuous session using sessionId and userId only, independent of playlist runs.
|
|
192
|
+
- **`enableAnalytics`** (boolean, optional): Whether to collect and send analytics data (default: `true`). Set to `false` in development environments to avoid noisy data
|
|
193
|
+
|
|
194
|
+
### Identify User (Optional)
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
saltfish.identify('user-123', { email: 'user@example.com', name: 'Test User' });
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Start a playlist
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
// No need to use await - call startPlaylist directly after init
|
|
204
|
+
saltfish.init('YOUR_API_TOKEN');
|
|
205
|
+
saltfish.startPlaylist('your_playlist_id');
|
|
206
|
+
|
|
207
|
+
// Or use events to know when initialization is complete
|
|
208
|
+
saltfish.on('initialized', () => {
|
|
209
|
+
console.log('Saltfish player is ready');
|
|
210
|
+
saltfish.startPlaylist('your_playlist_id');
|
|
211
|
+
});
|
|
212
|
+
saltfish.init('YOUR_API_TOKEN');
|
|
213
|
+
|
|
214
|
+
// For those who prefer async/await, this still works too
|
|
215
|
+
async function startPlayer() {
|
|
216
|
+
await saltfish.init('YOUR_API_TOKEN');
|
|
217
|
+
await saltfish.startPlaylist('your_playlist_id');
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The player will attempt to fetch the manifest for your_playlist_id from /api/manifests/your_playlist_id. If it fails or if window.demoManifest is set, it will use a mock manifest.
|
|
222
|
+
|
|
223
|
+
#### Playlist Options
|
|
224
|
+
|
|
225
|
+
The `startPlaylist` method accepts an optional second parameter for configuration:
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
saltfish.startPlaylist('your_playlist_id', {
|
|
229
|
+
position: 'top-left', // Player position: 'top-left', 'top-right', 'bottom-left', 'bottom-right', 'center'
|
|
230
|
+
allowDrag: false, // Whether the player can be dragged (default: true)
|
|
231
|
+
startNodeId: 'step_2', // Start from a specific step instead of the default
|
|
232
|
+
once: true // Only play if user hasn't watched this playlist before
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
##### Once Option
|
|
237
|
+
|
|
238
|
+
The `once: true` option ensures a playlist only plays if the user hasn't completed it before. This is useful for onboarding flows or important announcements that should only be shown once:
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// User identification is required when using the once option
|
|
242
|
+
saltfish.identify('user-123', { email: 'user@example.com' });
|
|
243
|
+
|
|
244
|
+
// This will only play if the user hasn't completed 'onboarding_tutorial' before
|
|
245
|
+
saltfish.startPlaylist('onboarding_tutorial', { once: true });
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Requirements for `once` option:**
|
|
249
|
+
- User must be identified using `saltfish.identify()` before starting the playlist
|
|
250
|
+
- User data (including watch history) must be loaded from the backend
|
|
251
|
+
- The playlist will be blocked if the user has already completed it
|
|
252
|
+
|
|
253
|
+
**Behavior:**
|
|
254
|
+
- ✅ First time: Playlist plays normally
|
|
255
|
+
- ❌ Subsequent times: Playlist is blocked and an error is thrown
|
|
256
|
+
- 🔄 Without `once` option: Playlist always plays regardless of watch history
|
|
257
|
+
|
|
258
|
+
### How It Works
|
|
259
|
+
|
|
260
|
+
The Saltfish API implements a command queuing system that:
|
|
261
|
+
|
|
262
|
+
1. Tracks the initialization state of the player
|
|
263
|
+
2. Automatically queues commands called before initialization completes
|
|
264
|
+
3. Processes the queue in the correct order after initialization
|
|
265
|
+
4. Handles errors appropriately
|
|
266
|
+
|
|
267
|
+
This means you can call methods like `init()` and `startPlaylist()` in sequence without worrying about their asynchronous nature. The API handles the complexity for you.
|
|
268
|
+
|
|
269
|
+
### Event Subscription
|
|
270
|
+
|
|
271
|
+
As an alternative to promises, you can use the event system to respond to player state changes:
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
// Subscribe to initialization completion
|
|
275
|
+
saltfish.on('initialized', () => {
|
|
276
|
+
console.log('Player initialized');
|
|
277
|
+
saltfish.startPlaylist('your_playlist_id');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Subscribe to other events
|
|
281
|
+
const playlistEndedListener = event => {
|
|
282
|
+
console.log(`Playlist ${event.playlist.id} ended at ${new Date(event.timestamp)}`);
|
|
283
|
+
// Perform actions when playlist ends
|
|
284
|
+
};
|
|
285
|
+
saltfish.on('playlistEnded', playlistEndedListener);
|
|
286
|
+
|
|
287
|
+
// Unsubscribe when no longer needed
|
|
288
|
+
saltfish.off('playlistEnded', playlistEndedListener);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
See [Event Documentation](./docs/events.md) for a complete list of events and payload structures.
|
|
292
|
+
|
|
293
|
+
### Destroy
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
saltfish.destroy();
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Manifest Example
|
|
300
|
+
|
|
301
|
+
A manifest is a JSON object that defines the structure and content of a playlist. It specifies the steps, videos, interactive elements (buttons, DOM interactions, cursor animations), and transitions between steps.
|
|
302
|
+
|
|
303
|
+
Here's an example from the mock data used in the project (src/utils/mockManifest.ts):
|
|
304
|
+
|
|
305
|
+
```json
|
|
306
|
+
{
|
|
307
|
+
"id": "realistic_mock_playlist",
|
|
308
|
+
"name": "Feature Walkthrough Tour",
|
|
309
|
+
"version": "1.0.0",
|
|
310
|
+
"position": "bottom-right",
|
|
311
|
+
"startStep": "intro",
|
|
312
|
+
"steps": [
|
|
313
|
+
{
|
|
314
|
+
"id": "intro",
|
|
315
|
+
"videoUrl": "https://storage.saltfish.ai/videos/QSWY4Wc8pcBVYHNQM12P.mp4",
|
|
316
|
+
"transitions": [
|
|
317
|
+
{
|
|
318
|
+
"type": "timeout",
|
|
319
|
+
"timeout": 30000,
|
|
320
|
+
"nextStep": "dashboard_overview"
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"id": "dashboard_overview",
|
|
326
|
+
"videoUrl": "https://storage.saltfish.ai/videos/rEWoNbKLAUQSvLmzHGmv.mp4",
|
|
327
|
+
"cursorAnimations": [
|
|
328
|
+
{
|
|
329
|
+
"duration": 3000,
|
|
330
|
+
"easing": "ease-out",
|
|
331
|
+
"targetSelector": ".container > div:nth-of-type(1)"
|
|
332
|
+
}
|
|
333
|
+
],
|
|
334
|
+
"domInteractions": [
|
|
335
|
+
{
|
|
336
|
+
"selector": "#feature-chart",
|
|
337
|
+
"action": "hover",
|
|
338
|
+
"waitFor": false
|
|
339
|
+
}
|
|
340
|
+
],
|
|
341
|
+
"buttons": [
|
|
342
|
+
{
|
|
343
|
+
"id": "skip_button",
|
|
344
|
+
"text": "Skip",
|
|
345
|
+
"position": { "x": 50, "y": 140 },
|
|
346
|
+
"size": { "width": 70, "height": 30 },
|
|
347
|
+
"action": { "type": "goto", "target": "user_settings" }
|
|
348
|
+
}
|
|
349
|
+
],
|
|
350
|
+
"transitions": [
|
|
351
|
+
{
|
|
352
|
+
"type": "timeout",
|
|
353
|
+
"timeout": 18000,
|
|
354
|
+
"nextStep": "user_settings"
|
|
355
|
+
}
|
|
356
|
+
]
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"id": "user_settings",
|
|
360
|
+
"videoUrl": "https://storage.saltfish.ai/codeformer/oNcN4ZoQXFFnMmaOwEC3/result.mp4",
|
|
361
|
+
"buttons": [
|
|
362
|
+
{
|
|
363
|
+
"id": "finish_button",
|
|
364
|
+
"text": "Finish Tour",
|
|
365
|
+
"position": { "x": 220, "y": 140 },
|
|
366
|
+
"size": { "width": 120, "height": 40 },
|
|
367
|
+
"action": { "type": "goto", "target": "conclusion" }
|
|
368
|
+
}
|
|
369
|
+
],
|
|
370
|
+
"transitions": [
|
|
371
|
+
{
|
|
372
|
+
"type": "dom-click",
|
|
373
|
+
"target": ".container > div:nth-of-type(1)",
|
|
374
|
+
"nextStep": "conclusion"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"type": "url-path",
|
|
378
|
+
"target": "/settings/profile",
|
|
379
|
+
"nextStep": "profile_settings"
|
|
380
|
+
}
|
|
381
|
+
]
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"id": "profile_settings",
|
|
385
|
+
"videoUrl": "https://storage.saltfish.ai/videos/profile_settings_demo.mp4",
|
|
386
|
+
"transitions": [
|
|
387
|
+
{
|
|
388
|
+
"type": "timeout",
|
|
389
|
+
"timeout": 15000,
|
|
390
|
+
"nextStep": "conclusion"
|
|
391
|
+
}
|
|
392
|
+
]
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"id": "conclusion",
|
|
396
|
+
"videoUrl": "https://storage.saltfish.ai/memo/5z5IDHYzuGDjx41hpvSo/result.mp4",
|
|
397
|
+
"buttons": [
|
|
398
|
+
{
|
|
399
|
+
"id": "close_button",
|
|
400
|
+
"text": "Close Tour",
|
|
401
|
+
"position": { "x": 220, "y": 140 },
|
|
402
|
+
"size": { "width": 120, "height": 40 },
|
|
403
|
+
"action": { "type": "next", "target": "" }
|
|
404
|
+
}
|
|
405
|
+
],
|
|
406
|
+
"transitions": []
|
|
407
|
+
}
|
|
408
|
+
]
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Testing
|
|
413
|
+
|
|
414
|
+
The project includes unit, integration, and end-to-end tests.
|
|
415
|
+
|
|
416
|
+
### Run all Jest tests (unit/integration)
|
|
417
|
+
```bash
|
|
418
|
+
npm test
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Run Jest tests in watch mode
|
|
422
|
+
```bash
|
|
423
|
+
npm run test:watch
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Run Jest tests with coverage
|
|
427
|
+
```bash
|
|
428
|
+
npm run test:coverage
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Run Playwright E2E tests
|
|
432
|
+
Ensure the test dev server is not running separately.
|
|
433
|
+
Install browser binaries if needed: `npx playwright install`
|
|
434
|
+
```bash
|
|
435
|
+
npm run test:e2e
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Run Playwright E2E tests in UI mode
|
|
439
|
+
```bash
|
|
440
|
+
npm run test:e2e:ui
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
See src/tests/README.md for more details on the testing strategy.
|
|
444
|
+
|
|
445
|
+
## React Dashboard
|
|
446
|
+
|
|
447
|
+
A React-based dashboard has been added to the project for easier testing and demonstration of the Saltfish playlist Player. The dashboard provides a user-friendly interface with multiple routes to trigger and manage different workplaylists.
|
|
448
|
+
|
|
449
|
+
### Features
|
|
450
|
+
|
|
451
|
+
- Multiple routes with React Router
|
|
452
|
+
- Dashboard for overview statistics
|
|
453
|
+
- Workplaylists page to trigger different interactive guides
|
|
454
|
+
- Settings page to configure the Saltfish player
|
|
455
|
+
- Integration with Saltfish playlist Player
|
|
456
|
+
|
|
457
|
+
### Running the Dashboard
|
|
458
|
+
|
|
459
|
+
1. Navigate to the app directory:
|
|
460
|
+
```bash
|
|
461
|
+
cd app
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
2. Install dependencies:
|
|
465
|
+
```bash
|
|
466
|
+
npm install
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
3. Start the development server:
|
|
470
|
+
```bash
|
|
471
|
+
npm run dev
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
4. Open your browser and navigate to:
|
|
475
|
+
```
|
|
476
|
+
http://localhost:3000
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
The dashboard will automatically load and integrate with the Saltfish playlist Player. You can use the interface to initialize the player, identify users, and trigger different workplaylists.
|
|
480
|
+
|
|
481
|
+
### Transition Types
|
|
482
|
+
|
|
483
|
+
The Saltfish player supports multiple types of transitions between steps:
|
|
484
|
+
|
|
485
|
+
#### Timeout Transitions
|
|
486
|
+
Automatic transitions after a specified duration.
|
|
487
|
+
|
|
488
|
+
```json
|
|
489
|
+
{
|
|
490
|
+
"type": "timeout",
|
|
491
|
+
"timeout": 5000, // milliseconds
|
|
492
|
+
"nextStep": "next_step_id"
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### DOM Click Transitions
|
|
497
|
+
Transitions triggered when the user clicks a specific element in the DOM.
|
|
498
|
+
|
|
499
|
+
```json
|
|
500
|
+
{
|
|
501
|
+
"type": "dom-click",
|
|
502
|
+
"target": ".some-element-selector",
|
|
503
|
+
"nextStep": "next_step_id"
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
#### URL Path Transitions
|
|
508
|
+
Transitions triggered when the URL path matches a specified pattern. This is useful for playlists that span multiple pages or routes in a single-page application.
|
|
509
|
+
|
|
510
|
+
```json
|
|
511
|
+
{
|
|
512
|
+
"type": "url-path",
|
|
513
|
+
"target": "/dashboard", // Will match when URL path is exactly "/dashboard"
|
|
514
|
+
"nextStep": "dashboard_step"
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
```json
|
|
519
|
+
{
|
|
520
|
+
"type": "url-path",
|
|
521
|
+
"target": "^/settings/.*", // Regex pattern matching any URL starting with "/settings/"
|
|
522
|
+
"nextStep": "settings_step"
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
When a URL path transition is specified:
|
|
527
|
+
1. The player monitors URL changes in the application
|
|
528
|
+
2. When the URL changes to match the pattern, the player automatically transitions to the next step
|
|
529
|
+
3. This allows seamless continuation of playlists across different pages or routes
|
|
530
|
+
|
|
531
|
+
## Build Options
|
|
532
|
+
|
|
533
|
+
The project supports two types of builds:
|
|
534
|
+
|
|
535
|
+
### Production Build
|
|
536
|
+
|
|
537
|
+
The production build removes all console.log statements to reduce the bundle size. This is the default build type.
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
npm run build
|
|
541
|
+
# or
|
|
542
|
+
npm run build:prod
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Test Build
|
|
546
|
+
|
|
547
|
+
The test build preserves all console.log statements for easier debugging. This creates a slightly larger bundle but makes troubleshooting much easier.
|
|
548
|
+
|
|
549
|
+
```bash
|
|
550
|
+
npm run build:test
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
The test build will generate files with the `-test` suffix to distinguish them from production files.
|
|
554
|
+
|
|
555
|
+
### Size Comparison
|
|
556
|
+
|
|
557
|
+
- Production build: Optimized for performance and smaller file size
|
|
558
|
+
- Test build: Includes debugging information and console logs
|
|
559
|
+
|
|
560
|
+
## State Management
|
|
561
|
+
|
|
562
|
+
The player implements a state machine to manage transitions between different states. The main states include:
|
|
563
|
+
|
|
564
|
+
* **idle**: Initial state before the player is used
|
|
565
|
+
* **loading**: Loading a playlist or video
|
|
566
|
+
* **playing**: Video is actively playing
|
|
567
|
+
* **paused**: Video is paused by user action
|
|
568
|
+
* **waitingForInteraction**: Video has ended and waiting for user interaction or transition trigger
|
|
569
|
+
* **autoplayBlocked**: Player requires user interaction for autoplay to continue
|
|
570
|
+
* **minimized**: Player is minimized
|
|
571
|
+
* **error**: An error has occurred
|
|
572
|
+
* **completed**: Playlist has finished playing
|
|
573
|
+
|
|
574
|
+
The `completed` state is triggered when the last video in a playlist finishes playing and there are no transitions defined, or when the `completePlaylist` method is called. This state signals that the player should be destroyed after showing a completion message.
|
|
575
|
+
|
|
576
|
+
### State Transitions
|
|
577
|
+
|
|
578
|
+
State transitions occur based on various events:
|
|
579
|
+
- User actions (play, pause, minimize)
|
|
580
|
+
- Video completion
|
|
581
|
+
- Transitions based on URL changes, DOM interactions, or timeouts
|
|
582
|
+
- Automatically when a step completes (via `VIDEO_ENDED` event)
|
|
583
|
+
|
|
584
|
+
The player ensures transitions work smoothly by only setting up DOM and URL-based transitions after the video has ended, preventing premature transitions while the video is still playing.
|
|
585
|
+
|
|
586
|
+
## Progress Bar and Video Playback
|
|
587
|
+
|
|
588
|
+
The player features a sophisticated progress bar system that smoothly updates during video playback:
|
|
589
|
+
|
|
590
|
+
* **Transition Handling**: The progress bar applies different CSS transitions based on the current video state (playing, paused, seeking, ended) to ensure smooth animations without jumps.
|
|
591
|
+
|
|
592
|
+
* **Forced Reflow**: To prevent visual glitches, the player uses forced reflow techniques to ensure CSS changes are applied in the correct sequence.
|
|
593
|
+
|
|
594
|
+
* **Play/Pause Optimization**: When playing or pausing, the progress bar updates immediately to prevent jumpy behavior, especially during rapid state changes.
|
|
595
|
+
|
|
596
|
+
* **Seeking Support**: Special handlers for seeking events ensure the progress bar updates correctly during user-initiated seeking.
|
|
597
|
+
|
|
598
|
+
* **End-of-Video Transition**: A smooth ease-out transition is applied when the video reaches the end, creating a professional finishing effect.
|
|
599
|
+
|
|
600
|
+
These improvements ensure a highly polished user experience with fluid progress tracking during all phases of video playback.
|
|
601
|
+
|
|
602
|
+
## Transitions Between Steps
|
|
603
|
+
|
|
604
|
+
Transitions control how the player moves from one step to the next in a playlist. The player supports several types of transitions:
|
|
605
|
+
|
|
606
|
+
* **timeout**: Automatically transitions after a specified time
|
|
607
|
+
* **dom-click**: Transitions when a user clicks on a specified DOM element
|
|
608
|
+
* **url-path**: Transitions when the URL path changes to match a specified pattern
|
|
609
|
+
* **dom-element-visible**: Transitions when a specified DOM element becomes visible
|
|
610
|
+
* **interaction**: Transitions based on user interactions with overlay buttons
|
|
611
|
+
|
|
612
|
+
### Delayed Transition Setup
|
|
613
|
+
|
|
614
|
+
To improve the user experience, DOM-based transitions (dom-click, url-path, dom-element-visible) are only set up after the video has finished playing. This prevents transitions from being triggered prematurely while the user is watching the video.
|
|
615
|
+
|
|
616
|
+
The process works as follows:
|
|
617
|
+
|
|
618
|
+
1. When a step begins, the video starts playing
|
|
619
|
+
2. The player waits for the video to complete (`ended` event)
|
|
620
|
+
3. After the video ends, transitions are set up based on the step's configuration
|
|
621
|
+
4. The player enters the `waitingForInteraction` state if interaction-based transitions are present
|
|
622
|
+
5. When a transition is triggered, the player moves to the next step
|
|
623
|
+
|
|
624
|
+
This delayed setup ensures users can watch the entire instructional video before needing to interact with the interface or having URL-based transitions occur.
|
|
625
|
+
|
|
626
|
+
### Handling Completion
|
|
627
|
+
|
|
628
|
+
When a step has no transitions defined, or when the last step in a playlist is reached, the player automatically transitions to the `completed` state. This can happen in two ways:
|
|
629
|
+
|
|
630
|
+
1. Auto-completion when no transitions are defined for a step
|
|
631
|
+
2. Manual completion via the `completePlaylist` method
|
|
632
|
+
|
|
633
|
+
The player then displays a completion message and is automatically destroyed after a short delay.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExitButton.d.ts","sourceRoot":"","sources":["../../../src/components/buttons/ExitButton.ts"],"names":[],"mappings":"AAGA,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,aAAa,CAAc;gBAEvB,aAAa,EAAE,WAAW;IAYtC,OAAO,CAAC,WAAW;IAsBZ,gBAAgB,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI;IAS5C,OAAO,IAAI,IAAI;CAIvB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class MinimizeButton {
|
|
2
|
+
private button;
|
|
3
|
+
private playerElement;
|
|
4
|
+
constructor(playerElement: HTMLElement);
|
|
5
|
+
private handleClick;
|
|
6
|
+
minimize(): void;
|
|
7
|
+
maximize(): void;
|
|
8
|
+
updateVisibility(isMinimized: boolean): void;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=MinimizeButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MinimizeButton.d.ts","sourceRoot":"","sources":["../../../src/components/buttons/MinimizeButton.ts"],"names":[],"mappings":"AAEA,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,aAAa,CAAc;gBAEvB,aAAa,EAAE,WAAW;IAYtC,OAAO,CAAC,WAAW;IAqBZ,QAAQ,IAAI,IAAI;IAIhB,QAAQ,IAAI,IAAI;IAIhB,gBAAgB,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI;IAS5C,OAAO,IAAI,IAAI;CAIvB"}
|