Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions examples/vanilla-avatar-stack-2/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Use your own API KEY from Ably here:
VITE_ABLY_KEY=
2 changes: 2 additions & 0 deletions examples/vanilla-avatar-stack-2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.env
17 changes: 17 additions & 0 deletions examples/vanilla-avatar-stack-2/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Avatar Stack</title>
<link rel="stylesheet" href="src/styles.css" />
</head>
<body>
<div id="app">
<div id="avatar-stack" class="avatarStackContainer">
<div id="avatars" class="avatars"></div>
</div>
</div>
<script type="module" src="src/script.ts"></script>
</body>
</html>
27 changes: 27 additions & 0 deletions examples/vanilla-avatar-stack-2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "vanilla-avatar-stack-2",
"version": "1.0.0",
"description": "",
"main": "src/index.js",
"author": "",
"license": "ISC",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@ably/spaces": "^0.4.0",
"ably": "1.2.45",
"classnames": "^2.3.2",
"nanoid": "^5.0.1",
"random-words": "^2.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"dotenv": "16.4.5",
"typescript": "^5.2.2",
"vite": "^5.2.13"
}
}
155 changes: 155 additions & 0 deletions examples/vanilla-avatar-stack-2/src/script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import Spaces, { type SpaceMember } from "@ably/spaces";
import { Realtime } from 'ably';
import { nanoid } from 'nanoid';

export type Member = Omit<SpaceMember, "profileData"> & {
profileData: { memberColor: string; name: string };
};
const mockNames: string[] = ["Anum Reeve", "Tiernan Stubbs", "Hakim Hernandez"];
const mockColors: string[] = ["#9951F5", "#f1c232", "#f44336"];

connect()

async function connect() {
const client = new Realtime.Promise({
clientId: nanoid(),
key: import.meta.env.VITE_ABLY_KEY as string,
});

const spaces = new Spaces(client);
const space = await spaces.get('avatar-stack');

/** 💡 Add every avatar that enters 💡 */
space.members.subscribe('enter', (memberUpdate: SpaceMember) => {
const member: Member = {
...memberUpdate,
profileData: {
memberColor: (memberUpdate.profileData as any).memberColor,
name: (memberUpdate.profileData as any).name,
}
};
renderAvatar(member, true);
});

/** 💡 Enter the space as soon as it's available 💡 */
space.enter({
name: mockNames[Math.floor(Math.random() * mockNames.length)],
memberColor: mockColors[Math.floor(Math.random() * mockColors.length)],
}).then(async () => {
const otherMembers = await space.members.getOthers();

/** 💡 Get first four except the local member in the space 💡 */
otherMembers.slice(0, 4).forEach((member) => {
renderAvatar(member as Member);
});

/** 💡 Get a count of the number exceeding four and display as a single tally 💡 */
renderExceedingCounter(otherMembers)
}).catch((err) => {
console.error('Error joining space:', err);
});
}

function buildUserInfo(member: Member, isSelf: boolean = false): HTMLDivElement {
const wrapper = document.createElement('div');
wrapper.className = 'wrapper';

const userInfoContainer = document.createElement('div');
userInfoContainer.className = 'userInfoContainer';
userInfoContainer.style.backgroundColor = member.profileData.memberColor;
userInfoContainer.id = 'avatar';

const userInitials = member.profileData.name
.split(" ")
.map((word: string) => word.charAt(0))
.join("");

const initials = document.createElement('p');
initials.className = 'smallText';
initials.textContent = userInitials;

userInfoContainer.appendChild(initials);
wrapper.appendChild(userInfoContainer);

const userList = document.createElement('div');
userList.className = 'userList';
userList.id = 'user-list';

const nameElement = document.createElement('p');
nameElement.className = 'name';
nameElement.textContent = isSelf ? member.profileData.name + ' (You)' : member.profileData.name;

userList.appendChild(nameElement);
wrapper.appendChild(userList);

return wrapper;
}

async function renderAvatar(member: Member, isSelf: boolean = false): Promise<void> {
const userInitials = member.profileData.name
.split(" ")
.map((word: string) => word.charAt(0))
.join("");

const avatarsElement = document.getElementById('avatars');
if (avatarsElement) {
const avatarElement = document.createElement('div');
avatarElement.className = isSelf ? 'selfAvatar' : 'otherAvatar';

const avatarContainer = document.createElement('div');
avatarContainer.className = 'avatarContainer';

const avatar = document.createElement('div');
avatar.className = 'avatar';
avatar.style.backgroundColor = member.profileData.memberColor;
avatar.setAttribute('data-member-id', member['clientId']);
avatar.setAttribute('key', member['clientId']);

const initials = document.createElement('p');
initials.className = "textWhite";
initials.textContent = userInitials;

avatar.appendChild(initials);

const popup = document.createElement('div');
popup.className = 'popup';
popup.style.display = 'none';

const userInfo = buildUserInfo(member, isSelf);
avatarElement.appendChild(avatarContainer);
avatarContainer.appendChild(avatar);
popup.appendChild(userInfo);
avatar.appendChild(popup);

avatarsElement.appendChild(avatarElement);

avatar.addEventListener('mouseover', () => {
popup.style.display = 'block';
});

avatar.addEventListener('mouseleave', () => {
popup.style.display = 'none';
});
}
}

function renderExceedingCounter(otherMembers: SpaceMember[]) {
console.log('here');
console.log(otherMembers);
if (otherMembers.length > 4) {
const avatarsElement = document.getElementById('avatars');

if (avatarsElement) {
const avatarElement = document.createElement('div');
avatarElement.className = 'avatar';
avatarElement.style.backgroundColor = '#595959';

const nameElement = document.createElement('p');
nameElement.className = 'textWhite nameOthers';
nameElement.textContent = `+${otherMembers.length - 4}`;

avatarElement.appendChild(nameElement);
avatarsElement.appendChild(avatarElement);
}
}
}
84 changes: 84 additions & 0 deletions examples/vanilla-avatar-stack-2/src/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
.avatarStackContainer {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
background-color: #f4f8fb;
height: 100vh;
}

.avatars {
display: inline-flex;
}

.avatarContainer {
position: relative;
}

.avatar {
background-color: rgb(234, 88, 12);
height: 3rem;
width: 3rem;
border-radius: 9999px;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #e2e7ef;
flex-shrink: 0;
position: relative;
margin-left: -7px;
}

.name {
font-size: 0.75rem;
line-height: 1rem;
color: rgb(255, 255, 255);
}

.textWhite {
color: #fff;
}

.nameOthers {
z-index: 20;
font-size: 0.75rem;
}

.popup {
position: absolute;
left: -5rem;
top: -4.8rem;
padding: 1rem;
background-color: #000;
border-radius: 8px;
color: #fff;
min-width: 240px;
}

.wrapper {
display: flex;
justify-content: flex-start;
align-items: center;
}

.userList {
padding-left: 0.75rem;
width: 100%;
}

.userInfoContainer {
height: 2rem;
width: 2rem;
border-radius: 9999px;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
position: relative;
border: 2px solid #cbd5e0;
}

.smallText {
font-size: 0.75rem;
}
35 changes: 35 additions & 0 deletions examples/vanilla-avatar-stack-2/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"compilerOptions": {
"lib": [
"es2021",
"DOM",
"DOM.Iterable",
],
"module": "ESNext",
"target": "ESNext",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "Node",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowUnusedLabels": false,
"allowUnreachableCode": false,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"checkJs": true,
// Everything below are custom changes for this app
"allowJs": true,
"outDir": "dist"
},
"include": [
"./src/**/*",
"./__tests__/**/*",
"vite-env.d.ts"
]
}
8 changes: 8 additions & 0 deletions examples/vanilla-avatar-stack-2/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}
8 changes: 8 additions & 0 deletions examples/vanilla-avatar-stack-2/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface ImportMetaEnv {
readonly VITE_ABLY_KEY: string;
// Add other environment variables here if needed
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
10 changes: 10 additions & 0 deletions examples/vanilla-avatar-stack-2/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [react()],
publicDir: 'public',
build: {
outDir: 'dist',
},
});
Loading