Skip to content

Commit d71ab27

Browse files
feat: add global API for controlling the widget
Co-authored-by: Yurii Kinakh <yuriikinakh5@gmail.com>
1 parent da3d6b5 commit d71ab27

File tree

5 files changed

+117
-11
lines changed

5 files changed

+117
-11
lines changed

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,44 @@ Builds the widget as a standalone UMD bundle (`viewpro-widget.js`) in the `dist`
7979

8080
2. The widget will automatically initialize and render when the page loads.
8181

82+
### Controlling the Widget
83+
84+
The widget exposes a global `ViewProWidget` API on `window` that you can use to control it programmatically from your website.
85+
86+
#### Show / Hide the Floating Button
87+
88+
```js
89+
// Hide the default floating button
90+
ViewProWidget.hideButton();
91+
92+
// Show it again
93+
ViewProWidget.showButton();
94+
```
95+
96+
#### Open / Close the Room Screen
97+
98+
Use this to trigger the video call from your own custom button:
99+
100+
```js
101+
// Open the room screen (same as clicking the floating button)
102+
ViewProWidget.open();
103+
104+
// Close the room screen
105+
ViewProWidget.close();
106+
```
107+
108+
#### Example: Custom Button with Hidden Default Button
109+
110+
```html
111+
<script src="https://viewpro.com/viewpro-widget.js"></script>
112+
<script>
113+
// Hide the default floating button once the widget loads
114+
ViewProWidget.hideButton();
115+
</script>
116+
117+
<button onclick="ViewProWidget.open()">Start Video Call</button>
118+
```
119+
82120
## Git Workflow & Commit Guidelines
83121

84122
### Commit Message Format

src/components/LobbyScreen.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import React, { useState, useEffect } from 'react';
22
import { toast } from 'react-hot-toast';
33
import { api } from '../utils/api';
44
import { SSE_URL, AVATAR_URL, username } from '../utils/constants';
5+
import controller from '../utils/controller';
56

67
export default function LobbyScreen({ setOpen }) {
78
const [userList, setUserList] = useState([]);
9+
const [buttonVisible, setButtonVisible] = useState(controller._state.buttonVisible);
810

911
const getCheck = async () => {
1012
try {
@@ -36,14 +38,17 @@ export default function LobbyScreen({ setOpen }) {
3638
console.error('SSE error:', error);
3739
};
3840

41+
const unsubVisibility = controller.on('buttonVisibility', setButtonVisible);
42+
3943
return () => {
4044
eventSource.removeEventListener('user:logout', getCheck);
4145
eventSource.removeEventListener('user:status', getCheck);
4246
eventSource.close();
47+
unsubVisibility();
4348
};
4449
}, []);
4550

46-
if (userList.length > 0) {
51+
if (buttonVisible && userList.length > 0) {
4752
return (
4853
<div onClick={() => setOpen(true)} className="vp-floating-button">
4954
<div className="vp-floating-button-gradient">

src/components/VideoCall.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
1-
import React, { useState } from 'react';
1+
import React, { useState, useEffect } from 'react';
22
import LobbyScreen from './LobbyScreen';
33
import RoomScreen from './RoomScreen';
4+
import controller from '../utils/controller';
45

56
export default function VideoCall() {
67
const [isOpen, setIsOpen] = useState(false);
78

9+
useEffect(() => {
10+
const unsubOpen = controller.on('open', () => setIsOpen(true));
11+
const unsubClose = controller.on('close', () => setIsOpen(false));
12+
return () => {
13+
unsubOpen();
14+
unsubClose();
15+
};
16+
}, []);
17+
18+
const handleSetOpen = (value) => {
19+
controller._setOpen(value);
20+
setIsOpen(value);
21+
};
22+
823
if (isOpen) {
9-
return <RoomScreen setOpen={setIsOpen} />;
24+
return <RoomScreen setOpen={handleSetOpen} />;
1025
}
1126

12-
return <LobbyScreen setOpen={setIsOpen} />;
27+
return <LobbyScreen setOpen={handleSetOpen} />;
1328
}

src/index.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,18 @@ import React from 'react';
22
import { createRoot } from 'react-dom/client';
33
import './index.css';
44
import App from './App';
5+
import controller from './utils/controller';
56

67
const initViewProWidget = () => {
7-
// Use document instead of window.top.document to avoid cross-origin issues
8-
// Try window.top.document first (for iframe scenarios), fallback to document
98
let doc;
109
try {
1110
doc = window.top && window.top !== window ? window.top.document : document;
1211
} catch (e) {
13-
// Cross-origin error, use current document
1412
doc = document;
1513
}
1614

1715
let container = doc.getElementById('viewpro-widget');
1816
if (!container) {
19-
// Wait for body to be available
2017
if (!doc.body) {
2118
console.warn('ViewProWidget: Document body not ready yet');
2219
return;
@@ -34,21 +31,26 @@ const initViewProWidget = () => {
3431
}
3532
};
3633

37-
// Wait for DOM to be ready before rendering
3834
const renderViewProWidget = () => {
3935
if (document.readyState === 'loading') {
4036
document.addEventListener('DOMContentLoaded', initViewProWidget);
4137
} else {
42-
// DOM is already ready
4338
initViewProWidget();
4439
}
4540
};
4641

47-
// Initialize when script loads
4842
renderViewProWidget();
4943

5044
const ViewProWidget = {
5145
render: renderViewProWidget,
46+
showButton: () => controller.showButton(),
47+
hideButton: () => controller.hideButton(),
48+
open: () => controller.open(),
49+
close: () => controller.close(),
5250
};
5351

52+
if (typeof window !== 'undefined') {
53+
window.ViewProWidget = ViewProWidget;
54+
}
55+
5456
export default ViewProWidget;

src/utils/controller.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const listeners = {};
2+
3+
const controller = {
4+
_state: {
5+
buttonVisible: true,
6+
isOpen: false,
7+
},
8+
9+
on(event, callback) {
10+
if (!listeners[event]) listeners[event] = [];
11+
listeners[event].push(callback);
12+
return () => {
13+
listeners[event] = listeners[event].filter((cb) => cb !== callback);
14+
};
15+
},
16+
17+
_emit(event, data) {
18+
(listeners[event] || []).forEach((cb) => cb(data));
19+
},
20+
21+
showButton() {
22+
this._state.buttonVisible = true;
23+
this._emit('buttonVisibility', true);
24+
},
25+
26+
hideButton() {
27+
this._state.buttonVisible = false;
28+
this._emit('buttonVisibility', false);
29+
},
30+
31+
open() {
32+
this._state.isOpen = true;
33+
this._emit('open');
34+
},
35+
36+
close() {
37+
this._state.isOpen = false;
38+
this._emit('close');
39+
},
40+
41+
_setOpen(value) {
42+
this._state.isOpen = value;
43+
},
44+
};
45+
46+
export default controller;

0 commit comments

Comments
 (0)