Skip to content

Commit

Permalink
Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
hanios123 committed Dec 20, 2023
1 parent c618529 commit e1a99d5
Show file tree
Hide file tree
Showing 18 changed files with 1,298 additions and 0 deletions.
Binary file added fast-press-game/public/asset/audio/correct001.mp3
Binary file not shown.
24 changes: 24 additions & 0 deletions fast-press-game/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fast Press Game Custom Plugin</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
<!-- React and ReactDOM -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<!-- Babel -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script
type="text/babel"
src="./js/hooks/useCustomObjectClient.js?ver0.0.1"
></script>
<script type="text/babel" src="./js/hooks/useGameLogic.js"></script>
<script type="text/babel" src="./js/app.js"></script>
</body>
</html>
102 changes: 102 additions & 0 deletions fast-press-game/public/js/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
const { Fragment, useEffect, useState } = React;

function InitView({ isInit, loadingNode, children }) {
if (!isInit) return loadingNode;
return <React.Fragment>{children}</React.Fragment>;
}

function StandbyView({ isStandby, children }) {
if (!isStandby) return null;
return <React.Fragment>{children}</React.Fragment>;
}

function PlayingView({ isPlaying, children }) {
if (!isPlaying) return null;
return <React.Fragment>{children}</React.Fragment>;
}

function ResultView({ hasResult, children }) {
if (!hasResult) return null;
return <React.Fragment>{children}</React.Fragment>;
}

function HostView({ isHost, children }) {
if (!isHost) return null;
return <React.Fragment>{children}</React.Fragment>;
}

function GuestView({ isHost, children }) {
if (isHost) return null;
return <React.Fragment>{children}</React.Fragment>;
}

function FastPressGameApp() {
const {
user,
isGameMaster,
isGameStarted,
isStandby,
fastest,
winner,
startGame,
answer,
reset,
} = useGameLogic();

return (
<div className="container mx-auto text-center min-h-screen bg-white flex flex-col justify-center items-center gap-4 select-none">
<InitView
isInit={user.id}
loadingNode={<div className="text-4xl">Connect to object</div>}
>
<StandbyView isStandby={!isGameStarted && !isStandby && !winner}>
<HostView isHost={isGameMaster}>
<div className="flex flex-col gap-4">
<div className="text-xl">Fast Press Game</div>
<button
onClick={startGame}
className="bg-blue-300 hover:bg-blue-400 active:bg-blue-500 p-6 rounded shadow text-4xl"
>
Start
</button>
</div>
</HostView>
<GuestView isHost={isGameMaster}>
<div className="flex flex-col gap-4">
<div className="text-xl">Fast Press Game</div>
<div className="text-4xl">Game Standby</div>
</div>
</GuestView>
</StandbyView>
<PlayingView isPlaying={isGameStarted && !isStandby && !winner}>
<button
onClick={answer}
className="bg-red-500 hover:bg-red-600 active:bg-red-700 text-white p-6 rounded-full h-36 w-36 shadow text-4xl"
>
Press
</button>
<HostView isHost={isGameMaster}>
<button onClick={reset} className="border-2 py-1 px-2 rounded">
Reset
</button>
</HostView>
</PlayingView>
<ResultView hasResult={winner}>
<div className="flex flex-col gap-2 p-4">
<div className="text-2xl font-bold">Fastest</div>
<div className="text-5xl font-bold whitespace-pre-wrap break-all select-text">
{winner && fastest.sort((a, b) => a.time - b.time)[0].name}
</div>
</div>
<HostView isHost={isGameMaster}>
<button onClick={reset} className="border-2 py-1 px-2 rounded">
Reset
</button>
</HostView>
</ResultView>
</InitView>
</div>
);
}

ReactDOM.render(<FastPressGameApp />, document.getElementById('root'));
247 changes: 247 additions & 0 deletions fast-press-game/public/js/hooks/useCustomObjectClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
const { useState, useRef, useCallback, useEffect } = React;

class Participant {
constructor(data = {}) {
this._id = data.id;
this.email = data.email;
this.name = data.name;
this.objectId = data.objectId;
this.objectType = data.objectType;
this.avatarUrl = data.avatarUrl;
this._workspaceId = data.workspaceId;
this.isHost = data.isHost;
this.isSelf = data.isSelf;
this.isVisitor = data.isVisitor;
this.language = data.language;
this.status = data.status; // joined, subscribed, etc.
}
get id() {
return this._id && this._id.toString();
}
get workspaceId() {
return this._workspaceId && this._workspaceId.toString();
}
}

class Message {
constructor(data = {}) {
this.source = data.source;
this.event = data.event;
this.objectId = data.objectId;
this.message = data.message;
this._to = data.to; // Optional, only used for direct messages
}
get to() {
return this._to && this._to.toString();
}
}

class MessageEvent {
constructor(data = {}) {
this.type = data.type;
this.payload = data.payload;
}
}

// Managing user information
function useUsers() {
const [user, setUser] = useState(new Participant());
const [users, setUsers] = useState([]);
return { user, users, setUser, setUsers };
}
// Event handling
function useEventHandlers() {
const eventFunction = useRef(() => {});
const userEventFunction = useRef(() => {});
const messageEventFunction = useRef(() => {});
const onEvent = useCallback(callback => {
eventFunction.current = callback;
}, []);
const onUserEvent = useCallback(callback => {
userEventFunction.current = callback;
}, []);
const onMessageEvent = useCallback(callback => {
messageEventFunction.current = callback;
}, []);
return {
onEvent,
onUserEvent,
onMessageEvent,
eventFunction,
userEventFunction,
messageEventFunction,
};
}
// Sending messages
function useMessageEmitter(user) {
const postMessage = useCallback(
message => {
const messageInstance = new MessageEvent(message);
window.parent.postMessage(messageInstance, '*');
},
[window]
);
const broadcast = useCallback(
(event, message) => {
postMessage({
type: 'ovice_broadcast_message',
payload: {
event: event,
message: message,
objectId: user.objectId && user.objectId.toString(),
source: user.id && user.id,
},
});
},
[user, postMessage]
);
const emitTo = useCallback(
(userId, event, message) => {
postMessage({
type: 'ovice_emit_message',
payload: {
event: event,
message: message,
objectId: user.objectId && user.objectId.toString(),
source: user.id && user.id,
to: userId && userId,
},
});
},
[user, postMessage]
);
return { postMessage, broadcast, emitTo };
}
// Calculating status
function useStatus(user, users) {
const isStaticObject = users.length
? users.every(user => !user.isHost)
: false;
const isDynamicObject = users.length
? users.some(user => user.isHost)
: false;
const isHost = user.isHost === true;
const isMaster = isHost || (isStaticObject ? user.id === users[0].id : false);
const isJoined =
users.length &&
user.id &&
users.some(v => user.id === v.id && v.status === 'joined');
return { isStaticObject, isDynamicObject, isHost, isMaster, isJoined };
}
// Initialization and updating the user list
function useInitialization(postMessage) {
const init = useCallback(() => {
postMessage({
type: 'ovice_get_participants',
});
}, [postMessage]);
const updateUsers = useCallback(() => {
postMessage({
type: 'ovice_get_participants',
});
}, [postMessage]);
return { init, updateUsers };
}
function useCustomObjectClient() {
const [lastMessage, setLastMessage] = useState(null);
// Managing user information
const { user, users, setUser, setUsers } = useUsers();
// Managing event handlers
const {
onEvent,
onUserEvent,
onMessageEvent,
eventFunction,
userEventFunction,
messageEventFunction,
} = useEventHandlers();
// Sending messages
const { postMessage, broadcast, emitTo } = useMessageEmitter(user);
// Calculating status
const { isStaticObject, isDynamicObject, isHost, isMaster, isJoined } =
useStatus(user, users);
// Initialization and updating the user list
const { init, updateUsers } = useInitialization(postMessage);
// Message handler
const handleMessage = useCallback(
event => {
const eventInstance = new MessageEvent(event.data);
eventFunction.current && eventFunction.current(eventInstance);
setLastMessage(event.data);
switch (eventInstance.type) {
case 'ovice_participants':
const participants = eventInstance.payload.map(
p => new Participant(p)
);
setUsers(participants);
setUser(
participants.find(participant => {
return participant.isSelf;
}) || new Participant()
);
break;
case 'ovice_participant_subscribed':
case 'ovice_participant_unsubscribed':
case 'ovice_participant_joined':
case 'ovice_participant_left':
const participant = new Participant(eventInstance.payload);
if (participant.isSelf) {
setUser(participant);
}
userEventFunction.current && userEventFunction.current(participant);
break;
case 'ovice_confirmation':
postMessage({ type: 'ovice_ready_confirmed' });
break;
case 'ovice_message':
const message = new Message(eventInstance.payload);
messageEventFunction.current && messageEventFunction.current(message);
break;
default:
break;
}
},
[setUsers, setUser, postMessage]
);
// Registering event listener and cleanup on initialization
useEffect(() => {
window.addEventListener('message', handleMessage);
init();
return () => {
window.removeEventListener('message', handleMessage);
};
}, [handleMessage, init]);
// Updating user information
useEffect(() => {
let timeoutId = null;
if (
lastMessage &&
(lastMessage.type.startsWith('ovice_participant_') ||
lastMessage.type.startsWith('ovice_other_participant_'))
) {
timeoutId = setTimeout(updateUsers, 1000);
}
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [lastMessage, updateUsers]);
// Returning the set of values provided by the custom hook
return {
user,
users,
isStaticObject,
isDynamicObject,
isHost,
isMaster,
isJoined,
updateUsers,
postMessage,
broadcast,
emitTo,
onEvent,
onUserEvent,
onMessageEvent,
};
}
Loading

0 comments on commit e1a99d5

Please sign in to comment.