Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Party kit followups #9439

Merged
merged 13 commits into from
Feb 17, 2024
10 changes: 5 additions & 5 deletions components/collection/drop/GenerativeLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@

<CursorParty
:connections="connections"
:ghost-on-elements="['#generative-preview-card']"
:ghost-on-elements="['generative-preview-card']"
:label-formatter="labelFormatter" />
</template>

Expand Down Expand Up @@ -117,20 +117,20 @@ const props = withDefaults(
)

const { chainSymbol, decimals } = useChain()
const { totalSpent, getUserStats } = useUserStats()

const { collection: collectionInfo } = useCollectionMinimal({
collectionId: computed(() => props.collectionId),
})
const address = computed(() => collectionInfo.value?.currentOwner)

const currentUserSpent = computed(
() => props.userMintedCount * (Number(props.drop?.price) || 0),
)
const { connections } = useCursorParty({
room: computed(() => props.drop.alias),
spent: currentUserSpent,
spent: totalSpent,
})

const labelFormatter = (connection: UserDetails) =>
`${formatAmountWithRound(Number(connection.spent) || 0, decimals.value)} ${chainSymbol.value}`

watch(() => props.userMintedCount, getUserStats)
</script>
2 changes: 1 addition & 1 deletion components/collection/drop/GenerativePreview.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div
id="generative-preview-card"
data-partykit="generative-preview-card"
class="border bg-background-color shadow-primary p-5 pb-6 w-full h-min lg:max-w-[490px]">
<BaseMediaItem
:src="sanitizeIpfsUrl(displayUrl)"
Expand Down
54 changes: 36 additions & 18 deletions components/common/party/CursorParty.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div
v-for="connection in connections"
:key="connection.id"
class="absolute z-[998]"
class="absolute z-[998] pointer-events-none transition-all"
:class="[{ 'opacity-20': cursorConnections.get(connection.id)?.ghost }]"
:style="{
top: `${connection.y}px`,
Expand All @@ -28,11 +28,12 @@ import { NeoIcon } from '@kodadot1/brick'
import { UserDetails } from '@/composables/party/types'

const colors = [
'text-red-400',
'text-blue-400',
'text-yellow-400',
'text-pink-400',
'text-k-accent',
'text-k-pink',
'text-k-blue',
'text-k-orange',
'text-k-aqua-blue',
'text-k-red',
'text-k-green',
]

const props = defineProps<{
Expand Down Expand Up @@ -66,17 +67,26 @@ const areRectanglesIntersecting = (rect1: DOMRect, rect2: DOMRect) => {
const checkGhostCursors = () => {
props.connections.forEach((connection) => {
props.ghostOnElements?.forEach((selector) => {
const element = document.querySelector(selector)
const cursor = document.getElementById(`cursor-${connection.id}`)
const elements = document.querySelectorAll(
`[data-partykit="${selector}"]`,
)
let ghost = false

if (!element || !cursor) {
return
}
elements.forEach((element) => {
if (ghost) {
return
}

const ghost = areRectanglesIntersecting(
cursor.getBoundingClientRect(),
element.getBoundingClientRect(),
)
const cursor = document.getElementById(`cursor-${connection.id}`)
if (!element || !cursor) {
return
}

ghost = areRectanglesIntersecting(
cursor.getBoundingClientRect(),
element.getBoundingClientRect(),
)
})

cursorConnections.value.set(connection.id, {
...(cursorConnections.value.get(connection.id) as any),
Expand All @@ -88,13 +98,21 @@ const checkGhostCursors = () => {

watch(
() => props.connections,
(connections: UserDetails[]) => {
(connections, prevConnections) => {
checkGhostCursors()

connections.forEach((connection) => {
if (!cursorConnections.value.has(connection.id)) {
const isNew = !cursorConnections.value.has(connection.id)
const newSpent =
prevConnections &&
prevConnections.find(
(prevConnection) => connection.id === prevConnection.id,
)?.spent !== connection.spent

if (isNew || newSpent) {
const color = cursorConnections.value.get(connection.id)?.color
cursorConnections.value.set(connection.id, {
color: getRandomColor(),
color: color || getRandomColor(),
label: props.labelFormatter(connection),
})
}
Expand Down
4 changes: 3 additions & 1 deletion composables/party/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type UserDetails = {
spent?: number
} & Cursor

export type MaybeUserDetails = UserDetails | null

export type UpdateMessage = {
type: 'update'
details: UserDetails
Expand All @@ -22,5 +24,5 @@ export type RemoveMessage = {

export type SyncMessage = {
type: 'sync'
connections: UserDetails[]
connections: Record<string, MaybeUserDetails>
}
81 changes: 56 additions & 25 deletions composables/party/useCursorParty.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,79 @@
import useParty from '@/composables/party/useParty'
import { RemoveMessage, SyncMessage, UpdateMessage, UserDetails } from './types'
import {
MaybeUserDetails,
RemoveMessage,
SyncMessage,
UpdateMessage,
UserDetails,
} from './types'
import filter from 'lodash/filter'

type CursorPartyEvents = UpdateMessage | RemoveMessage | SyncMessage

export default ({ room, spent }: { room: Ref<string>; spent: Ref<number> }) => {
const THROTTLE_AMOUNT_MESSAGE = 100
const ZERO_DOT_VISIBLE_CURSOR_AMOUNT = 10
const THROTTLE_AMOUNT_MESSAGE_UPDATE = 100 // ms

export default ({ room, spent }: { room: Ref<string>; spent: Ref<number> }) => {
const { x, y, sourceType } = useMouse()
const { width } = useWindowSize()
const connections = ref<UserDetails[]>([])
const connections = ref(new Map<string, MaybeUserDetails>())

const onMessage = (data: CursorPartyEvents) => {
switch (data.type) {
case 'sync':
connections.value = data.connections
case 'sync': {
const map = new Map()
for (const [key, value] of Object.entries(data.connections)) {
map.set(key, value ? getPositionAdujestToWindowSize(value) : null)
}
connections.value = map
break
}
case 'update': {
const has = connections.value.find(
(connection) => connection.id === data.details.id,
connections.value.set(
data.details.id,
getPositionAdujestToWindowSize(data.details),
)
const newValue = { ...data.details, x: data.details.x * width.value }

if (!has) {
connections.value.push(newValue)
} else {
connections.value = connections.value.map((connection) => {
if (connection.id === data.details.id) {
return newValue
}
return connection
})
}
break
}
case 'remove':
connections.value = connections.value.filter(
(connection) => connection.id !== data.id,
)
connections.value.delete(data.id)
break
}
}

const { sendMessage: send } = useParty<CursorPartyEvents>({ room, onMessage })
const sendMessage = useThrottleFn(send, THROTTLE_AMOUNT_MESSAGE)

const cursorConnections = computed(
() =>
Object.values(Object.fromEntries(connections.value.entries())).filter(
Boolean,
) as UserDetails[],
)

const getPositionAdujestToWindowSize = (
details: UserDetails,
): UserDetails => ({
...details,
x: details.x * width.value,
})

const isZeroDot = (connection: UserDetails) => !Number(connection.spent)

const visibleConnections = computed(() => {
const withPositionFirst = cursorConnections.value.sort(
(a, b) => Number(b?.x) - Number(a?.x),
)
const someZeroDotCursors = filter(withPositionFirst, (connection) =>
isZeroDot(connection),
).slice(0, ZERO_DOT_VISIBLE_CURSOR_AMOUNT)
const othersCursors = filter(
cursorConnections.value,
(connection) => !isZeroDot(connection),
)
return [...someZeroDotCursors, ...othersCursors]
})

const sendMessage = useThrottleFn(send, THROTTLE_AMOUNT_MESSAGE_UPDATE)

watchEffect(() => {
sendMessage({
Expand All @@ -53,5 +84,5 @@ export default ({ room, spent }: { room: Ref<string>; spent: Ref<number> }) => {
})
})

return { connections }
return { connections: visibleConnections }
}
40 changes: 40 additions & 0 deletions composables/useUserStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import resolveQueryPath from '@/utils/queryPathResolver'
import { Event } from '@/components/rmrk/service/types'

export default () => {
const { client, urlPrefix } = usePrefix()
const { accountId } = useAuth()

const totalSpent = ref(0)

const getUserStats = async () => {
const query = await resolveQueryPath(client.value, 'profileStatsById')
const { data } = await useAsyncQuery<{ invested: Event[] }>({
query: query.default,
clientId: client.value,
variables: {
id: accountId.value,
denyList: getDenyList(urlPrefix.value),
},
})

const holdingsEvents = data.value?.invested.filter(
(event) => event.nft.currentOwner === accountId.value,
)

totalSpent.value = Number(getSumOfObjectField(holdingsEvents, 'meta'))
}

onBeforeMount(getUserStats)

watch(accountId, (value) => {
if (!value) {
totalSpent.value = 0
}
})

return {
totalSpent,
getUserStats,
}
}
Loading