Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true
"singleQuote": true,
"endOfLine": "lf"
Comment on lines +5 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Enforce LF consistently across OS (add .gitattributes).

Prettier will rewrite files to LF on save, but Git checkouts on Windows can still introduce CRLF. Add a repo-level .gitattributes to avoid churn.

Suggested .gitattributes:

* text=auto eol=lf

# binary files
*.png -text
*.jpg -text
*.jpeg -text
*.gif -text
*.pdf -text

Verification script to confirm presence/config:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
if fd -t f ".gitattributes" >/dev/null; then
  echo ".gitattributes found"; echo
  fd -t f ".gitattributes" -x sh -c 'echo "== {} =="; cat {}'
else
  echo "Missing .gitattributes at repo root"
  exit 1
fi

Length of output: 163


Enforce LF line endings via .gitattributes

The .gitattributes file exists at the repo root but is currently empty. To avoid CRLF churn on Windows checkouts, please add the following content:

Files needing update:

  • .gitattributes (repo root)

Suggested diff:

--- .gitattributes
+++ .gitattributes
+* text=auto eol=lf
+
+# binary files
+*.png   -text
+*.jpg   -text
+*.jpeg  -text
+*.gif   -text
+*.pdf   -text
🤖 Prompt for AI Agents
In the .prettierrc file at lines 5 to 6, the review suggests enforcing LF line
endings via the .gitattributes file. To fix this, create or update the
.gitattributes file at the repository root by adding configuration that enforces
LF line endings for all relevant files, preventing CRLF line ending issues on
Windows checkouts.

}
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Changelog

All notable changes to this project will be documented in this file.

## [0.0.2] - 2025-08-10

### Style

- Configure prettier to use lf line endings

### Chore

- Support code lint and commit lint
- Add test lint
- Tsup config file confugured

### Test

- Fix test error

### Docs

- Add CONTRIBUTING.md guide
- Add DEVELOPER.md guide
- Update README.md with detailed project information
- Add horizontal rule to README

### Features

- Migrate tsup configuration to tsup.config.ts
- Add React wrapper and example
- Update package metadata and dependencies
- Setup project structure and CI workflows

### Bug Fixes

- Resolve race condition on initialization
13 changes: 12 additions & 1 deletion examples/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { InteractiveVideo } from '../dist/index';
import { InteractiveVideo } from '@interactive-video-labs/react';

const App = () => {
return (
Expand All @@ -13,6 +13,17 @@ const App = () => {
onAnalyticsEvent={(event, payload) => {
console.log('Analytics Event:', event, payload);
}}
cues={[
{
id: 'cue1',
time: 2,
payload: {
type: 'quiz',
question: 'What is the capital of France?',
answers: ['Paris', 'London', 'Berlin', 'Madrid'],
},
},
]}
/>
</div>
</div>
Expand Down
85 changes: 39 additions & 46 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,55 +59,35 @@ export const InteractiveVideo: React.FC<InteractiveVideoProps> = ({
const uniqueIdRef = useRef<string>(generateUniqueId());

useEffect(() => {
if (containerRef.current && !playerRef.current) {
const playerConfig: PlayerConfig = {
videoUrl,
...restOptions,
};
if (!containerRef.current) return;

try {
setTimeout(() => {
if (containerRef.current) {
const player = new IVLabsPlayer(uniqueIdRef.current, playerConfig);
playerRef.current = player;

if (onAnalyticsEvent) {
player.on('PLAYER_LOADED', (payload?: AnalyticsPayload) =>
onAnalyticsEvent('PLAYER_LOADED', payload)
);
player.on('VIDEO_STARTED', (payload?: AnalyticsPayload) =>
onAnalyticsEvent('VIDEO_STARTED', payload)
);
player.on('VIDEO_PAUSED', (payload?: AnalyticsPayload) =>
onAnalyticsEvent('VIDEO_PAUSED', payload)
);
player.on('VIDEO_ENDED', (payload?: AnalyticsPayload) =>
onAnalyticsEvent('VIDEO_ENDED', payload)
);
player.on('CUE_TRIGGERED', (payload?: AnalyticsPayload) =>
onAnalyticsEvent('CUE_TRIGGERED', payload)
);
player.on('INTERACTION_COMPLETED', (payload?: AnalyticsPayload) =>
onAnalyticsEvent('INTERACTION_COMPLETED', payload)
);
player.on('ERROR', (payload?: AnalyticsPayload) =>
onAnalyticsEvent('ERROR', payload)
);
}
const playerConfig: PlayerConfig = {
videoUrl,
...restOptions,
};

if (cues) {
player.loadCues(cues);
}
try {
const player = new IVLabsPlayer(uniqueIdRef.current, playerConfig);
playerRef.current = player;

if (translations) {
const locale = restOptions.locale || 'en';
player.loadTranslations(locale, translations);
}
}
}, 0);
} catch (error) {
console.error('Error initializing IVLabsPlayer:', error);
if (onAnalyticsEvent) {
const events: AnalyticsEvent[] = [
'PLAYER_LOADED',
'VIDEO_STARTED',
'VIDEO_PAUSED',
'VIDEO_ENDED',
'CUE_TRIGGERED',
'INTERACTION_COMPLETED',
'ERROR',
];
events.forEach((event) => {
player.on(event, (payload?: AnalyticsPayload) =>
onAnalyticsEvent(event, payload)
);
});
}
} catch (error) {
console.error('Error initializing IVLabsPlayer:', error);
}

return () => {
Expand All @@ -116,7 +96,20 @@ export const InteractiveVideo: React.FC<InteractiveVideoProps> = ({
playerRef.current = null;
}
};
}, [videoUrl, onAnalyticsEvent, cues, translations, restOptions]);
}, [videoUrl, onAnalyticsEvent, restOptions]);

useEffect(() => {
if (playerRef.current && cues) {
playerRef.current.loadCues(cues);
}
}, [cues]);

useEffect(() => {
if (playerRef.current && translations) {
const locale = restOptions.locale || 'en';
playerRef.current.loadTranslations(locale, translations);
}
}, [translations, restOptions.locale]);

return (
<div
Expand Down
13 changes: 10 additions & 3 deletions tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { defineConfig } from 'tsup';
import pkg from './package.json';

export default defineConfig({
export default defineConfig((options) => ({
entry: ['src/index.tsx'],
format: ['esm', 'cjs'],
outExtension({ format }) {
return {
js: `.${format === 'esm' ? 'mjs' : 'cjs'}`,
};
},
dts: true,
watch: options.watch,
clean: true,
banner: {
js: `/**
* ${pkg.name} v${pkg.version}
* Author: ${pkg.author}
* @license MIT
*/
`,
`,
},
});
}));