Files
VRCX/src/views/Favorites/FavoritesFriend.vue
T
2025-11-10 02:19:41 +11:00

1041 lines
37 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="favorites-page x-container" v-loading="isFavoriteLoading">
<div class="favorites-toolbar">
<div>
<el-select v-model="sortFav" class="favorites-toolbar__select">
<template #prefix>
<i class="ri-sort-asc"></i>
</template>
<el-option :label="t('view.settings.appearance.appearance.sort_favorite_by_name')" :value="false" />
<el-option :label="t('view.settings.appearance.appearance.sort_favorite_by_date')" :value="true" />
</el-select>
</div>
<div class="favorites-toolbar__right">
<el-input
v-model="friendFavoriteSearch"
clearable
class="favorites-toolbar__search"
:placeholder="t('view.favorite.worlds.search')"
@input="searchFriendFavorites" />
<el-dropdown trigger="click" :hide-on-click="true">
<el-button :icon="MoreFilled" size="small" circle />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="showFriendImportDialog">
{{ t('view.favorite.import') }}
</el-dropdown-item>
<el-dropdown-item divided @click="showFriendExportDialog">
{{ t('view.favorite.export') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<el-splitter class="favorites-splitter">
<el-splitter-panel :size="260" :min="0" :max="360" collapsible>
<div class="favorites-groups-panel">
<div class="group-section">
<div class="group-section__header">
<span>{{ t('view.favorite.worlds.vrchat_favorites') }}</span>
<el-tooltip placement="bottom" :content="t('view.favorite.refresh_favorites_tooltip')">
<el-button
:loading="isFavoriteLoading"
size="small"
:icon="Refresh"
circle
@click.stop="handleRefreshFavorites" />
</el-tooltip>
</div>
<div class="group-section__list">
<template v-if="favoriteFriendGroups.length">
<div
v-for="group in favoriteFriendGroups"
:key="group.key"
:class="[
'group-item',
{ 'is-active': !hasSearchInput && isGroupActive('remote', group.key) }
]"
@click="handleGroupClick('remote', group.key)">
<div class="group-item__top">
<span class="group-item__name">{{ group.displayName }}</span>
<span class="group-item__count">{{ group.count }}/{{ group.capacity }}</span>
</div>
<div class="group-item__bottom">
<el-tag
size="small"
effect="plain"
:type="userFavoriteFriendsStatusForFavTab(group.visibility)">
{{ formatVisibility(group.visibility) }}
</el-tag>
<el-popover
:visible="activeGroupMenu === remoteGroupMenuKey(group.key)"
@update:visible="
handleGroupMenuVisible(remoteGroupMenuKey(group.key), $event)
"
placement="right"
trigger="click"
:hide-after="0"
:width="220"
popper-style="padding: 4px; border-radius: 8px;">
<template #reference>
<el-button
text
size="small"
:icon="MoreFilled"
circle
@click.stop></el-button>
</template>
<div class="favorites-group-menu">
<button
type="button"
class="favorites-group-menu__item"
@click="handleRemoteRename(group)">
<span>{{ t('view.favorite.rename_tooltip') }}</span>
</button>
<el-popover
popper-class="favorites-group-menu__popover"
placement="right"
trigger="hover"
:width="180"
popper-style="padding: 4px; border-radius: 8px;">
<div class="group-visibility-menu">
<button
v-for="visibility in friendGroupVisibilityOptions"
:key="visibility"
type="button"
:class="[
'group-visibility-menu__item',
{ 'is-active': group.visibility === visibility }
]"
@click="handleVisibilitySelection(group, visibility)">
<span>{{ formatVisibility(visibility) }}</span>
<span
v-if="group.visibility === visibility"
class="group-visibility-menu__check">
<i class="ri-check-line"></i>
</span>
</button>
</div>
<template #reference>
<button
type="button"
class="favorites-group-menu__item favorites-group-menu__item--submenu">
<span>{{ t('view.favorite.visibility_tooltip') }}</span>
<span class="favorites-group-menu__arrow"></span>
</button>
</template>
</el-popover>
<button
type="button"
class="favorites-group-menu__item favorites-group-menu__item--danger"
@click="handleRemoteClear(group)">
<span>{{ t('view.favorite.clear') }}</span>
</button>
</div>
</el-popover>
</div>
</div>
</template>
<div v-else class="group-empty">No Data</div>
</div>
</div>
</div>
</el-splitter-panel>
<el-splitter-panel>
<div class="favorites-content">
<div class="favorites-content__header">
<div class="favorites-content__title">
<span v-if="isSearchActive">{{ t('view.favorite.worlds.search') }}</span>
<template v-else-if="activeRemoteGroup">
<span>
{{ activeRemoteGroup.displayName }}
<small>{{ activeRemoteGroup.count }}/{{ activeRemoteGroup.capacity }}</small>
</span>
</template>
<span v-else>No Group Selected</span>
</div>
<div class="favorites-content__edit">
<span>{{ t('view.favorite.edit_mode') }}</span>
<el-switch
v-model="friendEditMode"
size="small"
:disabled="isSearchActive || !activeRemoteGroup"></el-switch>
</div>
</div>
<div class="favorites-content__edit-actions">
<div v-if="friendEditMode && !isSearchActive" class="favorites-content__actions">
<el-button size="small" :disabled="!hasFriendSelection" @click="clearSelectedFriends">
{{ t('view.favorite.clear') }}
</el-button>
<el-button size="small" :disabled="!hasFriendSelection" @click="copySelectedFriends">
{{ t('view.favorite.copy') }}
</el-button>
<el-button
size="small"
:disabled="!hasFriendSelection"
@click="showFriendBulkUnfavoriteSelectionConfirm">
{{ t('view.favorite.bulk_unfavorite') }}
</el-button>
</div>
</div>
<div class="favorites-content__list">
<template v-if="activeRemoteGroup && !isSearchActive">
<div class="favorites-content__scroll favorites-content__scroll--native">
<template v-if="currentFriendFavorites.length">
<div class="favorites-card-list">
<FavoritesFriendItem
v-for="favorite in currentFriendFavorites"
:key="favorite.id"
:favorite="favorite"
:group="activeRemoteGroup"
:selected="selectedFavoriteFriends.includes(favorite.id)"
:edit-mode="friendEditMode"
@toggle-select="toggleFriendSelection(favorite.id, $event)"
@click="showUserDialog(favorite.id)" />
</div>
</template>
<div v-else class="favorites-empty">No Data</div>
</div>
</template>
<template v-else-if="!isSearchActive">
<div class="favorites-empty">No Group Selected</div>
</template>
<template v-else>
<div class="favorites-content__scroll favorites-content__scroll--native">
<div v-if="friendFavoriteSearchResults.length" class="favorites-search-grid">
<div
v-for="favorite in friendFavoriteSearchResults"
:key="favorite.id"
class="favorites-search-card"
@click="showUserDialog(favorite.id)">
<div class="favorites-search-card__content">
<div class="favorites-search-card__avatar">
<img :src="userImage(favorite, true)" loading="lazy" />
</div>
<div class="favorites-search-card__detail">
<div class="favorites-search-card__title">
<span class="name">{{ favorite.displayName }}</span>
</div>
<div
v-if="favorite.location && favorite.location !== 'offline'"
class="favorites-search-card__location">
<Location
:location="favorite.location"
:traveling="favorite.travelingToLocation"
:link="false" />
</div>
<span v-else class="extra">{{ favorite.statusDescription }}</span>
</div>
</div>
</div>
</div>
<div v-else class="favorites-empty">No Data</div>
</div>
</template>
</div>
</div>
</el-splitter-panel>
</el-splitter>
<FriendExportDialog v-model:friendExportDialogVisible="friendExportDialogVisible" />
</div>
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { MoreFilled, Refresh } from '@element-plus/icons-vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useAppearanceSettingsStore, useFavoriteStore, useUserStore } from '../../stores';
import { favoriteRequest } from '../../api';
import { userImage } from '../../shared/utils';
import FavoritesFriendItem from './components/FavoritesFriendItem.vue';
import FriendExportDialog from './dialogs/FriendExportDialog.vue';
const friendGroupVisibilityOptions = ref(['public', 'friends', 'private']);
const { sortFavorites } = storeToRefs(useAppearanceSettingsStore());
const { setSortFavorites } = useAppearanceSettingsStore();
const favoriteStore = useFavoriteStore();
const {
favoriteFriends,
favoriteFriendGroups,
groupedByGroupKeyFavoriteFriends,
selectedFavoriteFriends,
friendImportDialogInput,
isFavoriteLoading
} = storeToRefs(favoriteStore);
const { showFriendImportDialog, refreshFavorites, getLocalWorldFavorites, handleFavoriteGroup } = favoriteStore;
const { showUserDialog } = useUserStore();
const { t } = useI18n();
const friendExportDialogVisible = ref(false);
const friendFavoriteSearch = ref('');
const friendFavoriteSearchResults = ref([]);
const friendEditMode = ref(false);
const selectedGroup = ref(null);
const activeGroupMenu = ref(null);
const sortFav = computed({
get() {
return sortFavorites.value;
},
set() {
setSortFavorites();
}
});
const hasFriendSelection = computed(() => selectedFavoriteFriends.value.length > 0);
const hasSearchInput = computed(() => friendFavoriteSearch.value.trim().length > 0);
const isSearchActive = computed(() => friendFavoriteSearch.value.trim().length >= 3);
const isRemoteGroupSelected = computed(() => selectedGroup.value?.type === 'remote');
const remoteGroupMenuKey = (key) => `remote:${key}`;
const searchableFriendEntries = computed(() => {
const seen = new Set();
const entries = [];
favoriteFriends.value.forEach((favorite) => {
if (!favorite?.ref || !favorite.id || seen.has(favorite.id)) {
return;
}
seen.add(favorite.id);
entries.push(favorite.ref);
});
return entries;
});
const activeRemoteGroup = computed(() => {
if (!isRemoteGroupSelected.value) {
return null;
}
return favoriteFriendGroups.value.find((group) => group.key === selectedGroup.value.key) || null;
});
const currentFriendFavorites = computed(() => {
if (!activeRemoteGroup.value) {
return [];
}
return groupedByGroupKeyFavoriteFriends.value[activeRemoteGroup.value.key] || [];
});
watch(
() => favoriteFriendGroups.value.map((group) => `${group.key}:${group.count}`),
() => {
ensureSelectedGroup();
},
{ immediate: true }
);
watch(isSearchActive, (active) => {
if (active && friendEditMode.value) {
friendEditMode.value = false;
}
});
watch(
() => friendEditMode.value,
(value) => {
if (!value) {
clearSelectedFriends();
}
}
);
function showFriendExportDialog() {
friendExportDialogVisible.value = true;
}
function handleRefreshFavorites() {
refreshFavorites();
getLocalWorldFavorites();
}
function handleGroupMenuVisible(key, visible) {
if (visible) {
activeGroupMenu.value = key;
return;
}
if (activeGroupMenu.value === key) {
activeGroupMenu.value = null;
}
}
function ensureSelectedGroup() {
if (selectedGroup.value && isGroupAvailable(selectedGroup.value)) {
return;
}
selectDefaultGroup();
}
function selectDefaultGroup() {
if (favoriteFriendGroups.value.length) {
const nextGroup =
favoriteFriendGroups.value.find((group) => group.count > 0) || favoriteFriendGroups.value[0];
if (nextGroup) {
selectGroup('remote', nextGroup.key);
return;
}
}
selectedGroup.value = null;
clearSelectedFriends();
}
function isGroupAvailable(group) {
if (!group) {
return false;
}
if (group.type === 'remote') {
return favoriteFriendGroups.value.some((item) => item.key === group.key);
}
return false;
}
function selectGroup(type, key) {
if (selectedGroup.value?.type === type && selectedGroup.value?.key === key) {
return;
}
selectedGroup.value = { type, key };
clearSelectedFriends();
}
function isGroupActive(type, key) {
return selectedGroup.value?.type === type && selectedGroup.value?.key === key;
}
function handleGroupClick(type, key) {
if (hasSearchInput.value) {
friendFavoriteSearch.value = '';
searchFriendFavorites('');
}
selectGroup(type, key);
}
function searchFriendFavorites(searchTerm) {
const search = searchTerm.trim().toLowerCase();
if (search.length < 3) {
friendFavoriteSearchResults.value = [];
return;
}
const filtered = searchableFriendEntries.value.filter((ref) => {
if (!ref || typeof ref.id === 'undefined' || typeof ref.displayName === 'undefined') {
return false;
}
const username = ref.username || '';
return ref.displayName.toLowerCase().includes(search) || username.toLowerCase().includes(search);
});
friendFavoriteSearchResults.value = filtered;
}
function toggleFriendSelection(id, value) {
if (value) {
if (!selectedFavoriteFriends.value.includes(id)) {
selectedFavoriteFriends.value.push(id);
}
} else {
selectedFavoriteFriends.value = selectedFavoriteFriends.value.filter((selectedId) => selectedId !== id);
}
}
function clearSelectedFriends() {
selectedFavoriteFriends.value = [];
}
function copySelectedFriends() {
if (!selectedFavoriteFriends.value.length) {
return;
}
const idList = selectedFavoriteFriends.value.map((id) => `${id}\n`).join('');
friendImportDialogInput.value = idList;
showFriendImportDialog();
}
function showFriendBulkUnfavoriteSelectionConfirm() {
if (!selectedFavoriteFriends.value.length) {
return;
}
const total = selectedFavoriteFriends.value.length;
ElMessageBox.confirm(
`Are you sure you want to unfavorite ${total} favorites?\n This action cannot be undone.`,
`Delete ${total} favorites?`,
{
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
}
)
.then((action) => {
if (action === 'confirm') {
bulkUnfavoriteSelectedFriends([...selectedFavoriteFriends.value]);
}
})
.catch(() => {});
}
function bulkUnfavoriteSelectedFriends(ids) {
ids.forEach((id) => {
favoriteRequest.deleteFavorite({
objectId: id
});
});
selectedFavoriteFriends.value = [];
friendEditMode.value = false;
}
function clearFavoriteGroup(ctx) {
ElMessageBox.confirm('Continue? Clear Group', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
favoriteRequest.clearFavoriteGroup({
type: ctx.type,
group: ctx.name
});
}
})
.catch(() => {});
}
function handleVisibilitySelection(group, visibility) {
const menuKey = remoteGroupMenuKey(group.key);
changeFriendGroupVisibility(group.name, visibility, menuKey);
}
function handleRemoteRename(group) {
handleGroupMenuVisible(remoteGroupMenuKey(group.key), false);
changeFavoriteGroupName(group);
}
function handleRemoteClear(group) {
handleGroupMenuVisible(remoteGroupMenuKey(group.key), false);
clearFavoriteGroup(group);
}
function changeFavoriteGroupName(group) {
const currentName = group.displayName || group.name;
ElMessageBox.prompt(
t('prompt.change_favorite_group_name.description'),
t('prompt.change_favorite_group_name.header'),
{
confirmButtonText: t('prompt.change_favorite_group_name.change'),
cancelButtonText: t('prompt.change_favorite_group_name.cancel'),
inputPlaceholder: t('prompt.change_favorite_group_name.input_placeholder'),
inputPattern: /\S+/,
inputValue: currentName,
inputErrorMessage: t('prompt.change_favorite_group_name.input_error')
}
)
.then(({ value }) => {
const newName = value.trim();
if (!newName || newName === currentName) {
return;
}
favoriteRequest
.saveFavoriteGroup({
type: 'friend',
group: group.name,
displayName: newName
})
.then((args) => {
handleFavoriteGroup({
json: args.json,
params: {
favoriteGroupId: args.json.id
}
});
ElMessage({
message: t('prompt.change_favorite_group_name.message.success'),
type: 'success'
});
refreshFavorites();
});
})
.catch(() => {});
}
function changeFriendGroupVisibility(name, visibility, menuKey = null) {
const params = {
type: 'friend',
group: name,
visibility
};
favoriteRequest.saveFavoriteGroup(params).then((args) => {
handleFavoriteGroup({
json: args.json,
params: {
favoriteGroupId: args.json.id
}
});
ElMessage({
message: 'Group visibility changed',
type: 'success'
});
if (menuKey) {
handleGroupMenuVisible(menuKey, false);
}
refreshFavorites();
return args;
});
}
function userFavoriteFriendsStatusForFavTab(visibility) {
if (visibility === 'public') {
return 'primary';
}
if (visibility === 'friends') {
return 'success';
}
return 'info';
}
function formatVisibility(value) {
if (!value) {
return '';
}
return value.charAt(0).toUpperCase() + value.slice(1);
}
</script>
<style scoped>
.favorites-page {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
padding-bottom: 0;
}
.favorites-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.favorites-toolbar__select {
min-width: 200px;
}
.favorites-toolbar__right {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.favorites-toolbar__search {
flex: 1;
}
.favorites-splitter {
flex: 1;
min-height: 0;
}
.favorites-groups-panel {
height: 100%;
padding-right: 8px;
overflow: auto;
display: flex;
flex-direction: column;
gap: 12px;
}
.group-section {
display: flex;
flex-direction: column;
gap: 8px;
}
.group-section__header {
display: flex;
align-items: center;
justify-content: space-between;
font-weight: 600;
font-size: 14px;
}
.group-section__list {
display: flex;
flex-direction: column;
gap: 8px;
}
.group-item {
border: 1px solid var(--el-border-color);
border-radius: 8px;
padding: 8px;
cursor: pointer;
box-shadow: 0 0 6px rgba(15, 23, 42, 0.04);
transition:
box-shadow 0.2s ease,
transform 0.2s ease;
}
.group-item:hover {
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.07);
transform: translateY(-2px);
}
.group-item__top {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 4px;
font-size: 13px;
}
.group-item__name {
font-weight: 600;
}
.group-item__count {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.group-item__bottom {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.group-item.is-active {
border-color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.12));
}
.group-empty {
text-align: center;
color: var(--el-text-color-secondary);
font-size: 12px;
padding: 12px 0;
}
.favorites-group-menu {
display: flex;
flex-direction: column;
gap: 4px;
}
.favorites-group-menu__item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
border: none;
background: transparent;
border-radius: 8px;
padding: 6px 12px;
font-size: 13px;
cursor: pointer;
color: inherit;
transition: background-color 0.15s ease;
height: 32px;
}
.favorites-group-menu__item:hover {
background-color: var(--el-menu-hover-bg-color);
}
.favorites-group-menu__item--danger {
color: var(--el-color-danger);
}
.favorites-group-menu__item--submenu {
padding-right: 8px;
}
.favorites-group-menu__arrow {
margin-left: auto;
color: var(--el-text-color-secondary);
font-size: 12px;
}
.group-visibility-menu {
display: flex;
flex-direction: column;
gap: 4px;
}
.group-visibility-menu__item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
border: none;
background: transparent;
padding: 6px 10px;
border-radius: 8px;
cursor: pointer;
color: inherit;
font-size: 13px;
transition: background-color 0.15s ease;
height: 32px;
}
.group-visibility-menu__item:hover,
.group-visibility-menu__item.is-active {
background-color: var(--el-menu-hover-bg-color);
}
.group-visibility-menu__check {
font-size: 12px;
color: var(--el-color-primary);
}
.favorites-content {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
padding-left: 26px;
}
.favorites-content__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.favorites-content__title {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 16px;
font-weight: 600;
padding-left: 2px;
}
.favorites-content__title small {
font-size: 12px;
font-weight: normal;
color: var(--el-text-color-secondary);
}
.favorites-content__edit {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--el-text-color-regular);
}
.favorites-content__edit-actions {
display: flex;
align-items: center;
justify-content: flex-end;
}
.favorites-content__actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.favorites-content__list {
flex: 1;
min-height: 0;
}
.favorites-content__scroll {
height: 100%;
padding-right: 8px;
}
.favorites-content__scroll--native {
overflow: auto;
}
.favorites-search-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 12px;
padding-bottom: 12px;
}
.favorites-card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 12px;
padding: 4px 2px 12px 2px;
}
.favorites-card-list::after {
content: '';
}
:deep(.favorites-search-card--friend) {
flex: 0 1 280px;
min-width: 240px;
}
:deep(.favorites-search-card) {
display: flex;
align-items: center;
border: 1px solid var(--el-border-color);
border-radius: 8px;
padding: 8px 10px;
cursor: pointer;
background: var(--el-bg-color);
transition:
border-color 0.2s ease,
box-shadow 0.2s ease;
box-shadow: 0 0 6px rgba(15, 23, 42, 0.04);
}
:deep(.favorites-search-card:hover) {
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.07);
transform: translateY(-2px);
}
:deep(.favorites-search-card.is-selected) {
border-color: var(--el-color-primary);
box-shadow: 0 0 0 1px var(--el-color-primary-light-3, rgba(64, 158, 255, 0.4));
}
:deep(.favorites-search-card__content) {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
:deep(.favorites-search-card__avatar) {
width: 48px;
height: 48px;
border-radius: 6px;
overflow: hidden;
background: var(--el-fill-color-lighter);
flex-shrink: 0;
}
:deep(.favorites-search-card__avatar img) {
width: 100%;
height: 100%;
object-fit: cover;
}
:deep(.favorites-search-card__avatar.is-empty) {
background: repeating-linear-gradient(
-45deg,
rgba(148, 163, 184, 0.25),
rgba(148, 163, 184, 0.25) 10px,
rgba(255, 255, 255, 0.35) 10px,
rgba(255, 255, 255, 0.35) 20px
);
}
:deep(.favorites-search-card__detail) {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 13px;
min-width: 0;
}
:deep(.favorites-search-card__detail .name) {
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.favorites-search-card__detail .extra) {
font-size: 12px;
color: var(--el-text-color-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.favorites-search-card__title) {
display: flex;
align-items: center;
gap: 6px;
}
:deep(.favorites-search-card__badges) {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--el-text-color-secondary);
font-size: 14px;
}
:deep(.favorites-search-card__actions) {
display: flex;
flex-direction: column;
gap: 8px;
margin-left: 8px;
align-items: center;
justify-content: center;
flex: 0 0 auto;
min-width: 48px;
}
:deep(.favorites-search-card__action) {
display: flex;
justify-content: flex-end;
width: 100%;
}
:deep(.favorites-search-card__dropdown) {
width: 100%;
}
:deep(.favorites-search-card__action-group) {
display: flex;
gap: 6px;
width: 100%;
}
:deep(.favorites-search-card__action-group .favorites-search-card__action--full) {
flex: 1;
}
:deep(.favorites-search-card__action--checkbox) {
align-items: center;
justify-content: flex-end;
margin-right: 10px;
}
:deep(.favorites-search-card__action--checkbox .el-checkbox) {
margin: 0;
}
:deep(.favorites-search-card__actions:empty) {
display: none;
}
:deep(.favorites-search-card__location) {
font-size: 12px;
color: var(--el-text-color-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.favorites-empty {
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
font-size: 13px;
height: 100%;
}
</style>