From a51415cf0de44985e6e808edc5639c1330185cda Mon Sep 17 00:00:00 2001 From: Jonatan Holmgren Date: Tue, 8 Apr 2025 21:40:24 +0200 Subject: [PATCH 1/4] Code background based on user_id --- web/src/components/auth/Avatar.tsx | 22 ++----------- .../components/party/codes/PartyProgress.tsx | 31 +++++++++++++++++-- web/src/styles/index.css | 22 +++++++++++++ web/src/util/user.ts | 25 +++++++++++++++ 4 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 web/src/util/user.ts diff --git a/web/src/components/auth/Avatar.tsx b/web/src/components/auth/Avatar.tsx index a935db5..4d2b9de 100644 --- a/web/src/components/auth/Avatar.tsx +++ b/web/src/components/auth/Avatar.tsx @@ -2,28 +2,12 @@ import * as RadixAvatar from '@radix-ui/react-avatar'; import { FC, useMemo } from 'react'; import { FaCat } from 'react-icons/fa'; +import { backgroundColorBySeed } from '@/util/user'; + export const Avatar: FC<{ src?: string; seed?: string }> = ({ src, seed }) => { // Generate a deterministic color based on the seed string const backgroundColor = useMemo(() => { - if (!seed) return '#e2e8f0'; // Default color if no seed - - // Simple hash function to generate a color from the seed - let hash = 0; - - for (let i = 0; i < seed.length; i++) { - hash = seed.charCodeAt(i) + ((hash << 5) - hash); - } - - // Convert to hex color - let color = '#'; - - for (let i = 0; i < 3; i++) { - const value = (hash >> (i * 8)) & 0xff; - - color += ('00' + value.toString(16)).slice(-2); - } - - return color; + return backgroundColorBySeed(seed); }, [seed]); return ( diff --git a/web/src/components/party/codes/PartyProgress.tsx b/web/src/components/party/codes/PartyProgress.tsx index 058c598..1990a93 100644 --- a/web/src/components/party/codes/PartyProgress.tsx +++ b/web/src/components/party/codes/PartyProgress.tsx @@ -5,10 +5,11 @@ import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { PartyEvent } from '@/api/party/events'; import { usePartyCodes, usePartyProgress } from '@/api/progress'; import { Tooltip } from '@/components/helpers/Tooltip'; +import { backgroundColorBySeed } from '@/util/user'; export const PartyProgress: FC<{ party_id: string }> = ({ party_id }) => { const { data: orderedCodes } = usePartyCodes(party_id); - const { triedCodes, percentages } = usePartyProgress(party_id); + const { triedCodes } = usePartyProgress(party_id); const codes = orderedCodes; @@ -181,11 +182,35 @@ export const PartyProgressList: FC<{ className={cx( 'flex justify-center items-center w-full h-full rounded-sm text-[0.8rem]', triedCodes.has(cell.code) - ? 'bg-accent text-primary' + ? 'bg-tertiary crossed-out text-tertiary' : 'bg-tertiary text-secondary' )} + style={ + triedCodes.has(cell.code) + ? { + backgroundColor: backgroundColorBySeed( + (triedCodes.get(cell.code) || [])[0] + ?.user_id + ), + } + : {} + } > - {cell.code} +
+ {cell.code} +
) diff --git a/web/src/styles/index.css b/web/src/styles/index.css index cc76e83..13e031b 100644 --- a/web/src/styles/index.css +++ b/web/src/styles/index.css @@ -69,6 +69,28 @@ font-size-adjust: 0.6; } +.crossed-out { + background-image: + linear-gradient( + to bottom right, + transparent 0%, + transparent 48%, + #2d2b29 48%, + #2d2b29 52%, + transparent 52%, + transparent 100% + ), + linear-gradient( + to bottom left, + transparent 0%, + transparent 48%, + #2d2b29 48%, + #2d2b29 52%, + transparent 52%, + transparent 100% + ); +} + html, body { @apply bg-tertiary text-primary; diff --git a/web/src/util/user.ts b/web/src/util/user.ts new file mode 100644 index 0000000..6d13372 --- /dev/null +++ b/web/src/util/user.ts @@ -0,0 +1,25 @@ +export const backgroundColorBySeed = ( + seed?: string, + { + saturation = 75, + lightness = 60, + }: { + saturation?: number; + lightness?: number; + } = { saturation: 75, lightness: 60 } +) => { + const hash = (str: string) => { + let hash = 0; + + for (let i = 0; i < str.length; i++) { + hash = (hash << 5) - hash + str.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return Math.abs(hash % 360); + }; + + const hue = seed ? hash(seed) : 0; + + return `hsl(${hue}, ${saturation}%, ${lightness}%)`; +}; From eb5a3bc7f84ff0933758a87909770659813665f1 Mon Sep 17 00:00:00 2001 From: Jonatan Holmgren Date: Wed, 9 Apr 2025 21:19:17 +0200 Subject: [PATCH 2/4] Introduce logic for showing visibility --- web/src/api/schema.gen.ts | 4 +- .../components/party/codes/PartyProgress.tsx | 80 ++++++++----------- .../components/party/codes/ProgressCell.tsx | 57 +++++++++++++ 3 files changed, 94 insertions(+), 47 deletions(-) create mode 100644 web/src/components/party/codes/ProgressCell.tsx diff --git a/web/src/api/schema.gen.ts b/web/src/api/schema.gen.ts index 0d2f2ff..d1fd8bb 100644 --- a/web/src/api/schema.gen.ts +++ b/web/src/api/schema.gen.ts @@ -818,8 +818,8 @@ export interface components { * "name": "John D.", * "avatar_url": "https://avatars.akamai.steamstatic.com/0000000000000000.jpg", * "profile_url": "https://steamcommunity.com/id/john_doe", - * "created_at": "2025-04-09T01:04:16.289611048+00:00", - * "updated_at": "2025-04-09T01:04:16.289614158+00:00" + * "created_at": "2025-04-09T14:24:33.977349865+00:00", + * "updated_at": "2025-04-09T14:24:33.977352725+00:00" * } */ User: { diff --git a/web/src/components/party/codes/PartyProgress.tsx b/web/src/components/party/codes/PartyProgress.tsx index 1990a93..153995c 100644 --- a/web/src/components/party/codes/PartyProgress.tsx +++ b/web/src/components/party/codes/PartyProgress.tsx @@ -1,33 +1,38 @@ import { useVirtualizer } from '@tanstack/react-virtual'; -import cx from 'classnames'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { PartyEvent } from '@/api/party/events'; +import { PartyEvent, usePartyEvents } from '@/api/party/events'; import { usePartyCodes, usePartyProgress } from '@/api/progress'; import { Tooltip } from '@/components/helpers/Tooltip'; -import { backgroundColorBySeed } from '@/util/user'; + +import { ProgressCell } from './ProgressCell'; export const PartyProgress: FC<{ party_id: string }> = ({ party_id }) => { const { data: orderedCodes } = usePartyCodes(party_id); - const { triedCodes } = usePartyProgress(party_id); + const { triedCodes, percentages } = usePartyProgress(party_id); const codes = orderedCodes; return ( <> - + ); }; export const PartyProgressList: FC<{ - party_id: string, - orderedCodes: string[], - triedCodes: Map, - percentages: Map, - codes: string[] + party_id: string; + orderedCodes: string[]; + triedCodes: Map; + percentages: Map; + codes: string[]; }> = ({ party_id, orderedCodes, triedCodes, percentages, codes }) => { - const parentRef = useRef(null); // Constants for item sizing @@ -106,6 +111,20 @@ export const PartyProgressList: FC<{ [columnCount] ); + const visibleCodes: Map = new Map(); + + usePartyEvents(party_id, (event) => { + return event.data.type === 'PartyCursorUpdate'; + }).events.forEach((event) => { + if (event.data.type === 'PartyCursorUpdate') { + const cursor = parseInt(event.data.cursor, 10); + const { size } = event.data; + const codes = orderedCodes.slice(cursor, cursor + size); + + visibleCodes.set(event.user_id, codes); + } + }); + // Memoize virtual cells to prevent unnecessary re-renders const virtualCells = useMemo(() => { return rowVirtualizer.getVirtualItems().flatMap( @@ -178,40 +197,11 @@ export const PartyProgressList: FC<{ height: `${ITEM_HEIGHT}px`, }} > -
-
- {cell.code} -
-
+ ) )} diff --git a/web/src/components/party/codes/ProgressCell.tsx b/web/src/components/party/codes/ProgressCell.tsx new file mode 100644 index 0000000..e48b440 --- /dev/null +++ b/web/src/components/party/codes/ProgressCell.tsx @@ -0,0 +1,57 @@ +import cx from 'classnames'; +import { FC } from 'react'; + +import { usePartyCursor } from '@/api/party/cursor'; +import { PartyEvent, usePartyEvents } from '@/api/party/events'; +import { backgroundColorBySeed } from '@/util/user'; + +export const ProgressCell: FC<{ + code: string; + triedCodes: Map; + visibleCodes: Map; +}> = ({ code, triedCodes, visibleCodes }) => { + // Visible codes key is user_id, value is an array of codes. Find first user_id that has this code + const userId = Array.from(visibleCodes.entries()).find(([_, codes]) => + codes.includes(code) + )?.[0]; + + return ( +
+
+ {code} +
+
+ ); +}; From e6b9595164f782b8087272f145f4c9d5fe1e2f08 Mon Sep 17 00:00:00 2001 From: Jonatan Holmgren Date: Wed, 9 Apr 2025 21:53:57 +0200 Subject: [PATCH 3/4] Improve UI --- .../components/party/codes/ProgressCell.tsx | 19 ++++++++++------ web/src/styles/index.css | 22 ------------------- 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/web/src/components/party/codes/ProgressCell.tsx b/web/src/components/party/codes/ProgressCell.tsx index e48b440..2014140 100644 --- a/web/src/components/party/codes/ProgressCell.tsx +++ b/web/src/components/party/codes/ProgressCell.tsx @@ -1,8 +1,7 @@ import cx from 'classnames'; import { FC } from 'react'; -import { usePartyCursor } from '@/api/party/cursor'; -import { PartyEvent, usePartyEvents } from '@/api/party/events'; +import { PartyEvent } from '@/api/party/events'; import { backgroundColorBySeed } from '@/util/user'; export const ProgressCell: FC<{ @@ -19,15 +18,17 @@ export const ProgressCell: FC<{
Date: Wed, 9 Apr 2025 22:26:25 +0200 Subject: [PATCH 4/4] Clean up code --- .../components/party/codes/ProgressCell.tsx | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/web/src/components/party/codes/ProgressCell.tsx b/web/src/components/party/codes/ProgressCell.tsx index 2014140..3fc4c7e 100644 --- a/web/src/components/party/codes/ProgressCell.tsx +++ b/web/src/components/party/codes/ProgressCell.tsx @@ -17,7 +17,7 @@ export const ProgressCell: FC<{ return (
-
- {code} -
+ {code}
); };