mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-28 03:03:47 +02:00
255 lines
9.7 KiB
Vue
255 lines
9.7 KiB
Vue
<template>
|
|
<div class="x-container feed x-container--auto-height" ref="feedRef">
|
|
<DataTableLayout
|
|
:table="table"
|
|
:loading="feedTable.loading"
|
|
auto-height
|
|
:page-sizes="pageSizes"
|
|
:total-items="totalItems"
|
|
:on-page-size-change="handlePageSizeChange">
|
|
<template #toolbar>
|
|
<div class="mt-0 mx-0 mb-2" style="display: flex; align-items: center">
|
|
<div style="flex: none; display: flex; align-items: center" class="mr-2">
|
|
<Popover v-model:open="popoverOpen">
|
|
<PopoverTrigger as-child>
|
|
<Button variant="outline" size="sm" class="mx-2 h-8 gap-1.5">
|
|
<ListFilter class="size-4" />
|
|
{{ t('view.my_avatars.filter') }}
|
|
<Badge
|
|
v-if="activeFilterCount"
|
|
variant="secondary"
|
|
class="ml-0.5 h-4.5 min-w-4.5 rounded-full px-1 text-xs">
|
|
{{ activeFilterCount }}
|
|
</Badge>
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent class="w-auto" side="bottom" align="end">
|
|
<RangeCalendar
|
|
v-model="dateRange"
|
|
:locale="locale"
|
|
:max-value="todayDate"
|
|
:number-of-months="2" />
|
|
<div class="flex justify-end gap-2 mt-3">
|
|
<Button variant="outline" size="sm" @click="clearDateFilter">
|
|
{{ t('common.actions.clear') }}
|
|
</Button>
|
|
<Button size="sm" @click="applyDateFilter">
|
|
{{ t('common.actions.confirm') }}
|
|
</Button>
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<TooltipWrapper side="bottom" :content="t('view.feed.favorites_only_tooltip')">
|
|
<div>
|
|
<Toggle
|
|
variant="outline"
|
|
size="sm"
|
|
:model-value="feedTable.vip"
|
|
@update:modelValue="
|
|
(v) => {
|
|
feedTable.vip = v;
|
|
feedTableLookup();
|
|
}
|
|
">
|
|
<Star />
|
|
</Toggle>
|
|
</div>
|
|
</TooltipWrapper>
|
|
</div>
|
|
<ToggleGroup
|
|
type="multiple"
|
|
variant="outline"
|
|
size="sm"
|
|
:model-value="activeFilterSelection"
|
|
@update:model-value="handleFeedFilterChange"
|
|
class="w-full justify-start"
|
|
style="flex: 1">
|
|
<ToggleGroupItem value="All">
|
|
{{ t('view.search.avatar.all') }}
|
|
</ToggleGroupItem>
|
|
<ToggleGroupItem v-for="type in feedFilterTypes" :key="type" :value="type">
|
|
{{ t('view.feed.filters.' + type) }}
|
|
</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
<InputGroupField
|
|
class="ml-2"
|
|
v-model="feedTable.search"
|
|
:placeholder="t('view.feed.search_placeholder')"
|
|
clearable
|
|
style="flex: 0.4"
|
|
@keyup.enter="feedTableLookup"
|
|
@change="feedTableLookup" />
|
|
</div>
|
|
</template>
|
|
</DataTableLayout>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref } from 'vue';
|
|
import { ListFilter, Star } from 'lucide-vue-next';
|
|
import { getLocalTimeZone, today } from '@internationalized/date';
|
|
import { storeToRefs } from 'pinia';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
|
|
import { useAppearanceSettingsStore, useFeedStore, useVrcxStore } from '../../stores';
|
|
import { ToggleGroup, ToggleGroupItem } from '../../components/ui/toggle-group';
|
|
import { Badge } from '../../components/ui/badge';
|
|
import { Button } from '../../components/ui/button';
|
|
import { DataTableLayout } from '../../components/ui/data-table';
|
|
import { InputGroupField } from '../../components/ui/input-group';
|
|
import { RangeCalendar } from '../../components/ui/range-calendar';
|
|
import { Toggle } from '../../components/ui/toggle';
|
|
import { columns as baseColumns } from './columns.jsx';
|
|
import { useVrcxVueTable } from '../../lib/table/useVrcxVueTable';
|
|
|
|
const { feedTable, feedTableData } = storeToRefs(useFeedStore());
|
|
const { feedTableLookup } = useFeedStore();
|
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
|
const vrcxStore = useVrcxStore();
|
|
|
|
const { t, locale } = useI18n();
|
|
const feedFilterTypes = ['GPS', 'Online', 'Offline', 'Status', 'Avatar', 'Bio'];
|
|
|
|
const popoverOpen = ref(false);
|
|
const todayDate = today(getLocalTimeZone());
|
|
const dateRange = ref(undefined);
|
|
const hasDateFilter = computed(() => !!(feedTable.value.dateFrom || feedTable.value.dateTo));
|
|
const activeFilterCount = computed(() => (hasDateFilter.value ? 1 : 0));
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function applyDateFilter() {
|
|
if (dateRange.value?.start) {
|
|
const s = dateRange.value.start;
|
|
feedTable.value.dateFrom = dayjs(`${s.year}-${s.month}-${s.day}`).startOf('day').toISOString();
|
|
} else {
|
|
feedTable.value.dateFrom = '';
|
|
}
|
|
if (dateRange.value?.end) {
|
|
const e = dateRange.value.end;
|
|
feedTable.value.dateTo = dayjs(`${e.year}-${e.month}-${e.day}`).endOf('day').toISOString();
|
|
} else {
|
|
feedTable.value.dateTo = '';
|
|
}
|
|
popoverOpen.value = false;
|
|
feedTableLookup();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function clearDateFilter() {
|
|
dateRange.value = undefined;
|
|
feedTable.value.dateFrom = '';
|
|
feedTable.value.dateTo = '';
|
|
popoverOpen.value = false;
|
|
feedTableLookup();
|
|
}
|
|
|
|
const feedRef = ref(null);
|
|
|
|
const pageSizes = computed(() => appearanceSettingsStore.tablePageSizes);
|
|
|
|
/**
|
|
*
|
|
* @param row
|
|
*/
|
|
function getFeedRowId(row) {
|
|
if (row?.id != null) return `id:${row.id}`;
|
|
if (row?.rowId != null) return `row:${row.rowId}`;
|
|
|
|
const type = row?.type ?? '';
|
|
const createdAt = row?.created_at ?? row?.createdAt ?? '';
|
|
const userId = row?.userId ?? row?.senderUserId ?? '';
|
|
const location = row?.location ?? row?.details?.location ?? '';
|
|
const message = row?.message ?? '';
|
|
|
|
return `${type}:${createdAt}:${userId}:${location}:${message}`;
|
|
}
|
|
|
|
const { table, pagination } = useVrcxVueTable({
|
|
get data() {
|
|
return feedTableData.value;
|
|
},
|
|
persistKey: 'feed',
|
|
columns: baseColumns,
|
|
getRowId: getFeedRowId,
|
|
enableExpanded: true,
|
|
getRowCanExpand: () => true,
|
|
initialSorting: [],
|
|
initialExpanded: {},
|
|
initialPagination: {
|
|
pageIndex: 0,
|
|
pageSize: appearanceSettingsStore.tablePageSize
|
|
},
|
|
tableOptions: {
|
|
autoResetExpanded: false,
|
|
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 activeFilterSelection = computed(() => {
|
|
const filter = feedTable.value.filter;
|
|
if (!Array.isArray(filter) || filter.length === 0) {
|
|
return ['All'];
|
|
}
|
|
return filter;
|
|
});
|
|
|
|
/**
|
|
*
|
|
* @param value
|
|
*/
|
|
function handleFeedFilterChange(value) {
|
|
const selected = Array.isArray(value) ? value : [];
|
|
const wasAll = activeFilterSelection.value.includes('All');
|
|
const hasAll = selected.includes('All');
|
|
const types = selected.filter((v) => v !== 'All');
|
|
|
|
if (hasAll && !wasAll) {
|
|
feedTable.value.filter = [];
|
|
} else if (wasAll && types.length) {
|
|
feedTable.value.filter = types;
|
|
} else {
|
|
feedTable.value.filter = types.length === feedFilterTypes.length ? [] : types.length ? types : [];
|
|
}
|
|
feedTableLookup();
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.feed :deep(.x-text-removed) {
|
|
text-decoration: line-through;
|
|
color: #ff0000;
|
|
background-color: rgba(255, 0, 0, 0.2);
|
|
padding: 2px 2px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.feed :deep(.x-text-added) {
|
|
color: rgb(35, 188, 35);
|
|
background-color: rgba(76, 255, 80, 0.2);
|
|
padding: 2px 2px;
|
|
border-radius: 4px;
|
|
}
|
|
</style>
|