mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-21 15:53:50 +02:00
324 lines
12 KiB
Vue
324 lines
12 KiB
Vue
<template>
|
|
<div class="x-container" ref="notificationsRef">
|
|
<DataTableLayout
|
|
:table="table"
|
|
:loading="isNotificationsLoading"
|
|
:table-style="tableHeightStyle"
|
|
:page-sizes="pageSizes"
|
|
:total-items="totalItems"
|
|
:on-page-size-change="handlePageSizeChange">
|
|
<template #toolbar>
|
|
<div class="mb-2 flex items-center">
|
|
<Select
|
|
multiple
|
|
:model-value="
|
|
Array.isArray(notificationTable.filters?.[0]?.value)
|
|
? notificationTable.filters[0].value
|
|
: []
|
|
"
|
|
@update:modelValue="handleNotificationFilterChange">
|
|
<SelectTrigger class="w-full" style="flex: 1">
|
|
<SelectValue :placeholder="t('view.notification.filter_placeholder')" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
<SelectItem
|
|
v-for="type in [
|
|
'requestInvite',
|
|
'invite',
|
|
'requestInviteResponse',
|
|
'inviteResponse',
|
|
'friendRequest',
|
|
'ignoredFriendRequest',
|
|
'message',
|
|
'boop',
|
|
'event.announcement',
|
|
'groupChange',
|
|
'group.announcement',
|
|
'group.informative',
|
|
'group.invite',
|
|
'group.joinRequest',
|
|
'group.transfer',
|
|
'group.queueReady',
|
|
'moderation.warning.group',
|
|
'moderation.report.closed',
|
|
'moderation.contentrestriction',
|
|
'instance.closed',
|
|
'economy.alert'
|
|
]"
|
|
:key="type"
|
|
:value="type">
|
|
{{ t('view.notification.filters.' + type) }}
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
<InputGroupField
|
|
v-model="notificationTable.filters[1].value"
|
|
:placeholder="t('view.notification.search_placeholder')"
|
|
clearable
|
|
class="flex-[0.4] my-0 mx-2" />
|
|
<TooltipWrapper side="bottom" :content="t('view.notification.refresh_tooltip')">
|
|
<Button
|
|
class="rounded-full"
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
:disabled="isNotificationsLoading"
|
|
style="flex: none"
|
|
@click="refreshNotifications()">
|
|
<Spinner v-if="isNotificationsLoading" />
|
|
<RefreshCw v-else />
|
|
</Button>
|
|
</TooltipWrapper>
|
|
</div>
|
|
</template>
|
|
</DataTableLayout>
|
|
<SendInviteResponseDialog
|
|
v-model:send-invite-response-dialog="sendInviteResponseDialog"
|
|
v-model:sendInviteResponseDialogVisible="sendInviteResponseDialogVisible" />
|
|
<SendInviteRequestResponseDialog
|
|
v-model:send-invite-response-dialog="sendInviteResponseDialog"
|
|
v-model:sendInviteRequestResponseDialogVisible="sendInviteRequestResponseDialogVisible" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { computed, ref, watch } from 'vue';
|
|
import { Button } from '@/components/ui/button';
|
|
import { InputGroupField } from '@/components/ui/input-group';
|
|
import { RefreshCw } from 'lucide-vue-next';
|
|
import { Spinner } from '@/components/ui/spinner';
|
|
import { storeToRefs } from 'pinia';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
import {
|
|
useAppearanceSettingsStore,
|
|
useGalleryStore,
|
|
useInviteStore,
|
|
useNotificationStore,
|
|
useVrcxStore
|
|
} from '../../stores';
|
|
import { DataTableLayout } from '../../components/ui/data-table';
|
|
import { convertFileUrlToImageUrl } from '../../shared/utils';
|
|
import { createColumns } from './columns.jsx';
|
|
import { useDataTableScrollHeight } from '../../composables/useDataTableScrollHeight';
|
|
import { useVrcxVueTable } from '../../lib/table/useVrcxVueTable';
|
|
|
|
import SendInviteRequestResponseDialog from './dialogs/SendInviteRequestResponseDialog.vue';
|
|
import SendInviteResponseDialog from './dialogs/SendInviteResponseDialog.vue';
|
|
import configRepository from '../../services/config';
|
|
|
|
const { refreshInviteMessageTableData } = useInviteStore();
|
|
const { clearInviteImageUpload } = useGalleryStore();
|
|
const { notificationTable, isNotificationsLoading } = storeToRefs(useNotificationStore());
|
|
const {
|
|
refreshNotifications,
|
|
acceptFriendRequestNotification,
|
|
hideNotification,
|
|
hideNotificationPrompt,
|
|
acceptRequestInvite,
|
|
sendNotificationResponse,
|
|
deleteNotificationLog,
|
|
deleteNotificationLogPrompt,
|
|
openNotificationLink
|
|
} = useNotificationStore();
|
|
const { showFullscreenImageDialog } = useGalleryStore();
|
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
|
const vrcxStore = useVrcxStore();
|
|
|
|
const { t } = useI18n();
|
|
|
|
const notificationsRef = ref(null);
|
|
const { tableStyle: tableHeightStyle } = useDataTableScrollHeight(notificationsRef, {
|
|
offset: 30,
|
|
toolbarHeight: 54,
|
|
paginationHeight: 52
|
|
});
|
|
|
|
/**
|
|
*
|
|
* @param row
|
|
*/
|
|
function getNotificationCreatedAt(row) {
|
|
if (typeof row?.created_at === 'string' && row.created_at.length > 0) {
|
|
return row.created_at;
|
|
}
|
|
if (typeof row?.createdAt === 'string' && row.createdAt.length > 0) {
|
|
return row.createdAt;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param row
|
|
*/
|
|
function getNotificationCreatedAtTs(row) {
|
|
const createdAtRaw = row?.created_at ?? row?.createdAt;
|
|
if (typeof createdAtRaw === 'number') {
|
|
const ts = createdAtRaw > 1_000_000_000_000 ? createdAtRaw : createdAtRaw * 1000;
|
|
return Number.isFinite(ts) ? ts : 0;
|
|
}
|
|
|
|
const createdAt = getNotificationCreatedAt(row);
|
|
const ts = dayjs(createdAt).valueOf();
|
|
return Number.isFinite(ts) ? ts : 0;
|
|
}
|
|
|
|
const asRawArray = (value) => (Array.isArray(value) ? value : []);
|
|
const isEmptyFilterValue = (value) => (Array.isArray(value) ? value.length === 0 : !value);
|
|
const applyFilter = (row, filter) => {
|
|
if (Array.isArray(filter.prop)) {
|
|
return filter.prop.some((propItem) => applyFilter(row, { prop: propItem, value: filter.value }));
|
|
}
|
|
|
|
const cellValue = row[filter.prop];
|
|
if (cellValue === undefined || cellValue === null) {
|
|
return false;
|
|
}
|
|
|
|
if (Array.isArray(filter.value)) {
|
|
return filter.value.some((val) => String(cellValue).toLowerCase() === String(val).toLowerCase());
|
|
}
|
|
return String(cellValue).toLowerCase().includes(String(filter.value).toLowerCase());
|
|
};
|
|
|
|
const notificationDisplayData = computed(() => {
|
|
const rawData = asRawArray(notificationTable.value.data);
|
|
const rawFilters = Array.isArray(notificationTable.value.filters) ? notificationTable.value.filters : [];
|
|
const activeFilters = rawFilters.filter((filter) => !isEmptyFilterValue(filter?.value));
|
|
|
|
if (activeFilters.length === 0) {
|
|
return rawData.slice();
|
|
}
|
|
|
|
return rawData.filter((row) => {
|
|
for (const filter of activeFilters) {
|
|
if (filter.filterFn) {
|
|
if (!filter.filterFn(row, filter)) {
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
if (!applyFilter(row, filter)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
});
|
|
|
|
const columns = createColumns({
|
|
getNotificationCreatedAt,
|
|
getNotificationCreatedAtTs,
|
|
openNotificationLink,
|
|
showFullscreenImageDialog,
|
|
getSmallThumbnailUrl,
|
|
acceptFriendRequestNotification,
|
|
showSendInviteResponseDialog,
|
|
showSendInviteRequestResponseDialog,
|
|
acceptRequestInvite,
|
|
sendNotificationResponse,
|
|
hideNotification,
|
|
hideNotificationPrompt,
|
|
deleteNotificationLog,
|
|
deleteNotificationLogPrompt
|
|
});
|
|
|
|
const pageSizes = computed(() => appearanceSettingsStore.tablePageSizes);
|
|
|
|
const { table, pagination } = useVrcxVueTable({
|
|
persistKey: 'notifications',
|
|
get data() {
|
|
return notificationDisplayData.value;
|
|
},
|
|
columns,
|
|
getRowId: (row) => row.id ?? `${row.type}:${row.senderUserId ?? ''}:${row.created_at ?? ''}`,
|
|
initialSorting: [{ id: 'created_at', desc: true }],
|
|
initialPagination: {
|
|
pageIndex: 0,
|
|
pageSize: appearanceSettingsStore.tablePageSize
|
|
},
|
|
tableOptions: {
|
|
autoResetPageIndex: false
|
|
}
|
|
});
|
|
|
|
const totalItems = computed(() => {
|
|
const length = table.getFilteredRowModel().rows.length;
|
|
const max = vrcxStore.maxTableSize;
|
|
return length > max && length < max + 51 ? max : length;
|
|
});
|
|
|
|
const handlePageSizeChange = (size) => {
|
|
pagination.value = {
|
|
...pagination.value,
|
|
pageIndex: 0,
|
|
pageSize: size
|
|
};
|
|
};
|
|
|
|
const sendInviteResponseDialog = ref({
|
|
messageSlot: {},
|
|
invite: {}
|
|
});
|
|
|
|
const sendInviteResponseDialogVisible = ref(false);
|
|
|
|
const sendInviteRequestResponseDialogVisible = ref(false);
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function saveTableFilters() {
|
|
configRepository.setString(
|
|
'VRCX_notificationTableFilters',
|
|
JSON.stringify(notificationTable.value.filters[0].value)
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param value
|
|
*/
|
|
function handleNotificationFilterChange(value) {
|
|
notificationTable.value.filters[0].value = Array.isArray(value) ? value : [];
|
|
saveTableFilters();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param url
|
|
*/
|
|
function getSmallThumbnailUrl(url) {
|
|
return convertFileUrlToImageUrl(url);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param invite
|
|
*/
|
|
function showSendInviteResponseDialog(invite) {
|
|
sendInviteResponseDialog.value.invite = invite;
|
|
sendInviteResponseDialog.value.messageSlot = {};
|
|
refreshInviteMessageTableData('response');
|
|
clearInviteImageUpload();
|
|
sendInviteResponseDialogVisible.value = true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param invite
|
|
*/
|
|
function showSendInviteRequestResponseDialog(invite) {
|
|
sendInviteResponseDialog.value.invite = invite;
|
|
sendInviteResponseDialog.value.messageSlot = {};
|
|
refreshInviteMessageTableData('requestResponse');
|
|
clearInviteImageUpload();
|
|
sendInviteRequestResponseDialogVisible.value = true;
|
|
}
|
|
</script>
|