Skip to content

Commit 0e57332

Browse files
authored
chore: social refactor (#474)
1 parent ab7396c commit 0e57332

42 files changed

Lines changed: 2196 additions & 472 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

components/PlayerDisplay.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ import FiveStackToolTip from "./FiveStackToolTip.vue";
256256
</a>
257257
</p>
258258
</div>
259+
<slot name="subline"></slot>
259260
</div>
260261
</slot>
261262
</div>

components/PlayerSearch.vue

Lines changed: 224 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -49,30 +49,74 @@ const { height: viewportHeight } = useVisualViewport();
4949
>
5050
<div class="flex-1 overflow-y-auto min-h-0 p-4 flex flex-col">
5151
<div class="flex-1" />
52-
<div
53-
v-if="!players?.length"
54-
class="p-4 text-center text-muted-foreground"
55-
>
56-
{{ $t("player.search.no_players_found") }}
57-
</div>
5852

59-
<div v-else class="divide-y">
53+
<!-- Grouped: Friends / Others -->
54+
<template v-if="groupByFriends">
6055
<div
61-
v-for="player in players"
62-
:key="`player-${player.steam_id}}`"
63-
class="px-3 py-2 hover:bg-accent cursor-pointer"
64-
@click="select(player)"
56+
v-if="!hasGroupResults"
57+
class="p-4 text-center text-muted-foreground"
6558
>
66-
<PlayerDisplay :player="player" />
59+
{{ $t("player.search.no_players_found") }}
6760
</div>
68-
</div>
61+
<template v-for="group in playerGroups" :key="group.key">
62+
<div v-if="group.players.length">
63+
<div
64+
class="sticky top-0 z-20 flex items-center gap-2 border-b border-border bg-background px-3 py-2 font-mono text-[0.6rem] font-bold uppercase tracking-[0.18em] text-muted-foreground"
65+
>
66+
<span class="h-[2px] w-2 bg-[hsl(var(--tac-amber))]" />
67+
{{ group.label }}
68+
<span
69+
class="ml-auto tabular-nums text-[hsl(var(--tac-amber))]"
70+
>
71+
{{ group.players.length }}
72+
</span>
73+
</div>
74+
<div class="divide-y">
75+
<div
76+
v-for="player in group.players"
77+
:key="`g-${group.key}-${player.steam_id}`"
78+
class="px-3 py-2 hover:bg-accent cursor-pointer"
79+
@click="select(player)"
80+
>
81+
<PlayerDisplay :player="player" />
82+
</div>
83+
</div>
84+
</div>
85+
</template>
86+
</template>
87+
88+
<template v-else>
89+
<div
90+
v-if="!displayPlayers.length"
91+
class="p-4 text-center text-muted-foreground"
92+
>
93+
{{ $t("player.search.no_players_found") }}
94+
</div>
95+
96+
<div v-else class="divide-y">
97+
<div
98+
v-for="player in displayPlayers"
99+
:key="`player-${player.steam_id}}`"
100+
class="px-3 py-2 hover:bg-accent cursor-pointer"
101+
@click="select(player)"
102+
>
103+
<PlayerDisplay :player="player" />
104+
</div>
105+
</div>
106+
</template>
69107
</div>
70108

71109
<div
72-
v-if="players?.length"
110+
v-if="groupByFriends ? hasGroupResults : displayPlayers.length"
73111
class="px-4 py-2 text-xs text-muted-foreground border-t"
74112
>
75-
{{ players.length }} {{ $t("player.search.found_players") }}
113+
<template v-if="groupByFriends">
114+
{{ playerGroups[0].players.length + playerGroups[1].players.length }}
115+
{{ $t("player.search.found_players") }}
116+
</template>
117+
<template v-else>
118+
{{ displayPlayers.length }} {{ $t("player.search.found_players") }}
119+
</template>
76120
</div>
77121

78122
<div class="flex items-center justify-between p-4 border-t">
@@ -158,29 +202,67 @@ const { height: viewportHeight } = useVisualViewport();
158202
</div>
159203

160204
<div class="max-h-[300px] overflow-y-auto">
161-
<div
162-
v-if="!players?.length"
163-
class="p-4 text-center text-muted-foreground"
164-
>
165-
{{ $t("player.search.no_players_found") }}
166-
</div>
205+
<!-- Grouped: Friends / Others -->
206+
<template v-if="groupByFriends">
207+
<div
208+
v-if="!hasGroupResults"
209+
class="p-4 text-center text-muted-foreground"
210+
>
211+
{{ $t("player.search.no_players_found") }}
212+
</div>
213+
<template v-for="group in playerGroups" :key="group.key">
214+
<div v-if="group.players.length">
215+
<div
216+
class="sticky top-0 z-20 flex items-center gap-2 border-b border-border bg-popover px-3 py-2 font-mono text-[0.6rem] font-bold uppercase tracking-[0.18em] text-muted-foreground"
217+
>
218+
<span class="h-[2px] w-2 bg-[hsl(var(--tac-amber))]" />
219+
{{ group.label }}
220+
<span
221+
class="ml-auto tabular-nums text-[hsl(var(--tac-amber))]"
222+
>
223+
{{ group.players.length }}
224+
</span>
225+
</div>
226+
<div class="divide-y">
227+
<div
228+
v-for="player in group.players"
229+
:key="`g-${group.key}-${player.steam_id}`"
230+
class="px-3 py-2 hover:bg-accent cursor-pointer"
231+
@click="select(player)"
232+
>
233+
<PlayerDisplay :player="player" />
234+
</div>
235+
</div>
236+
</div>
237+
</template>
238+
</template>
167239

168-
<div v-else>
169-
<div class="px-3 py-2 text-sm text-muted-foreground">
170-
{{ players.length }} {{ $t("player.search.found_players") }}
240+
<template v-else>
241+
<div
242+
v-if="!displayPlayers.length"
243+
class="p-4 text-center text-muted-foreground"
244+
>
245+
{{ $t("player.search.no_players_found") }}
171246
</div>
172247

173-
<div class="divide-y">
174-
<div
175-
v-for="player in players"
176-
:key="`player-${player.steam_id}}`"
177-
class="px-3 py-2 hover:bg-accent cursor-pointer"
178-
@click="select(player)"
179-
>
180-
<PlayerDisplay :player="player" />
248+
<div v-else>
249+
<div class="px-3 py-2 text-sm text-muted-foreground">
250+
{{ displayPlayers.length }}
251+
{{ $t("player.search.found_players") }}
252+
</div>
253+
254+
<div class="divide-y">
255+
<div
256+
v-for="player in displayPlayers"
257+
:key="`player-${player.steam_id}}`"
258+
class="px-3 py-2 hover:bg-accent cursor-pointer"
259+
@click="select(player)"
260+
>
261+
<PlayerDisplay :player="player" />
262+
</div>
181263
</div>
182264
</div>
183-
</div>
265+
</template>
184266
</div>
185267
</div>
186268
</PopoverContent>
@@ -245,6 +327,11 @@ export default {
245327
required: false,
246328
default: false,
247329
},
330+
groupByFriends: {
331+
type: Boolean,
332+
required: false,
333+
default: false,
334+
},
248335
},
249336
data() {
250337
return {
@@ -263,6 +350,44 @@ export default {
263350
canSelectSelf() {
264351
return this.self && this.me && !this.exclude.includes(this.me.steam_id);
265352
},
353+
// The current user, surfaced as a selectable entry (the online presence
354+
// list never contains yourself). Hidden once you're in `exclude`, i.e.
355+
// already in a lineup, and filtered by the active query.
356+
selfPlayer(): Player | null {
357+
if (!this.canSelectSelf || !this.me) return null;
358+
const me = this.me as any;
359+
const q = this.query.toLowerCase();
360+
if (
361+
q &&
362+
!(
363+
me.name?.toLowerCase().includes(q) ||
364+
String(me.steam_id).includes(this.query)
365+
)
366+
) {
367+
return null;
368+
}
369+
return {
370+
steam_id: me.steam_id,
371+
name: me.name,
372+
avatar_url: me.avatar_url,
373+
country: me.country,
374+
role: me.role,
375+
is_banned: me.is_banned,
376+
is_muted: me.is_muted,
377+
is_gagged: me.is_gagged,
378+
elo: me.elo,
379+
} as Player;
380+
},
381+
// Non-grouped results with `me` pinned to the top when selectable.
382+
displayPlayers(): Player[] {
383+
const base = this.players ?? [];
384+
if (!this.selfPlayer) return base as Player[];
385+
const meId = String(this.me?.steam_id);
386+
return [
387+
this.selfPlayer,
388+
...base.filter((p: Player) => String(p.steam_id) !== meId),
389+
];
390+
},
266391
onlineOnly: {
267392
get() {
268393
return useSearchStore().onlineOnly;
@@ -272,6 +397,72 @@ export default {
272397
useSearchStore().onlineOnly = value;
273398
},
274399
},
400+
friendIds(): Set<string> {
401+
return new Set(
402+
(useMatchmakingStore().friends as any[])
403+
.filter((f: any) => f.status !== "Pending")
404+
.map((f: any) => String(f.steam_id)),
405+
);
406+
},
407+
// Friends list, filtered by query/exclude/self and sorted online-first.
408+
// The online toggle applies here too: when on, only online friends show;
409+
// when off, all friends (online + offline). Built from the full friends
410+
// list so offline friends reliably appear when the toggle is off.
411+
friendsForSearch(): Player[] {
412+
if (!this.groupByFriends) return [];
413+
const store = useMatchmakingStore();
414+
const onlineIds = new Set(
415+
(store.onlinePlayerSteamIds as string[]).map(String),
416+
);
417+
const q = this.query.toLowerCase();
418+
const excluded = new Set((this.exclude as string[]).map(String));
419+
const meId = String(this.me?.steam_id ?? "");
420+
421+
return (store.friends as any[])
422+
.filter((f: any) => {
423+
if (f.status === "Pending") return false;
424+
const id = String(f.steam_id);
425+
if (excluded.has(id)) return false;
426+
if (!this.canSelectSelf && id === meId) return false;
427+
// Strictly respect the toggle: online-only -> only online friends,
428+
// otherwise -> only offline friends.
429+
const online = onlineIds.has(id);
430+
if (this.onlineOnly !== online) return false;
431+
if (!q) return true;
432+
return f.name?.toLowerCase().includes(q) || id.includes(this.query);
433+
})
434+
.sort((a: any, b: any) =>
435+
(a.name || "").localeCompare(b.name || ""),
436+
);
437+
},
438+
// Normal search results, minus anyone already shown in the Friends section.
439+
otherPlayers(): Player[] {
440+
const meId = this.selfPlayer ? String(this.me?.steam_id) : null;
441+
return (this.players ?? []).filter(
442+
(p: Player) =>
443+
!this.friendIds.has(String(p.steam_id)) &&
444+
(meId === null || String(p.steam_id) !== meId),
445+
);
446+
},
447+
playerGroups(): Array<{ key: string; label: string; players: Player[] }> {
448+
return [
449+
{
450+
key: "friends",
451+
label: this.$t("matchmaking.friends.title"),
452+
players: this.selfPlayer
453+
? [this.selfPlayer, ...this.friendsForSearch]
454+
: this.friendsForSearch,
455+
},
456+
{
457+
key: "others",
458+
label: this.$t("matchmaking.others.title"),
459+
players: this.otherPlayers,
460+
},
461+
];
462+
},
463+
hasGroupResults(): boolean {
464+
return this.playerGroups.some((g) => g.players.length > 0);
465+
},
275466
},
276467
methods: {
277468
toggleOnlineOnly() {

components/draft-games/DraftActiveAlert.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@ const isOnDraftPage = computed(() => route.path.startsWith("/draft-room"));
4040
4141
const acknowledgedKey = ref<string | null>(null);
4242
43+
// Track rooms the user has actually opened the draft page for. If they were on
44+
// the draft page (e.g. landed on it) and then navigated away, we don't nag them
45+
// with the alert — they already know they're in the room.
46+
const visitedRoomId = ref<string | null>(null);
47+
48+
watch(
49+
[isOnDraftPage, draftGameId],
50+
([onPage, id]) => {
51+
if (onPage && id) {
52+
visitedRoomId.value = id;
53+
}
54+
},
55+
{ immediate: true },
56+
);
57+
4358
watch(roomKey, (next) => {
4459
if (next !== acknowledgedKey.value) {
4560
acknowledgedKey.value = null;
@@ -50,6 +65,7 @@ const rawShow = computed(
5065
() =>
5166
isAlertable.value &&
5267
!isOnDraftPage.value &&
68+
visitedRoomId.value !== draftGameId.value &&
5369
acknowledgedKey.value !== roomKey.value,
5470
);
5571

components/draft-games/DraftGames.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import { onMounted, onUnmounted, computed, ref, watch } from "vue";
33
import {
44
Plus,
5-
Swords,
65
Search,
76
ArrowUpDown,
87
RotateCcw,
@@ -291,7 +290,7 @@ const rehost = async () => {
291290
<div class="min-w-0">
292291
<div :class="tacticalSectionLabelClasses">
293292
<span :class="tacticalSectionTickClasses"></span>
294-
OPEN.MATCHES
293+
{{ $t("pages.play.draft_games.section_label") }}
295294
</div>
296295
<div :class="tacticalSectionDescriptionClasses">
297296
{{ $t("pages.play.draft_games.description") }}
@@ -461,7 +460,6 @@ const rehost = async () => {
461460
v-if="filtered.length === 0"
462461
class="flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border bg-card/30 px-6 py-10 text-center"
463462
>
464-
<Swords class="h-7 w-7 text-muted-foreground/50" />
465463
<p class="text-sm text-muted-foreground">
466464
{{
467465
draftGames.length === 0

components/draft-games/DraftOpenSlot.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const onSelected = (player: { steam_id: string }) => {
2020
<PlayerSearch
2121
:label="$t('draft_games.room.search_player')"
2222
:exclude="exclude"
23+
:group-by-friends="true"
24+
:self="true"
2325
@selected="onSelected"
2426
>
2527
<button type="button" class="open-slot">

0 commit comments

Comments
 (0)