mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-24 01:03:50 +02:00
replace some element plus components
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<HoverCardTrigger as-child>
|
||||
<Info style="margin-left: 5px; font-size: 12px; opacity: 0.7" />
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent side="bottom" align="start" class="w-[300px]">
|
||||
<HoverCardContent side="bottom" align="start" class="w-75">
|
||||
<div class="tips-popover">
|
||||
<div>{{ t('view.charts.instance_activity.tips.online_time') }}</div>
|
||||
<div>{{ t('view.charts.instance_activity.tips.click_Y_axis') }}</div>
|
||||
@@ -98,13 +98,28 @@
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
</ButtonGroup>
|
||||
<el-date-picker
|
||||
v-model="selectedDate"
|
||||
type="date"
|
||||
:clearable="false"
|
||||
:default-value="dayjs().toDate()"
|
||||
:disabled-date="getDatePickerDisabledDate"
|
||||
@change="reloadData"></el-date-picker>
|
||||
<Popover v-model:open="isDatePickerOpen">
|
||||
<PopoverTrigger asChild>
|
||||
<div>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="w-50 justify-start text-left font-normal"
|
||||
:disabled="isLoading">
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
{{ dayjs(selectedDate).format('YYYY-MM-DD') }}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0" align="end">
|
||||
<Calendar
|
||||
:model-value="calendarModelValue"
|
||||
:default-placeholder="defaultCalendarPlaceholder"
|
||||
:is-date-disabled="isCalendarDateDisabled"
|
||||
:prevent-deselect="true"
|
||||
initial-focus
|
||||
@update:modelValue="handleCalendarModelUpdate" />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-online">
|
||||
@@ -125,7 +140,11 @@
|
||||
|
||||
<transition name="el-fade-in-linear">
|
||||
<div v-show="isDetailVisible && !isLoading && activityData.length !== 0" class="divider">
|
||||
<el-divider>·</el-divider>
|
||||
<div class="flex items-center">
|
||||
<Separator class="flex-1" />
|
||||
<span class="px-2 text-muted-foreground">·</span>
|
||||
<Separator class="flex-1" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<template v-if="isDetailVisible && activityData.length !== 0">
|
||||
@@ -141,11 +160,15 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, onBeforeMount, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { AlertTriangle, ArrowLeft, ArrowRight, Info, RefreshCcw, Settings } from 'lucide-vue-next';
|
||||
import { ArrowLeft, ArrowRight, Calendar as CalendarIcon, Info, RefreshCcw, Settings } from 'lucide-vue-next';
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
||||
import { fromDate, getLocalTimeZone, today } from '@internationalized/date';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ButtonGroup } from '@/components/ui/button-group';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toDate } from 'reka-ui/date';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
@@ -258,6 +281,30 @@
|
||||
getDatePickerDisabledDate
|
||||
} = useDateNavigation(allDateOfActivity, () => reloadData());
|
||||
|
||||
const isDatePickerOpen = ref(false);
|
||||
const calendarTimeZone = getLocalTimeZone();
|
||||
const defaultCalendarPlaceholder = today(calendarTimeZone);
|
||||
|
||||
const calendarModelValue = computed(() => {
|
||||
// Keep the rest of the feature using JS Date; adapt to Calendar's DateValue model.
|
||||
return fromDate(selectedDate.value ?? new Date(), calendarTimeZone);
|
||||
});
|
||||
|
||||
function isCalendarDateDisabled(dateValue) {
|
||||
try {
|
||||
return getDatePickerDisabledDate(toDate(dateValue, calendarTimeZone));
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCalendarModelUpdate(dateValue) {
|
||||
if (!dateValue) return;
|
||||
selectedDate.value = toDate(dateValue, calendarTimeZone);
|
||||
isDatePickerOpen.value = false;
|
||||
reloadData();
|
||||
}
|
||||
|
||||
const activityChartRef = ref(null);
|
||||
const activityDetailChartRef = ref(null);
|
||||
|
||||
@@ -702,11 +749,6 @@
|
||||
.el-date-editor.el-input__inner {
|
||||
width: 200px;
|
||||
}
|
||||
.el-divider__text {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
<Card class="friend-card p-0 gap-0" :style="cardStyle" @click="showUserDialog(friend.id)">
|
||||
<div class="friend-card__header">
|
||||
<div class="friend-card__avatar-wrapper">
|
||||
<el-avatar :size="avatarSize" :src="userImage(props.friend.ref, true)" class="friend-card__avatar">
|
||||
{{ avatarFallback }}
|
||||
</el-avatar>
|
||||
<Avatar class="friend-card__avatar" :style="{ width: `${avatarSize}px`, height: `${avatarSize}px` }">
|
||||
<AvatarImage :src="userImage(props.friend.ref, true)" />
|
||||
<AvatarFallback>{{ avatarFallback }}</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<span class="friend-card__status-dot" :class="statusDotClass"></span>
|
||||
<div class="friend-card__name ml-0.5" :title="friend.name">{{ friend.name }}</div>
|
||||
@@ -26,6 +27,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Pencil } from 'lucide-vue-next';
|
||||
import { computed } from 'vue';
|
||||
|
||||
@@ -199,7 +199,7 @@
|
||||
}}</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<Separator orientation="vertical" class="mx-2 h-5" />
|
||||
<RadioGroup
|
||||
:model-value="searchAvatarFilterRemote"
|
||||
class="flex items-center gap-4"
|
||||
@@ -361,6 +361,7 @@
|
||||
import { ButtonGroup } from '@/components/ui/button-group';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { InputGroupField } from '@/components/ui/input-group';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -45,23 +45,22 @@
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<el-skeleton v-else animated class="skeleton" :throttle="100">
|
||||
<template #template>
|
||||
<div v-else class="skeleton" aria-busy="true" aria-label="Loading">
|
||||
<div>
|
||||
<Skeleton class="h-10 w-10 rounded-full" />
|
||||
<div>
|
||||
<el-skeleton-item variant="circle" />
|
||||
<div>
|
||||
<el-skeleton-item variant="text" />
|
||||
<el-skeleton-item variant="text" />
|
||||
</div>
|
||||
<Skeleton class="h-3.5 w-1/2" />
|
||||
<Skeleton class="mt-1.5 h-3 w-full" />
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { AlertTriangle, Loader2, Trash2 } from 'lucide-vue-next';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -108,19 +107,5 @@
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
.el-skeleton__circle {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
.el-skeleton__text {
|
||||
&:first-child {
|
||||
height: 14px;
|
||||
margin-bottom: 6px;
|
||||
width: 50%;
|
||||
}
|
||||
&:last-child {
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
220
src/views/Tools/components/GroupCalendarMonth.vue
Normal file
220
src/views/Tools/components/GroupCalendarMonth.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<script setup>
|
||||
import {
|
||||
CalendarCell,
|
||||
CalendarCellTrigger,
|
||||
CalendarGrid,
|
||||
CalendarGridBody,
|
||||
CalendarGridHead,
|
||||
CalendarGridRow,
|
||||
CalendarHeadCell,
|
||||
CalendarHeader,
|
||||
CalendarHeading,
|
||||
CalendarNextButton,
|
||||
CalendarPrevButton
|
||||
} from '@/components/ui/calendar';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { fromDate, getLocalTimeZone } from '@internationalized/date';
|
||||
import { CalendarRoot } from 'reka-ui';
|
||||
import { toDate } from 'reka-ui/date';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Date,
|
||||
required: true
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
eventsByDate: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
followingByDate: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const timeZone = getLocalTimeZone();
|
||||
|
||||
// JSDoc casts: this project can end up with nominal-type mismatches for DateValue
|
||||
// due to duplicate @internationalized/date copies in tooling.
|
||||
/** @type {import('vue').Ref<any>} */
|
||||
const internalValue = ref(fromDate(props.modelValue ?? new Date(), timeZone));
|
||||
/** @type {import('vue').Ref<any>} */
|
||||
const placeholder = ref(fromDate(props.modelValue ?? new Date(), timeZone));
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(next) => {
|
||||
if (!next) return;
|
||||
internalValue.value = fromDate(next, timeZone);
|
||||
placeholder.value = fromDate(next, timeZone);
|
||||
}
|
||||
);
|
||||
|
||||
const selectedDayKey = computed(() => dayjs(props.modelValue).format('YYYY-MM-DD'));
|
||||
|
||||
function toKey(dateValue) {
|
||||
return dayjs(toDate(dateValue, timeZone)).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
function eventCountFor(dateValue) {
|
||||
const key = toKey(dateValue);
|
||||
return props.eventsByDate?.[key]?.length ?? 0;
|
||||
}
|
||||
|
||||
function hasFollowingFor(dateValue) {
|
||||
const key = toKey(dateValue);
|
||||
return Boolean(props.followingByDate?.[key]);
|
||||
}
|
||||
|
||||
function onUpdateModelValue(next) {
|
||||
if (!next) return;
|
||||
internalValue.value = next;
|
||||
const asDate = toDate(next, timeZone);
|
||||
placeholder.value = next;
|
||||
emit('update:modelValue', asDate);
|
||||
}
|
||||
|
||||
function onUpdatePlaceholder(next) {
|
||||
if (!next) return;
|
||||
placeholder.value = next;
|
||||
internalValue.value = next;
|
||||
emit('update:modelValue', toDate(next, timeZone));
|
||||
}
|
||||
|
||||
function dayLabel(dateValue) {
|
||||
return dayjs(toDate(dateValue, timeZone)).format('D');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="props.isLoading" class="group-calendar-month">
|
||||
<CalendarRoot
|
||||
v-slot="{ grid, weekDays }"
|
||||
:model-value="internalValue"
|
||||
@update:modelValue="onUpdateModelValue"
|
||||
:placeholder="placeholder"
|
||||
@update:placeholder="onUpdatePlaceholder"
|
||||
:prevent-deselect="true"
|
||||
class="p-4">
|
||||
<CalendarHeader class="pt-0">
|
||||
<nav class="flex items-center gap-1 absolute top-0 inset-x-0 justify-between">
|
||||
<CalendarPrevButton class="size-9" />
|
||||
<CalendarNextButton class="size-9" />
|
||||
</nav>
|
||||
<div class="flex items-center justify-center">
|
||||
<CalendarHeading class="text-base" />
|
||||
</div>
|
||||
</CalendarHeader>
|
||||
|
||||
<div class="flex flex-col gap-y-4 mt-6">
|
||||
<CalendarGrid v-for="month in grid" :key="month.value.toString()" class="w-full">
|
||||
<CalendarGridHead>
|
||||
<CalendarGridRow>
|
||||
<CalendarHeadCell v-for="day in weekDays" :key="day" class="text-sm">
|
||||
{{ day }}
|
||||
</CalendarHeadCell>
|
||||
</CalendarGridRow>
|
||||
</CalendarGridHead>
|
||||
<CalendarGridBody>
|
||||
<CalendarGridRow
|
||||
v-for="(weekDates, index) in month.rows"
|
||||
:key="`weekDate-${index}`"
|
||||
class="mt-2 w-full">
|
||||
<CalendarCell v-for="weekDate in weekDates" :key="weekDate.toString()" :date="weekDate">
|
||||
<CalendarCellTrigger
|
||||
:day="weekDate"
|
||||
:month="month.value"
|
||||
class="size-12 cursor-pointer">
|
||||
<div class="date">
|
||||
<div
|
||||
class="calendar-date-content"
|
||||
:class="{
|
||||
'has-events': eventCountFor(weekDate) > 0,
|
||||
'is-selected': toKey(weekDate) === selectedDayKey
|
||||
}">
|
||||
{{ dayLabel(weekDate) }}
|
||||
<div
|
||||
v-if="eventCountFor(weekDate) > 0"
|
||||
class="calendar-event-badge"
|
||||
:class="hasFollowingFor(weekDate) ? 'has-following' : 'no-following'">
|
||||
{{ eventCountFor(weekDate) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CalendarCellTrigger>
|
||||
</CalendarCell>
|
||||
</CalendarGridRow>
|
||||
</CalendarGridBody>
|
||||
</CalendarGrid>
|
||||
</div>
|
||||
</CalendarRoot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.group-calendar-month {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.date {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.calendar-date-content {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
font-size: 18px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.calendar-date-content.has-events {
|
||||
background-color: var(--group-calendar-event-bg, color-mix(in oklch, var(--el-color-primary) 10%, transparent));
|
||||
}
|
||||
|
||||
.calendar-event-badge {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 9px;
|
||||
color: var(--el-color-white, #fff);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
box-shadow: var(--el-box-shadow-lighter);
|
||||
z-index: 10;
|
||||
padding: 0 5px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.calendar-event-badge.has-following {
|
||||
background-color: var(--group-calendar-badge-following, var(--el-color-success));
|
||||
}
|
||||
|
||||
.calendar-event-badge.no-following {
|
||||
background-color: var(--group-calendar-badge-normal, var(--el-color-primary));
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Dialog :open="visible" @update:open="(open) => (open ? null : closeDialog())">
|
||||
<DialogContent class="x-dialog w-[90vw] max-w-[90vw] h-[80vh] overflow-hidden">
|
||||
<DialogContent class="x-dialog w-[90vw] max-w-[90vw] sm:max-w-[70vw] h-[60vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<div class="dialog-title-container">
|
||||
<DialogTitle>{{ t('dialog.group_calendar.header') }}</DialogTitle>
|
||||
@@ -46,29 +46,11 @@
|
||||
</div>
|
||||
|
||||
<div class="calendar-container">
|
||||
<el-calendar v-model="selectedDay" v-loading="isLoading">
|
||||
<template #date-cell="{ data }">
|
||||
<div class="date">
|
||||
<div
|
||||
class="calendar-date-content"
|
||||
:class="{
|
||||
'has-events': filteredCalendar[formatDateKey(data.date)]?.length
|
||||
}">
|
||||
{{ dayjs(data.date).format('D') }}
|
||||
<div
|
||||
v-if="filteredCalendar[formatDateKey(data.date)]?.length"
|
||||
class="calendar-event-badge"
|
||||
:class="
|
||||
followingCalendarDate[formatDateKey(data.date)]
|
||||
? 'has-following'
|
||||
: 'no-following'
|
||||
">
|
||||
{{ filteredCalendar[formatDateKey(data.date)]?.length }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-calendar>
|
||||
<GroupCalendarMonth
|
||||
v-model="selectedDay"
|
||||
:is-loading="isLoading"
|
||||
:events-by-date="filteredCalendar"
|
||||
:following-by-date="followingCalendarDate" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else key="grid" class="grid-view">
|
||||
@@ -134,6 +116,7 @@
|
||||
import { useGroupStore } from '../../../stores';
|
||||
|
||||
import GroupCalendarEventCard from '../components/GroupCalendarEventCard.vue';
|
||||
import GroupCalendarMonth from '../components/GroupCalendarMonth.vue';
|
||||
import configRepository from '../../../service/config';
|
||||
|
||||
const { applyGroupEvent, showGroupDialog } = useGroupStore();
|
||||
|
||||
Reference in New Issue
Block a user