adjust friend location style

This commit is contained in:
pa
2025-11-01 00:11:59 +09:00
committed by Natsumi
parent 53f253daea
commit 3f2ef7138a
2 changed files with 132 additions and 39 deletions

View File

@@ -31,12 +31,10 @@
:key="group.instanceId" :key="group.instanceId"
class="friend-view__instance"> class="friend-view__instance">
<header class="friend-view__instance-header"> <header class="friend-view__instance-header">
<span class="friend-view__instance-id" :title="group.instanceId">{{ <Location class="extra" :location="group.instanceId" style="display: inline" />
group.instanceId
}}</span>
<span class="friend-view__instance-count">{{ group.friends.length }}</span> <span class="friend-view__instance-count">{{ group.friends.length }}</span>
</header> </header>
<div class="friend-view__grid" :style="gridStyle"> <div class="friend-view__grid" :style="gridStyle(group.friends.length)">
<FriendLocationCard <FriendLocationCard
v-for="friend in group.friends" v-for="friend in group.friends"
:key="friend.id ?? friend.userId ?? friend.displayName" :key="friend.id ?? friend.userId ?? friend.displayName"
@@ -48,7 +46,7 @@
<div v-else class="friend-view__empty">No matching friends</div> <div v-else class="friend-view__empty">No matching friends</div>
</template> </template>
<template v-else> <template v-else>
<div v-if="visibleFriends.length" class="friend-view__grid" :style="gridStyle"> <div v-if="visibleFriends.length" class="friend-view__grid" :style="gridStyle(visibleFriends.length)">
<FriendLocationCard <FriendLocationCard
v-for="entry in visibleFriends" v-for="entry in visibleFriends"
:key="entry.id ?? entry.friend.id ?? entry.friend.displayName" :key="entry.id ?? entry.friend.id ?? entry.friend.displayName"
@@ -68,7 +66,7 @@
</template> </template>
<script setup> <script setup>
import { computed, nextTick, onMounted, ref, watch } from 'vue'; import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { Loading, Search } from '@element-plus/icons-vue'; import { Loading, Search } from '@element-plus/icons-vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
@@ -98,6 +96,58 @@
const itemsToShow = ref(PAGE_SIZE); const itemsToShow = ref(PAGE_SIZE);
const isLoadingMore = ref(false); const isLoadingMore = ref(false);
const scrollbarRef = ref(); const scrollbarRef = ref();
const gridWidth = ref(0);
let resizeObserver;
let cleanupResize;
const updateGridWidth = () => {
const wrap = scrollbarRef.value?.wrapRef;
if (!wrap) {
return;
}
gridWidth.value = wrap.clientWidth ?? 0;
};
const setupResizeHandling = () => {
if (cleanupResize) {
cleanupResize();
cleanupResize = undefined;
}
const wrap = scrollbarRef.value?.wrapRef;
if (!wrap) {
return;
}
updateGridWidth();
if (typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver((entries) => {
if (!entries || entries.length === 0) {
return;
}
const [entry] = entries;
gridWidth.value = entry.contentRect?.width ?? wrap.clientWidth ?? 0;
});
resizeObserver.observe(wrap);
cleanupResize = () => {
resizeObserver?.disconnect();
resizeObserver = undefined;
};
return;
}
if (typeof window !== 'undefined') {
const handleResize = () => {
updateGridWidth();
};
window.addEventListener('resize', handleResize, { passive: true });
cleanupResize = () => {
window.removeEventListener('resize', handleResize);
};
}
};
const normalizedSearchTerm = computed(() => searchTerm.value.trim().toLowerCase()); const normalizedSearchTerm = computed(() => searchTerm.value.trim().toLowerCase());
@@ -112,24 +162,19 @@
const sameInstanceGroups = computed(() => { const sameInstanceGroups = computed(() => {
const source = friendsInSameInstance?.value; const source = friendsInSameInstance?.value;
if (!Array.isArray(source) || source.length === 0) return [];
if (!Array.isArray(source) || source.length === 0) { return source
return []; .map((group, index) => {
} if (!Array.isArray(group) || group.length === 0) return null;
const friends = group;
return source.map((group, index) => { const instanceId = getFriendsLocations(friends) || `instance-${index + 1}`;
if (!Array.isArray(group) || group.length === 0) { return {
return null; instanceId: String(instanceId),
} friends
};
const friends = group; })
.filter(Boolean);
const instanceId = getFriendsLocations(friends) || `instance-${index + 1}`;
return {
instanceId: String(instanceId),
friends
};
});
}); });
const sameInstanceEntries = computed(() => const sameInstanceEntries = computed(() =>
@@ -213,16 +258,43 @@
const gridStyle = computed(() => { const gridStyle = computed(() => {
const baseWidth = 220; const baseWidth = 220;
const baseGap = 14; const baseGap = 14;
const cardWidth = baseWidth * cardScale.value; const scale = cardScale.value;
const gap = baseGap + (cardScale.value - 1) * 10; const minWidth = baseWidth * scale;
return { const gap = baseGap + (scale - 1) * 10;
'--friend-card-min-width': `${Math.round(cardWidth)}px`,
'--friend-card-gap': `${gap.toFixed(0)}px` return (count = 1) => {
const containerWidth = Math.max(gridWidth.value ?? 0, 0);
const itemCount = Math.max(Number(count) || 0, 1);
const maxColumns = Math.max(1, Math.floor((containerWidth + gap) / (minWidth + gap)));
const columns = Math.max(1, Math.min(itemCount, maxColumns));
const shouldStretch = itemCount >= maxColumns;
let cardWidth = minWidth;
if (shouldStretch) {
const columnsWidth = containerWidth - gap * (columns - 1);
const rawWidth = columnsWidth > 0 ? columnsWidth / columns : minWidth;
if (Number.isFinite(rawWidth) && rawWidth > 0) {
cardWidth = Math.max(minWidth, rawWidth);
}
}
return {
'--friend-card-min-width': `${Math.round(minWidth)}px`,
'--friend-card-gap': `${Math.round(gap)}px`,
'--friend-card-target-width': `${Math.round(cardWidth)}px`,
'--friend-grid-columns': `${columns}`
};
}; };
}); });
const handleScroll = () => { const handleScroll = () => {
if (isLoadingMore.value || filteredFriends.value.length === 0) { if (
isLoadingMore.value ||
filteredFriends.value.length === 0 ||
itemsToShow.value >= filteredFriends.value.length
) {
return; return;
} }
@@ -240,7 +312,7 @@
}; };
function loadMoreFriends() { function loadMoreFriends() {
if (isLoadingMore.value) { if (isLoadingMore.value || itemsToShow.value >= filteredFriends.value.length) {
return; return;
} }
@@ -277,7 +349,10 @@
watch([searchTerm, activeSegment], () => { watch([searchTerm, activeSegment], () => {
itemsToShow.value = PAGE_SIZE; itemsToShow.value = PAGE_SIZE;
maybeFillViewport(); nextTick(() => {
updateGridWidth();
maybeFillViewport();
});
}); });
watch( watch(
@@ -286,19 +361,33 @@
if (itemsToShow.value > length) { if (itemsToShow.value > length) {
itemsToShow.value = length; itemsToShow.value = length;
} }
maybeFillViewport(); nextTick(() => {
updateGridWidth();
maybeFillViewport();
});
} }
); );
watch(cardScale, () => { watch(cardScale, () => {
nextTick(() => { nextTick(() => {
scrollbarRef.value?.update?.(); scrollbarRef.value?.update?.();
updateGridWidth();
maybeFillViewport(); maybeFillViewport();
}); });
}); });
onMounted(() => { onMounted(() => {
maybeFillViewport(); nextTick(() => {
setupResizeHandling();
maybeFillViewport();
});
});
onBeforeUnmount(() => {
if (cleanupResize) {
cleanupResize();
cleanupResize = undefined;
}
}); });
</script> </script>
@@ -314,7 +403,7 @@
gap: 20px; gap: 20px;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 6px 2px 0 2px; padding: 6px 10px 0 2px;
} }
.friend-view__segmented :deep(.el-segmented) { .friend-view__segmented :deep(.el-segmented) {
@@ -353,14 +442,18 @@
} }
.friend-view__scroll { .friend-view__scroll {
padding: 2px; padding: 2px 10px 2px 2px;
} }
.friend-view__grid { .friend-view__grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(var(--friend-card-min-width, 200px), max-content)); grid-template-columns: repeat(
var(--friend-grid-columns, 1),
minmax(var(--friend-card-min-width, 200px), var(--friend-card-target-width, 1fr))
);
gap: var(--friend-card-gap, 18px); gap: var(--friend-card-gap, 18px);
justify-content: start; justify-content: start;
padding-right: 2px;
} }
.friend-view__instances { .friend-view__instances {

View File

@@ -81,7 +81,6 @@
<style scoped lang="scss"> <style scoped lang="scss">
.friend-card { .friend-card {
--card-scale: 1; --card-scale: 1;
--friend-card-width: 300px;
position: relative; position: relative;
display: grid; display: grid;
gap: calc(14px * var(--card-scale)); gap: calc(14px * var(--card-scale));
@@ -92,8 +91,9 @@
transition: transition:
box-shadow 0.2s ease, box-shadow 0.2s ease,
transform 0.2s ease; transform 0.2s ease;
width: 220px; width: 100%;
max-width: 100%; max-width: var(--friend-card-target-width, 220px);
min-width: var(--friend-card-min-width, 220px);
&:hover { &:hover {
box-shadow: 0 calc(10px * var(--card-scale)) calc(24px * var(--card-scale)) rgba(15, 23, 42, 0.07); box-shadow: 0 calc(10px * var(--card-scale)) calc(24px * var(--card-scale)) rgba(15, 23, 42, 0.07);