feat: add spacing option to friends locations card and favtories cards (#1477)

This commit is contained in:
pa
2025-11-12 16:12:45 +09:00
committed by Natsumi
parent dcd06ff6dd
commit a2f76d1a3e
6 changed files with 320 additions and 96 deletions
+50 -18
View File
@@ -21,10 +21,10 @@
<el-button :icon="MoreFilled" size="small" circle />
<template #dropdown>
<el-dropdown-menu class="favorites-dropdown">
<li class="favorites-dropdown__scale" @click.stop>
<div class="favorites-dropdown__scale-header">
<li class="favorites-dropdown__control" @click.stop>
<div class="favorites-dropdown__control-header">
<span>Scale</span>
<span class="favorites-dropdown__scale-value">{{ avatarCardScalePercent }}%</span>
<span class="favorites-dropdown__control-value">{{ avatarCardScalePercent }}%</span>
</div>
<el-slider
v-model="avatarCardScale"
@@ -34,6 +34,21 @@
:step="avatarCardScaleSlider.step"
:show-tooltip="false" />
</li>
<li class="favorites-dropdown__control" @click.stop>
<div class="favorites-dropdown__control-header">
<span>Spacing</span>
<span class="favorites-dropdown__control-value">
{{ avatarCardSpacingPercent }}%
</span>
</div>
<el-slider
v-model="avatarCardSpacing"
class="favorites-dropdown__slider"
:min="avatarCardSpacingSlider.min"
:max="avatarCardSpacingSlider.max"
:step="avatarCardSpacingSlider.step"
:show-tooltip="false" />
</li>
<el-dropdown-item @click="handleAvatarImportClick">
{{ t('view.favorite.import') }}
</el-dropdown-item>
@@ -531,17 +546,31 @@
const {
cardScale: avatarCardScale,
cardSpacing: avatarCardSpacing,
slider: avatarCardScaleSlider,
spacingSlider: avatarCardSpacingSlider,
containerRef: avatarFavoritesContainerRef,
gridStyle: avatarFavoritesGridStyle
} = useFavoritesCardScaling({
configKey: 'VRCX_FavoritesAvatarCardScale',
spacingConfigKey: 'VRCX_FavoritesAvatarCardSpacing',
min: 0.6,
max: 1,
step: 0.01
step: 0.01,
spacingMin: 0.5,
spacingMax: 1.5,
spacingStep: 0.05,
basePaddingY: 8,
basePaddingX: 10,
baseContentGap: 10,
baseActionGap: 8,
baseActionGroupGap: 6,
baseActionMargin: 8,
baseCheckboxMargin: 10
});
const avatarCardScalePercent = computed(() => Math.round(avatarCardScale.value * 100));
const avatarCardSpacingPercent = computed(() => Math.round(avatarCardSpacing.value * 100));
const avatarExportDialogVisible = ref(false);
const avatarFavoriteSearch = ref('');
@@ -1460,7 +1489,6 @@
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
border: none;
background: transparent;
border-radius: 8px;
@@ -1469,7 +1497,8 @@
cursor: pointer;
color: inherit;
transition: background-color 0.15s ease;
height: 32px;
min-height: 32px;
align-self: stretch;
}
.favorites-group-menu__item:hover {
@@ -1500,7 +1529,6 @@
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
border: none;
background: transparent;
padding: 6px 10px;
@@ -1509,7 +1537,8 @@
color: inherit;
font-size: 13px;
transition: background-color 0.15s ease;
height: 32px;
min-height: 32px;
align-self: stretch;
}
.group-visibility-menu__item:hover,
@@ -1625,7 +1654,7 @@
box-sizing: border-box;
border: 1px solid var(--el-border-color);
border-radius: calc(8px * var(--favorites-card-scale, 1));
padding: calc(8px * var(--favorites-card-scale, 1)) calc(10px * var(--favorites-card-scale, 1));
padding: var(--favorites-card-padding-y, 8px) var(--favorites-card-padding-x, 10px);
cursor: pointer;
background: var(--el-bg-color);
transition:
@@ -1650,7 +1679,7 @@
:deep(.favorites-search-card__content) {
display: flex;
align-items: center;
gap: calc(10px * var(--favorites-card-scale, 1));
gap: var(--favorites-card-content-gap, 10px);
flex: 1;
min-width: 0;
}
@@ -1720,8 +1749,8 @@
:deep(.favorites-search-card__actions) {
display: flex;
flex-direction: column;
gap: 8px;
margin-left: 8px;
gap: var(--favorites-card-action-gap, 8px);
margin-left: var(--favorites-card-action-margin, 8px);
align-items: center;
justify-content: center;
flex: 0 0 auto;
@@ -1740,7 +1769,7 @@
:deep(.favorites-search-card__action-group) {
display: flex;
gap: 6px;
gap: var(--favorites-card-action-group-gap, 6px);
width: 100%;
}
@@ -1751,7 +1780,7 @@
:deep(.favorites-search-card__action--checkbox) {
align-items: center;
justify-content: flex-end;
margin-right: 10px;
margin-right: var(--favorites-card-checkbox-margin, 10px);
}
:deep(.favorites-search-card__action--checkbox .el-checkbox) {
@@ -1771,15 +1800,18 @@
height: 100%;
}
.favorites-dropdown__scale {
.favorites-dropdown__control {
list-style: none;
padding: 12px 16px 8px;
border-bottom: 1px solid var(--el-border-color-lighter);
min-width: 220px;
cursor: default;
}
.favorites-dropdown__scale-header {
.favorites-dropdown__control:not(:last-child) {
border-bottom: 1px solid var(--el-border-color-lighter);
}
.favorites-dropdown__control-header {
display: flex;
align-items: center;
justify-content: space-between;
@@ -1789,7 +1821,7 @@
margin-bottom: 6px;
}
.favorites-dropdown__scale-value {
.favorites-dropdown__control-value {
font-size: 12px;
color: var(--el-text-color-secondary);
}
+52 -18
View File
@@ -21,10 +21,12 @@
<el-button :icon="MoreFilled" size="small" circle @click.stop />
<template #dropdown>
<el-dropdown-menu class="favorites-dropdown">
<li class="favorites-dropdown__scale" @click.stop>
<div class="favorites-dropdown__scale-header">
<li class="favorites-dropdown__control" @click.stop>
<div class="favorites-dropdown__control-header">
<span>Scale</span>
<span class="favorites-dropdown__scale-value">{{ friendCardScalePercent }}%</span>
<span class="favorites-dropdown__control-value">
{{ friendCardScalePercent }}%
</span>
</div>
<el-slider
v-model="friendCardScale"
@@ -34,6 +36,21 @@
:step="friendCardScaleSlider.step"
:show-tooltip="false" />
</li>
<li class="favorites-dropdown__control" @click.stop>
<div class="favorites-dropdown__control-header">
<span>Spacing</span>
<span class="favorites-dropdown__control-value">
{{ friendCardSpacingPercent }}%
</span>
</div>
<el-slider
v-model="friendCardSpacing"
class="favorites-dropdown__slider"
:min="friendCardSpacingSlider.min"
:max="friendCardSpacingSlider.max"
:step="friendCardSpacingSlider.step"
:show-tooltip="false" />
</li>
<el-dropdown-item @click="handleFriendImportClick">
{{ t('view.favorite.import') }}
</el-dropdown-item>
@@ -295,17 +312,31 @@
const {
cardScale: friendCardScale,
cardSpacing: friendCardSpacing,
slider: friendCardScaleSlider,
spacingSlider: friendCardSpacingSlider,
containerRef: friendFavoritesContainerRef,
gridStyle: friendFavoritesGridStyle
} = useFavoritesCardScaling({
configKey: 'VRCX_FavoritesFriendCardScale',
spacingConfigKey: 'VRCX_FavoritesFriendCardSpacing',
min: 0.6,
max: 1,
step: 0.01
step: 0.01,
spacingMin: 0.5,
spacingMax: 1.5,
spacingStep: 0.05,
basePaddingY: 8,
basePaddingX: 10,
baseContentGap: 10,
baseActionGap: 8,
baseActionGroupGap: 6,
baseActionMargin: 8,
baseCheckboxMargin: 10
});
const friendCardScalePercent = computed(() => Math.round(friendCardScale.value * 100));
const friendCardSpacingPercent = computed(() => Math.round(friendCardSpacing.value * 100));
const friendExportDialogVisible = ref(false);
const friendFavoriteSearch = ref('');
@@ -808,7 +839,6 @@
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
border: none;
background: transparent;
border-radius: 8px;
@@ -817,7 +847,8 @@
cursor: pointer;
color: inherit;
transition: background-color 0.15s ease;
height: 32px;
min-height: 32px;
align-self: stretch;
}
.favorites-group-menu__item:hover {
@@ -848,7 +879,6 @@
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
border: none;
background: transparent;
padding: 6px 10px;
@@ -857,7 +887,8 @@
color: inherit;
font-size: 13px;
transition: background-color 0.15s ease;
height: 32px;
min-height: 32px;
align-self: stretch;
}
.group-visibility-menu__item:hover,
@@ -973,7 +1004,7 @@
box-sizing: border-box;
border: 1px solid var(--el-border-color);
border-radius: calc(8px * var(--favorites-card-scale, 1));
padding: calc(8px * var(--favorites-card-scale, 1)) calc(10px * var(--favorites-card-scale, 1));
padding: var(--favorites-card-padding-y, 8px) var(--favorites-card-padding-x, 10px);
cursor: pointer;
background: var(--el-bg-color);
transition:
@@ -998,7 +1029,7 @@
:deep(.favorites-search-card__content) {
display: flex;
align-items: center;
gap: calc(10px * var(--favorites-card-scale, 1));
gap: var(--favorites-card-content-gap, 10px);
flex: 1;
min-width: 0;
}
@@ -1068,8 +1099,8 @@
:deep(.favorites-search-card__actions) {
display: flex;
flex-direction: column;
gap: 8px;
margin-left: 8px;
gap: var(--favorites-card-action-gap, 8px);
margin-left: var(--favorites-card-action-margin, 8px);
align-items: center;
justify-content: center;
flex: 0 0 auto;
@@ -1088,7 +1119,7 @@
:deep(.favorites-search-card__action-group) {
display: flex;
gap: 6px;
gap: var(--favorites-card-action-group-gap, 6px);
width: 100%;
}
@@ -1099,7 +1130,7 @@
:deep(.favorites-search-card__action--checkbox) {
align-items: center;
justify-content: flex-end;
margin-right: 10px;
margin-right: var(--favorites-card-checkbox-margin, 10px);
}
:deep(.favorites-search-card__action--checkbox .el-checkbox) {
@@ -1127,15 +1158,18 @@
height: 100%;
}
.favorites-dropdown__scale {
.favorites-dropdown__control {
list-style: none;
padding: 12px 16px 8px;
border-bottom: 1px solid var(--el-border-color-lighter);
min-width: 220px;
cursor: default;
}
.favorites-dropdown__scale-header {
.favorites-dropdown__control:not(:last-child) {
border-bottom: 1px solid var(--el-border-color-lighter);
}
.favorites-dropdown__control-header {
display: flex;
align-items: center;
justify-content: space-between;
@@ -1145,7 +1179,7 @@
margin-bottom: 6px;
}
.favorites-dropdown__scale-value {
.favorites-dropdown__control-value {
font-size: 12px;
color: var(--el-text-color-secondary);
}
+52 -18
View File
@@ -21,10 +21,12 @@
<el-button :icon="MoreFilled" size="small" circle />
<template #dropdown>
<el-dropdown-menu class="favorites-dropdown">
<li class="favorites-dropdown__scale" @click.stop>
<div class="favorites-dropdown__scale-header">
<li class="favorites-dropdown__control" @click.stop>
<div class="favorites-dropdown__control-header">
<span>Scale</span>
<span class="favorites-dropdown__scale-value">{{ worldCardScalePercent }}%</span>
<span class="favorites-dropdown__control-value">
{{ worldCardScalePercent }}%
</span>
</div>
<el-slider
v-model="worldCardScale"
@@ -34,6 +36,21 @@
:step="worldCardScaleSlider.step"
:show-tooltip="false" />
</li>
<li class="favorites-dropdown__control" @click.stop>
<div class="favorites-dropdown__control-header">
<span>Spacing</span>
<span class="favorites-dropdown__control-value">
{{ worldCardSpacingPercent }}%
</span>
</div>
<el-slider
v-model="worldCardSpacing"
class="favorites-dropdown__slider"
:min="worldCardSpacingSlider.min"
:max="worldCardSpacingSlider.max"
:step="worldCardSpacingSlider.step"
:show-tooltip="false" />
</li>
<el-dropdown-item @click="handleWorldImportClick">
{{ t('view.favorite.import') }}
</el-dropdown-item>
@@ -439,17 +456,31 @@
const {
cardScale: worldCardScale,
cardSpacing: worldCardSpacing,
slider: worldCardScaleSlider,
spacingSlider: worldCardSpacingSlider,
containerRef: worldFavoritesContainerRef,
gridStyle: worldFavoritesGridStyle
} = useFavoritesCardScaling({
configKey: 'VRCX_FavoritesWorldCardScale',
spacingConfigKey: 'VRCX_FavoritesWorldCardSpacing',
min: 0.6,
max: 1,
step: 0.01
step: 0.01,
spacingMin: 0.5,
spacingMax: 1.5,
spacingStep: 0.05,
basePaddingY: 8,
basePaddingX: 10,
baseContentGap: 10,
baseActionGap: 8,
baseActionGroupGap: 6,
baseActionMargin: 8,
baseCheckboxMargin: 10
});
const worldCardScalePercent = computed(() => Math.round(worldCardScale.value * 100));
const worldCardSpacingPercent = computed(() => Math.round(worldCardSpacing.value * 100));
const worldGroupVisibilityOptions = ref(['public', 'friends', 'private']);
const worldSplitterSize = ref(260);
@@ -1324,7 +1355,6 @@
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
border: none;
background: transparent;
border-radius: 8px;
@@ -1333,7 +1363,8 @@
cursor: pointer;
color: inherit;
transition: background-color 0.15s ease;
height: 32px;
min-height: 32px;
align-self: stretch;
}
.favorites-group-menu__item:hover {
background-color: var(--el-menu-hover-bg-color);
@@ -1363,7 +1394,6 @@
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
border: none;
background: transparent;
padding: 6px 10px;
@@ -1372,7 +1402,8 @@
color: inherit;
font-size: 13px;
transition: background-color 0.15s ease;
height: 32px;
min-height: 32px;
align-self: stretch;
}
.group-visibility-menu__item:hover,
@@ -1488,7 +1519,7 @@
box-sizing: border-box;
border: 1px solid var(--el-border-color);
border-radius: calc(8px * var(--favorites-card-scale, 1));
padding: calc(8px * var(--favorites-card-scale, 1)) calc(10px * var(--favorites-card-scale, 1));
padding: var(--favorites-card-padding-y, 8px) var(--favorites-card-padding-x, 10px);
cursor: pointer;
background: var(--el-bg-color);
transition:
@@ -1513,7 +1544,7 @@
:deep(.favorites-search-card__content) {
display: flex;
align-items: center;
gap: calc(10px * var(--favorites-card-scale, 1));
gap: var(--favorites-card-content-gap, 10px);
flex: 1;
min-width: 0;
}
@@ -1583,8 +1614,8 @@
:deep(.favorites-search-card__actions) {
display: flex;
flex-direction: column;
gap: 8px;
margin-left: 8px;
gap: var(--favorites-card-action-gap, 8px);
margin-left: var(--favorites-card-action-margin, 8px);
align-items: center;
justify-content: center;
flex: 0 0 auto;
@@ -1603,7 +1634,7 @@
:deep(.favorites-search-card__action-group) {
display: flex;
gap: 6px;
gap: var(--favorites-card-action-group-gap, 6px);
width: 100%;
}
@@ -1614,7 +1645,7 @@
:deep(.favorites-search-card__action--checkbox) {
align-items: center;
justify-content: flex-end;
margin-right: 10px;
margin-right: var(--favorites-card-checkbox-margin, 10px);
}
:deep(.favorites-search-card__action--checkbox .el-checkbox) {
@@ -1634,15 +1665,18 @@
height: 100%;
}
.favorites-dropdown__scale {
.favorites-dropdown__control {
list-style: none;
padding: 12px 16px 8px;
border-bottom: 1px solid var(--el-border-color-lighter);
min-width: 220px;
cursor: default;
}
.favorites-dropdown__scale-header {
.favorites-dropdown__control:not(:last-child) {
border-bottom: 1px solid var(--el-border-color-lighter);
}
.favorites-dropdown__control-header {
display: flex;
align-items: center;
justify-content: space-between;
@@ -1652,7 +1686,7 @@
margin-bottom: 6px;
}
.favorites-dropdown__scale-value {
.favorites-dropdown__control-value {
font-size: 12px;
color: var(--el-text-color-secondary);
}
@@ -28,12 +28,33 @@ export function useFavoritesCardScaling(options = {}) {
max: options.max ?? 1,
step: options.step ?? 0.01
};
const spacingSlider = {
min: options.spacingMin ?? 0.5,
max: options.spacingMax ?? 1.5,
step: options.spacingStep ?? 0.05
};
const baseWidth = options.baseWidth ?? 260;
const baseGap = options.baseGap ?? 12;
const gapStep = options.gapStep ?? 8;
const configKey = options.configKey ?? '';
const spacingConfigKey = options.spacingConfigKey ?? '';
const basePaddingY = options.basePaddingY ?? 8;
const basePaddingX = options.basePaddingX ?? 10;
const baseContentGap = options.baseContentGap ?? 10;
const baseActionGap = options.baseActionGap ?? 8;
const baseActionGroupGap = options.baseActionGroupGap ?? 6;
const baseActionMargin = options.baseActionMargin ?? 8;
const baseCheckboxMargin = options.baseCheckboxMargin ?? 10;
const minGap = options.minGap ?? 4;
const minPadding = options.minPadding ?? 4;
const defaultSpacing = clamp(
options.defaultSpacing ?? 1,
spacingSlider.min,
spacingSlider.max
);
const cardScaleBase = ref(1);
const cardSpacingBase = ref(defaultSpacing);
const containerRef = ref(null);
const containerWidth = ref(0);
@@ -69,15 +90,59 @@ export function useFavoritesCardScaling(options = {}) {
const nextValue = clamp(Number(value) || 1, slider.min, slider.max);
cardScaleBase.value = nextValue;
if (configKey) {
configRepository.setString(configKey, nextValue);
configRepository.setString(configKey, String(nextValue));
}
}
});
const cardSpacing = computed({
get: () => cardSpacingBase.value,
set: (value) => {
const nextValue = clamp(
Number(value) || 1,
spacingSlider.min,
spacingSlider.max
);
cardSpacingBase.value = nextValue;
if (spacingConfigKey) {
configRepository.setString(spacingConfigKey, String(nextValue));
}
}
});
const gridStyle = computed(() => {
const minWidth = baseWidth * cardScale.value;
const rawGap = baseGap + (cardScale.value - 1) * gapStep;
const gap = Math.max(6, rawGap);
const spacing = cardSpacing.value;
const adjustedGapBase = baseGap + (cardScale.value - 1) * gapStep;
const gap = Math.max(minGap, adjustedGapBase * spacing);
const paddingY = Math.max(
minPadding,
basePaddingY * cardScale.value * spacing
);
const paddingX = Math.max(
minPadding,
basePaddingX * cardScale.value * spacing
);
const contentGap = Math.max(
minPadding,
baseContentGap * cardScale.value * spacing
);
const actionsGap = Math.max(
minPadding,
baseActionGap * cardScale.value * spacing
);
const actionsGroupGap = Math.max(
minPadding,
baseActionGroupGap * cardScale.value * spacing
);
const actionsMargin = Math.max(
0,
baseActionMargin * cardScale.value * spacing
);
const checkboxMargin = Math.max(
0,
baseCheckboxMargin * cardScale.value * spacing
);
return (count = 1, options = {}) => {
const width = Math.max(containerWidth.value ?? 0, 0);
@@ -100,9 +165,7 @@ export function useFavoritesCardScaling(options = {}) {
const columns = Math.max(1, Math.min(safeCount, requestedColumns));
const forceStretch = Boolean(options?.forceStretch);
const disableAutoStretch = Boolean(options?.disableAutoStretch);
const matchMaxColumnWidth = Boolean(
options?.matchMaxColumnWidth
);
const matchMaxColumnWidth = Boolean(options?.matchMaxColumnWidth);
const shouldStretch =
!disableAutoStretch &&
(forceStretch || itemCount >= maxColumns);
@@ -133,7 +196,15 @@ export function useFavoritesCardScaling(options = {}) {
'--favorites-card-gap': `${Math.round(gap)}px`,
'--favorites-card-target-width': `${Math.round(cardWidth)}px`,
'--favorites-grid-columns': `${columns}`,
'--favorites-card-scale': `${cardScale.value.toFixed(2)}`
'--favorites-card-scale': `${cardScale.value.toFixed(2)}`,
'--favorites-card-padding-y': `${Math.round(paddingY)}px`,
'--favorites-card-padding-x': `${Math.round(paddingX)}px`,
'--favorites-card-content-gap': `${Math.round(contentGap)}px`,
'--favorites-card-action-gap': `${Math.round(actionsGap)}px`,
'--favorites-card-action-group-gap': `${Math.round(actionsGroupGap)}px`,
'--favorites-card-action-margin': `${Math.round(actionsMargin)}px`,
'--favorites-card-checkbox-margin': `${Math.round(checkboxMargin)}px`,
'--favorites-card-spacing-scale': `${cardSpacing.value.toFixed(2)}`
};
};
});
@@ -184,34 +255,46 @@ export function useFavoritesCardScaling(options = {}) {
});
onBeforeMount(async () => {
if (!configKey) {
return;
}
try {
const storedScale = await configRepository.getString(
configKey,
'1'
);
const parsedValue = parseFloat(storedScale);
if (!Number.isNaN(parsedValue)) {
cardScaleBase.value = clamp(
parsedValue,
slider.min,
slider.max
if (configKey) {
const storedScale = await configRepository.getString(
configKey,
'1'
);
const parsedScale = parseFloat(storedScale);
if (!Number.isNaN(parsedScale)) {
cardScaleBase.value = clamp(
parsedScale,
slider.min,
slider.max
);
}
}
if (spacingConfigKey) {
const storedSpacing = await configRepository.getString(
spacingConfigKey,
String(defaultSpacing)
);
const parsedSpacing = parseFloat(storedSpacing);
if (!Number.isNaN(parsedSpacing)) {
cardSpacingBase.value = clamp(
parsedSpacing,
spacingSlider.min,
spacingSlider.max
);
}
}
} catch (error) {
console.error(
'Failed to load favorites card scale preference',
error
);
console.error('Failed to load favorites card preferences', error);
}
});
return {
cardScale,
cardSpacing,
slider,
spacingSlider,
containerRef,
gridStyle
};