Skip to content

Commit

Permalink
feat: put a button to add cursor and a 15 users limitation
Browse files Browse the repository at this point in the history
  • Loading branch information
jarrisondev committed Jul 31, 2024
1 parent fa72b96 commit b41187d
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 57 deletions.
34 changes: 33 additions & 1 deletion app/components/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
/* eslint-disable @next/next/no-img-element */
import { useCursors } from "app/cursors-context";
import { Button } from "./ui/button";
import Logo from "app/components/icons/Logo";
import { cn } from "./utils";

export const Nav = () => {
const { cursors, disabled, setDisabled } = useCursors();

const slice = 3;
const cursorsSlice = cursors.slice(-slice);

const imgCircleClass = cn('relative rounded-full h-8 w-8 ring-2 ring-white overflow-hidden group-hover:ring-[3px]')

return (
<nav className="relative px-2 w-full max-w-6xl h-16 mx-auto flex items-center justify-between">
<a href="/" className="flex items-center gap-1 cursor-pointer">
<Logo />
<h1 className="text-2xl text-white font-semibold">Aforshow</h1>
</a>
<Button>Inscribirse</Button>
<div className="flex items-center gap-5 h-full">
<div
className={cn('group flex -space-x-3 hover:cursor-pointer overflow-hidden p-2', {"opacity-70": disabled})}
onClick={() => {
if (setDisabled) setDisabled((prev) => !prev);
}}>
{
cursorsSlice.map((cursor) => (
<div key={cursor.id} className={imgCircleClass}>
<img className="scale-[1.7] absolute top-0 left-0" src={cursor.flagUrl} alt="" />
</div>
))
}
{
cursors.length > slice && (
<div className={cn(imgCircleClass, 'flex items-center justify-center bg-[#121112]')}>
<span className="text-white font-semibold">+{cursors.length - slice}</span>
</div>
)
}
</div>
<Button>Inscribirse</Button>
</div>
</nav>
);
};
50 changes: 40 additions & 10 deletions app/cursors-context.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,56 @@
"use client";

import { useState, useEffect, useContext, createContext, useRef } from "react";
import { useState, useEffect, useContext, createContext, useRef, Dispatch, SetStateAction } from "react";
import usePartySocket from "partysocket/react";
import twemoji from "twemoji";

type Position = {
x: number;
y: number;
pointer: "mouse";
};

type Cursor = Position & {
export type Cursor = Position & {
id: string;
country: string | null;
flag: string;
flagUrl: string;
lastUpdate: number;
};

type OtherCursorsMap = {
[id: string]: Cursor;
};

interface CursorsContextType {
others: OtherCursorsMap;
self: Position | null;
cursors: Array<Cursor>
disabled: boolean | null
setDisabled: Dispatch<SetStateAction<boolean>> | null
}

export const CursorsContext = createContext<CursorsContextType>({
others: {},
self: null,
cursors: [],
disabled: null,
setDisabled: null,
});

export function useCursors() {
return useContext(CursorsContext);
}

function getFlagEmoji(countryCode: string) {
const codePoints = countryCode
.toUpperCase()
.split("")
.map((char) => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}

export default function CursorsContextProvider(props: {
host: string;
room: string;
children: React.ReactNode;
}) {
const [self, setSelf] = useState<Position | null>(null);
const [disabled, setDisabled] = useState<boolean>(false);
const [dimensions, setDimensions] = useState<{
width: number;
height: number;
Expand All @@ -49,6 +62,24 @@ export default function CursorsContextProvider(props: {
});
const [others, setOthers] = useState<OtherCursorsMap>({});

const cursors: Cursor[] = Object.entries(others).map(([id, cursor]): Cursor => {
const flag = cursor.country ? getFlagEmoji(cursor.country) : "";

const flagAsImage = twemoji.parse(flag,
{
base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/'
})

const flagUrl = flagAsImage.match(/src="([^"]+)"/)?.[1] || "";

return {
...cursor,
id,
flag: flagAsImage,
flagUrl: flagUrl,
}
});

useEffect(() => {
if (socket) {
const onMessage = (evt: WebSocketEventMap["message"]) => {
Expand Down Expand Up @@ -109,7 +140,6 @@ export default function CursorsContextProvider(props: {
pointer: "mouse",
} as Position;
socket.send(JSON.stringify(position));
setSelf(position);
};
window.addEventListener("mousemove", onMouseMove);

Expand All @@ -119,7 +149,7 @@ export default function CursorsContextProvider(props: {
}, [socket, dimensions]);

return (
<CursorsContext.Provider value={{ others: others, self: self }}>
<CursorsContext.Provider value={{ cursors, disabled, setDisabled }}>
{props.children}
</CursorsContext.Provider>
);
Expand Down
28 changes: 8 additions & 20 deletions app/cursors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useCursors } from "./cursors-context";
import OtherCursor from "./other-cursor";

export default function Cursors() {
const { others, self } = useCursors();
const { cursors, disabled } = useCursors();
const [windowDimensions, setWindowDimensions] = useState({
width: 0,
height: 0,
Expand All @@ -26,28 +26,16 @@ export default function Cursors() {
}, []);


// get the last 30 cursors
const cursors = Object.keys(others).slice(-30);
const count = Object.keys(others).length + (self ? 1 : 0);

const cursorsSliced = disabled ? [] : cursors.slice(-15);

return (
<div className="absolute h-full w-full pointer-events-none overflow-hidden">
<div className="hidden z-10 sm:absolute pointer-events-none top-0 left-0 w-full h-full overflow-clip">
{count > 0 && (
<div className="absolute z-10 top-4 left-4 pointer-events-none flex items-center">
<span className="text-2xl">{count}&times;</span>
<span className="text-5xl">🐁</span>
</div>
)}
</div>

{cursors.map((id) => (
<OtherCursor
id={id}
key={id}
windowDimensions={windowDimensions}
/>
{cursorsSliced.map((cursor) => (
<OtherCursor
key={cursor.id}
cursor={cursor}
windowDimensions={windowDimensions}
/>
))}
</div>
);
Expand Down
30 changes: 4 additions & 26 deletions app/other-cursor.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,18 @@
import twemoji from "twemoji";
import { useCursors } from "./cursors-context";
import { Cursor } from "./cursors-context";

// NOTE
// The pointer SVG is from https://github.com/daviddarnes/mac-cursors
// The license is the Apple User Agreement

function getFlagEmoji(countryCode: string) {
const codePoints = countryCode
.toUpperCase()
.split("")
.map((char) => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}

export default function OtherCursor(props: {
id: string;
cursor: Cursor
windowDimensions: { width: number; height: number };
}) {
const { id, windowDimensions } = props;
const { others } = useCursors();
const cursor = others[id];
if (!cursor) {
return null;
}
const { cursor, windowDimensions } = props;

const left = cursor.x * windowDimensions.width;
const top = cursor.y * windowDimensions.height;

const flag = cursor.country ? getFlagEmoji(cursor.country) : "";

const flagAsImage = twemoji.parse(flag,
{
base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/',
className:'twemoji',
})

return (
<div className="absolute flex flex-col z-10" style={{ left: left, top: top }}>
<svg
Expand All @@ -50,7 +28,7 @@ export default function OtherCursor(props: {
</svg>
<span
className="w-5 -mt-6 ml-2"
dangerouslySetInnerHTML={{ __html: flagAsImage }}
dangerouslySetInnerHTML={{ __html: cursor.flag }}
>
</span>
</div>
Expand Down

0 comments on commit b41187d

Please sign in to comment.