mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 15:19:08 +03:00
good state
This commit is contained in:
1 parent
33effe567e
commit
bcf986f426
3 files changed
+265
-252
No files matched your search
@@ -16,13 +16,27 @@ import useConfData from '~/components/LaunchWeek/hooks/use-conf-data'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useRouter } from 'next/router'
|
||||
import { getRandomColor, getRandomColors, getRandomUniqueColor } from './Multiplayer/randomColor'
|
||||
import {
|
||||
getColor,
|
||||
// getRandomColor,
|
||||
// getRandomColors,
|
||||
// getRandomUniqueColor,
|
||||
} from './Multiplayer/randomColor'
|
||||
import { Coordinates, Message, Payload, User } from './Multiplayer/types'
|
||||
import { cloneDeep, throttle } from 'lodash'
|
||||
import { Badge } from 'ui'
|
||||
import Cursor from './Multiplayer/Cursor'
|
||||
import Chatbox from './Multiplayer/Chatbox'
|
||||
|
||||
/**
|
||||
* [x] multiplayer cursors
|
||||
* [x] show own trail
|
||||
* [ ] share color between cursor and trails
|
||||
* [ ] show own cursor
|
||||
* [ ] abstract reusable setHoverTrail function
|
||||
* [ ] show other users cursors
|
||||
*/
|
||||
|
||||
const GRID_SIZE = 100
|
||||
const CELL_SIZE = 40
|
||||
const CANVAS_WIDTH = 1800
|
||||
@@ -35,7 +49,7 @@ const MAX_ROOM_USERS = 50
|
||||
const MAX_DISPLAY_MESSAGES = 50
|
||||
const MAX_EVENTS_PER_SECOND = 10
|
||||
const X_THRESHOLD = 25
|
||||
const Y_THRESHOLD = 35
|
||||
const Y_THRESHOLD = 65
|
||||
|
||||
interface CellState {
|
||||
isHovered: boolean
|
||||
@@ -79,7 +93,7 @@ export default function InteractiveGrid() {
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const localColorBackup = getRandomColor()
|
||||
const localColorBackup = getColor('gray')
|
||||
|
||||
const chatboxRef = useRef<any>()
|
||||
// [Joshen] Super hacky fix for a really weird bug for onKeyDown
|
||||
@@ -111,15 +125,15 @@ export default function InteractiveGrid() {
|
||||
const [roomId, setRoomId] = useState<undefined | string>(undefined)
|
||||
const [users, setUsers] = useState<{ [key: string]: User }>({})
|
||||
|
||||
const setIsTyping = (value: boolean) => {
|
||||
isTypingRef.current = value
|
||||
_setIsTyping(value)
|
||||
}
|
||||
// const setIsTyping = (value: boolean) => {
|
||||
// isTypingRef.current = value
|
||||
// _setIsTyping(value)
|
||||
// }
|
||||
|
||||
const setIsCancelled = (value: boolean) => {
|
||||
isCancelledRef.current = value
|
||||
_setIsCancelled(value)
|
||||
}
|
||||
// const setIsCancelled = (value: boolean) => {
|
||||
// isCancelledRef.current = value
|
||||
// _setIsCancelled(value)
|
||||
// }
|
||||
|
||||
const setMessage = (value: string) => {
|
||||
messageRef.current = value
|
||||
@@ -131,10 +145,10 @@ export default function InteractiveGrid() {
|
||||
_setMousePosition(coordinates)
|
||||
}
|
||||
|
||||
const setMessagesInTransit = (messages: string[]) => {
|
||||
messagesInTransitRef.current = messages
|
||||
_setMessagesInTransit(messages)
|
||||
}
|
||||
// const setMessagesInTransit = (messages: string[]) => {
|
||||
// messagesInTransitRef.current = messages
|
||||
// _setMessagesInTransit(messages)
|
||||
// }
|
||||
|
||||
const mapInitialUsers = (userChannel: RealtimeChannel, roomId: string) => {
|
||||
const state = userChannel.presenceState()
|
||||
@@ -143,21 +157,21 @@ export default function InteractiveGrid() {
|
||||
if (!_users) return
|
||||
|
||||
// Deconflict duplicate colours at the beginning of the browser session
|
||||
const colors = Object.keys(usersRef.current).length === 0 ? getRandomColors(_users.length) : []
|
||||
// const colors = Object.keys(usersRef.current).length === 0 ? getRandomColors(_users.length) : []
|
||||
|
||||
if (_users) {
|
||||
setUsers((existingUsers) => {
|
||||
const updatedUsers = _users.reduce(
|
||||
(acc: { [key: string]: User }, { user_id: userId }: any, index: number) => {
|
||||
const userColors = Object.values(usersRef.current).map((user: any) => user.color)
|
||||
// const userColors = Object.values(usersRef.current).map((user: any) => user.color)
|
||||
// Deconflict duplicate colors for incoming clients during the browser session
|
||||
const color = colors.length > 0 ? colors[index] : getRandomUniqueColor(userColors)
|
||||
// const color = colors.length > 0 ? colors[index] : getRandomUniqueColor(userColors)
|
||||
|
||||
acc[userId] = existingUsers[userId] || {
|
||||
x: 0,
|
||||
y: 0,
|
||||
color: color.bg,
|
||||
hue: color.hue,
|
||||
// color: color.bg,
|
||||
// hue: color.hue,
|
||||
}
|
||||
return acc
|
||||
},
|
||||
@@ -272,101 +286,102 @@ export default function InteractiveGrid() {
|
||||
useEffect(() => {
|
||||
if (!roomId || !isInitialStateSynced) return
|
||||
|
||||
let pingIntervalId: ReturnType<typeof setInterval> | undefined
|
||||
let messageChannel: RealtimeChannel, pingChannel: RealtimeChannel
|
||||
let setMouseEvent: (e: MouseEvent) => void = () => {},
|
||||
onKeyDown: (e: KeyboardEvent) => void = () => {}
|
||||
// let pingIntervalId: ReturnType<typeof setInterval> | undefined
|
||||
let messageChannel: RealtimeChannel
|
||||
// , pingChannel: RealtimeChannel
|
||||
let setMouseEvent: (e: MouseEvent) => void = () => {}
|
||||
// onKeyDown: (e: KeyboardEvent) => void = () => {}
|
||||
|
||||
// Ping channel is used to calculate roundtrip time from client to server to client
|
||||
pingChannel = supabase?.channel(`ping:${userId}`, {
|
||||
config: { broadcast: { ack: true } },
|
||||
})!
|
||||
pingChannel.subscribe((status: `${REALTIME_SUBSCRIBE_STATES}`) => {
|
||||
if (status === REALTIME_SUBSCRIBE_STATES.SUBSCRIBED) {
|
||||
pingIntervalId = setInterval(async () => {
|
||||
const start = performance.now()
|
||||
const resp = await pingChannel.send({
|
||||
type: 'broadcast',
|
||||
event: 'PING',
|
||||
payload: {},
|
||||
})
|
||||
// pingChannel = supabase?.channel(`ping:${userId}`, {
|
||||
// config: { broadcast: { ack: true } },
|
||||
// })!
|
||||
// pingChannel.subscribe((status: `${REALTIME_SUBSCRIBE_STATES}`) => {
|
||||
// if (status === REALTIME_SUBSCRIBE_STATES.SUBSCRIBED) {
|
||||
// pingIntervalId = setInterval(async () => {
|
||||
// const start = performance.now()
|
||||
// const resp = await pingChannel.send({
|
||||
// type: 'broadcast',
|
||||
// event: 'PING',
|
||||
// payload: {},
|
||||
// })
|
||||
|
||||
if (resp !== 'ok') {
|
||||
console.log('pingChannel broadcast error')
|
||||
setLatency(-1)
|
||||
} else {
|
||||
const end = performance.now()
|
||||
const newLatency = end - start
|
||||
// if (resp !== 'ok') {
|
||||
// console.log('pingChannel broadcast error')
|
||||
// setLatency(-1)
|
||||
// } else {
|
||||
// const end = performance.now()
|
||||
// const newLatency = end - start
|
||||
|
||||
// if (newLatency >= LATENCY_THRESHOLD) {
|
||||
// sendLog(
|
||||
// `Roundtrip Latency for User ${userId} surpassed ${LATENCY_THRESHOLD} ms at ${newLatency.toFixed(
|
||||
// 1
|
||||
// )} ms`
|
||||
// )
|
||||
// }
|
||||
// // if (newLatency >= LATENCY_THRESHOLD) {
|
||||
// // sendLog(
|
||||
// // `Roundtrip Latency for User ${userId} surpassed ${LATENCY_THRESHOLD} ms at ${newLatency.toFixed(
|
||||
// // 1
|
||||
// // )} ms`
|
||||
// // )
|
||||
// // }
|
||||
|
||||
setLatency(newLatency)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
// setLatency(newLatency)
|
||||
// }
|
||||
// }, 1000)
|
||||
// }
|
||||
// })
|
||||
|
||||
messageChannel = supabase?.channel(`chat_messages:${roomId}`)!
|
||||
|
||||
// Listen for messages inserted into the database
|
||||
messageChannel.on(
|
||||
REALTIME_LISTEN_TYPES.POSTGRES_CHANGES,
|
||||
{
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT,
|
||||
schema: 'public',
|
||||
table: 'messages',
|
||||
filter: `room_id=eq.${roomId}`,
|
||||
},
|
||||
(
|
||||
payload: RealtimePostgresInsertPayload<{
|
||||
id: number
|
||||
created_at: string
|
||||
message: string
|
||||
user_id: string
|
||||
room_id: string
|
||||
}>
|
||||
) => {
|
||||
// if (payload.new.user_id === userId && insertMsgTimestampRef.current) {
|
||||
// sendLog(
|
||||
// `Message Latency for User ${userId} from insert to receive was ${(
|
||||
// performance.now() - insertMsgTimestampRef.current
|
||||
// ).toFixed(1)} ms`
|
||||
// )
|
||||
// insertMsgTimestampRef.current = undefined
|
||||
// }
|
||||
// // Listen for messages inserted into the database
|
||||
// messageChannel.on(
|
||||
// REALTIME_LISTEN_TYPES.POSTGRES_CHANGES,
|
||||
// {
|
||||
// event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT,
|
||||
// schema: 'public',
|
||||
// table: 'messages',
|
||||
// filter: `room_id=eq.${roomId}`,
|
||||
// },
|
||||
// (
|
||||
// payload: RealtimePostgresInsertPayload<{
|
||||
// id: number
|
||||
// created_at: string
|
||||
// message: string
|
||||
// user_id: string
|
||||
// room_id: string
|
||||
// }>
|
||||
// ) => {
|
||||
// // if (payload.new.user_id === userId && insertMsgTimestampRef.current) {
|
||||
// // sendLog(
|
||||
// // `Message Latency for User ${userId} from insert to receive was ${(
|
||||
// // performance.now() - insertMsgTimestampRef.current
|
||||
// // ).toFixed(1)} ms`
|
||||
// // )
|
||||
// // insertMsgTimestampRef.current = undefined
|
||||
// // }
|
||||
|
||||
setMessages((prevMsgs: Message[]) => {
|
||||
const messages = prevMsgs.slice(-MAX_DISPLAY_MESSAGES + 1)
|
||||
const msg = (({ id, message, room_id, user_id }) => ({
|
||||
id,
|
||||
message,
|
||||
room_id,
|
||||
user_id,
|
||||
}))(payload.new)
|
||||
messages.push(msg)
|
||||
// setMessages((prevMsgs: Message[]) => {
|
||||
// const messages = prevMsgs.slice(-MAX_DISPLAY_MESSAGES + 1)
|
||||
// const msg = (({ id, message, room_id, user_id }) => ({
|
||||
// id,
|
||||
// message,
|
||||
// room_id,
|
||||
// user_id,
|
||||
// }))(payload.new)
|
||||
// messages.push(msg)
|
||||
|
||||
if (msg.user_id === userId) {
|
||||
const updatedMessagesInTransit = removeFirst(
|
||||
messagesInTransitRef?.current ?? [],
|
||||
msg.message
|
||||
)
|
||||
setMessagesInTransit(updatedMessagesInTransit)
|
||||
}
|
||||
// if (msg.user_id === userId) {
|
||||
// const updatedMessagesInTransit = removeFirst(
|
||||
// messagesInTransitRef?.current ?? [],
|
||||
// msg.message
|
||||
// )
|
||||
// setMessagesInTransit(updatedMessagesInTransit)
|
||||
// }
|
||||
|
||||
return messages
|
||||
})
|
||||
// return messages
|
||||
// })
|
||||
|
||||
if (chatboxRef.current) {
|
||||
chatboxRef.current.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
)
|
||||
// if (chatboxRef.current) {
|
||||
// chatboxRef.current.scrollIntoView({ behavior: 'smooth' })
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
|
||||
// Listen for cursor positions from other users in the room
|
||||
messageChannel.on(
|
||||
@@ -385,7 +400,7 @@ export default function InteractiveGrid() {
|
||||
const y =
|
||||
(payload?.payload?.y ?? 0 - Y_THRESHOLD) > window.innerHeight
|
||||
? window.innerHeight - Y_THRESHOLD
|
||||
: payload?.payload?.y
|
||||
: payload?.payload?.y! - Y_THRESHOLD
|
||||
|
||||
users[userId] = { ...existingUser, ...{ x, y } }
|
||||
users = cloneDeep(users)
|
||||
@@ -397,26 +412,26 @@ export default function InteractiveGrid() {
|
||||
)
|
||||
|
||||
// Listen for messages sent by other users directly via Broadcast
|
||||
messageChannel.on(
|
||||
REALTIME_LISTEN_TYPES.BROADCAST,
|
||||
{ event: 'MESSAGE' },
|
||||
(payload: Payload<{ user_id: string; isTyping: boolean; message: string }>) => {
|
||||
setUsers((users) => {
|
||||
const userId = payload!.payload!.user_id
|
||||
const existingUser = users[userId]
|
||||
// messageChannel.on(
|
||||
// REALTIME_LISTEN_TYPES.BROADCAST,
|
||||
// { event: 'MESSAGE' },
|
||||
// (payload: Payload<{ user_id: string; isTyping: boolean; message: string }>) => {
|
||||
// setUsers((users) => {
|
||||
// const userId = payload!.payload!.user_id
|
||||
// const existingUser = users[userId]
|
||||
|
||||
if (existingUser) {
|
||||
users[userId] = {
|
||||
...existingUser,
|
||||
...{ isTyping: payload?.payload?.isTyping, message: payload?.payload?.message },
|
||||
}
|
||||
users = cloneDeep(users)
|
||||
}
|
||||
// if (existingUser) {
|
||||
// users[userId] = {
|
||||
// ...existingUser,
|
||||
// ...{ isTyping: payload?.payload?.isTyping, message: payload?.payload?.message },
|
||||
// }
|
||||
// users = cloneDeep(users)
|
||||
// }
|
||||
|
||||
return users
|
||||
})
|
||||
}
|
||||
)
|
||||
// return users
|
||||
// })
|
||||
// }
|
||||
// )
|
||||
messageChannel.subscribe((status: `${REALTIME_SUBSCRIBE_STATES}`) => {
|
||||
if (status === REALTIME_SUBSCRIBE_STATES.SUBSCRIBED) {
|
||||
// Lodash throttle will be removed once realtime-js client throttles on the channel level
|
||||
@@ -436,108 +451,108 @@ export default function InteractiveGrid() {
|
||||
setMousePosition({ x, y })
|
||||
}
|
||||
|
||||
onKeyDown = async (e: KeyboardEvent) => {
|
||||
if (document.activeElement?.id === 'email') return
|
||||
// onKeyDown = async (e: KeyboardEvent) => {
|
||||
// if (document.activeElement?.id === 'email') return
|
||||
|
||||
// Start typing session
|
||||
if (e.code === 'Enter' || (e.key.length === 1 && !e.metaKey)) {
|
||||
if (!isTypingRef.current) {
|
||||
setIsTyping(true)
|
||||
setIsCancelled(false)
|
||||
// // Start typing session
|
||||
// if (e.code === 'Enter' || (e.key.length === 1 && !e.metaKey)) {
|
||||
// if (!isTypingRef.current) {
|
||||
// setIsTyping(true)
|
||||
// setIsCancelled(false)
|
||||
|
||||
if (chatInputFix.current) {
|
||||
setMessage('')
|
||||
chatInputFix.current = false
|
||||
} else {
|
||||
setMessage(e.key.length === 1 ? e.key : '')
|
||||
}
|
||||
messageChannel
|
||||
.send({
|
||||
type: 'broadcast',
|
||||
event: 'MESSAGE',
|
||||
payload: { user_id: userId, isTyping: true, message: '' },
|
||||
})
|
||||
.catch(() => {})
|
||||
} else if (e.code === 'Enter') {
|
||||
// End typing session and send message
|
||||
setIsTyping(false)
|
||||
messageChannel
|
||||
.send({
|
||||
type: 'broadcast',
|
||||
event: 'MESSAGE',
|
||||
payload: { user_id: userId, isTyping: false, message: messageRef.current },
|
||||
})
|
||||
.catch(() => {})
|
||||
if (messageRef.current) {
|
||||
const updatedMessagesInTransit = (messagesInTransitRef?.current ?? []).concat([
|
||||
messageRef.current,
|
||||
])
|
||||
setMessagesInTransit(updatedMessagesInTransit)
|
||||
if (chatboxRef.current) chatboxRef.current.scrollIntoView({ behavior: 'smooth' })
|
||||
insertMsgTimestampRef.current = performance.now()
|
||||
await supabase?.from('messages').insert([
|
||||
{
|
||||
user_id: userId,
|
||||
room_id: roomId,
|
||||
message: messageRef.current,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (chatInputFix.current) {
|
||||
// setMessage('')
|
||||
// chatInputFix.current = false
|
||||
// } else {
|
||||
// setMessage(e.key.length === 1 ? e.key : '')
|
||||
// }
|
||||
// messageChannel
|
||||
// .send({
|
||||
// type: 'broadcast',
|
||||
// event: 'MESSAGE',
|
||||
// payload: { user_id: userId, isTyping: true, message: '' },
|
||||
// })
|
||||
// .catch(() => {})
|
||||
// } else if (e.code === 'Enter') {
|
||||
// // End typing session and send message
|
||||
// setIsTyping(false)
|
||||
// messageChannel
|
||||
// .send({
|
||||
// type: 'broadcast',
|
||||
// event: 'MESSAGE',
|
||||
// payload: { user_id: userId, isTyping: false, message: messageRef.current },
|
||||
// })
|
||||
// .catch(() => {})
|
||||
// if (messageRef.current) {
|
||||
// const updatedMessagesInTransit = (messagesInTransitRef?.current ?? []).concat([
|
||||
// messageRef.current,
|
||||
// ])
|
||||
// setMessagesInTransit(updatedMessagesInTransit)
|
||||
// if (chatboxRef.current) chatboxRef.current.scrollIntoView({ behavior: 'smooth' })
|
||||
// insertMsgTimestampRef.current = performance.now()
|
||||
// await supabase?.from('messages').insert([
|
||||
// {
|
||||
// user_id: userId,
|
||||
// room_id: roomId,
|
||||
// message: messageRef.current,
|
||||
// },
|
||||
// ])
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// End typing session without sending
|
||||
if (e.code === 'Escape' && isTypingRef.current) {
|
||||
setIsTyping(false)
|
||||
setIsCancelled(true)
|
||||
chatInputFix.current = true
|
||||
// // End typing session without sending
|
||||
// if (e.code === 'Escape' && isTypingRef.current) {
|
||||
// setIsTyping(false)
|
||||
// setIsCancelled(true)
|
||||
// chatInputFix.current = true
|
||||
|
||||
messageChannel
|
||||
.send({
|
||||
type: 'broadcast',
|
||||
event: 'MESSAGE',
|
||||
payload: { user_id: userId, isTyping: false, message: '' },
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
}
|
||||
// messageChannel
|
||||
// .send({
|
||||
// type: 'broadcast',
|
||||
// event: 'MESSAGE',
|
||||
// payload: { user_id: userId, isTyping: false, message: '' },
|
||||
// })
|
||||
// .catch(() => {})
|
||||
// }
|
||||
// }
|
||||
|
||||
window.addEventListener('mousemove', setMouseEvent)
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
// window.addEventListener('keydown', onKeyDown)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
pingIntervalId && clearInterval(pingIntervalId)
|
||||
// pingIntervalId && clearInterval(pingIntervalId)
|
||||
|
||||
window.removeEventListener('mousemove', setMouseEvent)
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
// window.removeEventListener('keydown', onKeyDown)
|
||||
|
||||
pingChannel && supabase?.removeChannel(pingChannel)
|
||||
// pingChannel && supabase?.removeChannel(pingChannel)
|
||||
messageChannel && supabase?.removeChannel(messageChannel)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [roomId, isInitialStateSynced])
|
||||
|
||||
const getUserColor = useCallback(
|
||||
(userId: string | undefined) => {
|
||||
if (!userId) {
|
||||
return INTERACTIVE_GRID_COLORS(isDarkTheme).CURRENT_USER_HOVER
|
||||
}
|
||||
// const getUserColor = useCallback(
|
||||
// (userId: string | undefined) => {
|
||||
// if (!userId) {
|
||||
// return INTERACTIVE_GRID_COLORS(isDarkTheme).CURRENT_USER_HOVER
|
||||
// }
|
||||
|
||||
if (userColors[userId]) {
|
||||
return userColors[userId]
|
||||
}
|
||||
// if (userColors[userId]) {
|
||||
// return userColors[userId]
|
||||
// }
|
||||
|
||||
const colors = INTERACTIVE_GRID_COLORS(isDarkTheme).HOVER_COLORS
|
||||
const color = colors[userId.charCodeAt(0) % colors.length]
|
||||
// const colors = INTERACTIVE_GRID_COLORS(isDarkTheme).HOVER_COLORS
|
||||
// const color = colors[userId.charCodeAt(0) % colors.length]
|
||||
|
||||
setUserColors((prev) => ({ ...prev, [userId]: color }))
|
||||
return color
|
||||
},
|
||||
[userColors, isDarkTheme]
|
||||
)
|
||||
// setUserColors((prev) => ({ ...prev, [userId]: color }))
|
||||
// return color
|
||||
// },
|
||||
// [userColors, isDarkTheme]
|
||||
// )
|
||||
|
||||
const setCellHovered = useCallback(
|
||||
(key: string, isHovered: boolean, color: string) => {
|
||||
@@ -617,7 +632,7 @@ export default function InteractiveGrid() {
|
||||
// ctx.fill()
|
||||
// })
|
||||
},
|
||||
[hoveredCells, userCursors, getUserColor, isDarkTheme]
|
||||
[hoveredCells, userCursors, isDarkTheme]
|
||||
)
|
||||
|
||||
const animate = useCallback(() => {
|
||||
@@ -647,7 +662,8 @@ export default function InteractiveGrid() {
|
||||
const x = Math.floor(e.nativeEvent.offsetX / CELL_SIZE)
|
||||
const y = Math.floor(e.nativeEvent.offsetY / CELL_SIZE)
|
||||
const cellKey = `${x},${y}`
|
||||
const userColor = getUserColor(CURRENT_USER_ID)
|
||||
// const userColor = getUserColor(CURRENT_USER_ID)
|
||||
const userColor = INTERACTIVE_GRID_COLORS(isDarkTheme).CURRENT_USER_HOVER
|
||||
|
||||
if (x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE) {
|
||||
setCellHovered(cellKey, true, userColor)
|
||||
@@ -671,14 +687,14 @@ export default function InteractiveGrid() {
|
||||
// }
|
||||
}
|
||||
},
|
||||
[hoveredCells, setCellHovered, realtimeChannel, getUserColor, isDarkTheme]
|
||||
[hoveredCells, setCellHovered, realtimeChannel, isDarkTheme]
|
||||
)
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
hoveredCells.forEach((_, key) => {
|
||||
setCellHovered(key, false, getUserColor(CURRENT_USER_ID))
|
||||
setCellHovered(key, false, INTERACTIVE_GRID_COLORS(isDarkTheme).CURRENT_USER_HOVER)
|
||||
})
|
||||
}, [hoveredCells, setCellHovered, getUserColor, userData])
|
||||
}, [hoveredCells, setCellHovered, userData])
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!realtimeChannel && supabase) {
|
||||
@@ -751,8 +767,10 @@ export default function InteractiveGrid() {
|
||||
key={userId}
|
||||
x={x}
|
||||
y={y}
|
||||
color={color}
|
||||
hue={hue}
|
||||
// color={color}
|
||||
// hue={hue}
|
||||
color={getColor('gray').bg}
|
||||
hue={getColor('gray').hue}
|
||||
message={message || ''}
|
||||
isTyping={isTyping || false}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FC, FormEvent, useEffect, useRef, useState } from 'react'
|
||||
import { cn } from 'ui'
|
||||
|
||||
interface Props {
|
||||
x?: number
|
||||
@@ -75,29 +76,47 @@ const Cursor: FC<Props> = ({
|
||||
{!_isLocalClient && (
|
||||
<svg
|
||||
width="18"
|
||||
height="24"
|
||||
viewBox="0 0 18 24"
|
||||
height="21"
|
||||
viewBox="0 0 18 21"
|
||||
fill="none"
|
||||
className="absolute top-0 left-0 transform transition pointer-events-none"
|
||||
style={{ color, transform: `translateX(${x}px) translateY(${y}px)` }}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2.717 2.22918L15.9831 15.8743C16.5994 16.5083 16.1503 17.5714 15.2661 17.5714H9.35976C8.59988 17.5714 7.86831 17.8598 7.3128 18.3783L2.68232 22.7C2.0431 23.2966 1 22.8434 1 21.969V2.92626C1 2.02855 2.09122 1.58553 2.717 2.22918Z"
|
||||
fill={color}
|
||||
stroke={hue}
|
||||
strokeWidth="2"
|
||||
d="M4.19777 1.00263C3.10663 0.0999637 1.45741 0.876037 1.45741 2.29215V18.767C1.45741 20.368 3.48943 21.0551 4.4611 19.7827L8.48462 14.5138C8.59593 14.368 8.76885 14.2825 8.95226 14.2825H15.6018C17.1701 14.2825 17.877 12.3191 16.6686 11.3194L4.19777 1.00263Z"
|
||||
fill={hue}
|
||||
stroke={color}
|
||||
strokeWidth="1"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
// <svg
|
||||
// width="18"
|
||||
// height="24"
|
||||
// viewBox="0 0 18 24"
|
||||
// fill="none"
|
||||
// className="absolute top-0 left-0 transform transition pointer-events-none"
|
||||
// style={{ color, transform: `translateX(${x}px) translateY(${y}px)` }}
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// >
|
||||
// <path
|
||||
// d="M2.717 2.22918L15.9831 15.8743C16.5994 16.5083 16.1503 17.5714 15.2661 17.5714H9.35976C8.59988 17.5714 7.86831 17.8598 7.3128 18.3783L2.68232 22.7C2.0431 23.2966 1 22.8434 1 21.969V2.92626C1 2.02855 2.09122 1.58553 2.717 2.22918Z"
|
||||
// fill={color}
|
||||
// stroke={hue}
|
||||
// strokeWidth="2"
|
||||
// />
|
||||
// </svg>
|
||||
)}
|
||||
<div
|
||||
ref={chatBubbleRef}
|
||||
className={[
|
||||
className={cn(
|
||||
'transition-all absolute top-0 left-0 py-2 rounded-full shadow-md',
|
||||
'flex items-center justify-between px-4 space-x-2 pointer-events-none',
|
||||
`${showMessageBubble ? 'opacity-100' : 'opacity-0'}`,
|
||||
`${_isLocalClient && !hideInput ? 'w-[280px]' : 'max-w-[280px] overflow-hidden'}`,
|
||||
].join(' ')}
|
||||
`${_isLocalClient && !hideInput ? 'w-[280px]' : 'max-w-[280px] overflow-hidden'}`
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
transform: `translateX(${
|
||||
|
||||
@@ -1,41 +1,13 @@
|
||||
import { sampleSize } from 'lodash'
|
||||
|
||||
const colors = {
|
||||
tomato: {
|
||||
bg: 'var(--colors-tomato9)',
|
||||
hue: 'var(--colors-tomato7)',
|
||||
brand: {
|
||||
bg: 'hsl(var(--brand-default))',
|
||||
hue: 'hsl(var(--brand-500))',
|
||||
},
|
||||
crimson: {
|
||||
bg: 'var(--colors-crimson9)',
|
||||
hue: 'var(--colors-crimson7)',
|
||||
},
|
||||
pink: {
|
||||
bg: 'var(--colors-pink9)',
|
||||
hue: 'var(--colors-pink7)',
|
||||
},
|
||||
plum: {
|
||||
bg: 'var(--colors-plum9)',
|
||||
hue: 'var(--colors-plum7)',
|
||||
},
|
||||
indigo: {
|
||||
bg: 'var(--colors-indigo9)',
|
||||
hue: 'var(--colors-indigo7)',
|
||||
},
|
||||
blue: {
|
||||
bg: 'var(--colors-blue9)',
|
||||
hue: 'var(--colors-blue7)',
|
||||
},
|
||||
cyan: {
|
||||
bg: 'var(--colors-cyan9)',
|
||||
hue: 'var(--colors-cyan7)',
|
||||
},
|
||||
green: {
|
||||
bg: 'var(--colors-green9)',
|
||||
hue: 'var(--colors-green7)',
|
||||
},
|
||||
orange: {
|
||||
bg: 'var(--colors-orange9)',
|
||||
hue: 'var(--colors-orange7)',
|
||||
gray: {
|
||||
bg: 'hsl(var(--foreground-muted))',
|
||||
hue: 'hsl(var(--background-surface-100))',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -54,3 +26,7 @@ export const getRandomColors = (qty: number) => {
|
||||
export const getRandomColor = () => {
|
||||
return Object.values(colors)[Math.floor(Math.random() * Object.values(colors).length)]
|
||||
}
|
||||
|
||||
export const getColor = (color: 'brand' | 'gray') => {
|
||||
return colors[color]
|
||||
}
|
||||
Reference in new issue
Block a user