mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-26 18:23:47 +02:00
rewrite tables
This commit is contained in:
@@ -38,361 +38,41 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-tabs type="card">
|
<el-tabs type="card">
|
||||||
<el-tab-pane :label="t('view.player_list.photon.current')">
|
<el-tab-pane :label="t('view.player_list.photon.current')">
|
||||||
<DataTable v-bind="photonEventTable" style="margin-bottom: 10px">
|
<DataTableLayout
|
||||||
<el-table-column :label="t('table.playerList.date')" prop="created_at" width="130">
|
class="min-w-0 w-full"
|
||||||
<template #default="scope">
|
:table="currentTable"
|
||||||
<TooltipWrapper side="right">
|
:loading="false"
|
||||||
<template #content>
|
:table-style="tableStyle"
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
:page-sizes="pageSizes"
|
||||||
</template>
|
:total-items="currentTotal"
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'short') }}</span>
|
:on-page-size-change="handleCurrentPageSizeChange"
|
||||||
</TooltipWrapper>
|
style="margin-bottom: 10px" />
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column :label="t('table.playerList.user')" prop="photonId" width="160">
|
|
||||||
<template #default="scope">
|
|
||||||
<span
|
|
||||||
class="x-link"
|
|
||||||
style="padding-right: 10px"
|
|
||||||
@click="showUserFromPhotonId(scope.row.photonId)"
|
|
||||||
v-text="scope.row.displayName"></span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column :label="t('table.playerList.type')" prop="type" width="140"></el-table-column>
|
|
||||||
<el-table-column :label="t('table.playerList.detail')" prop="text">
|
|
||||||
<template #default="scope">
|
|
||||||
<template v-if="scope.row.type === 'ChangeAvatar'">
|
|
||||||
<span
|
|
||||||
class="x-link"
|
|
||||||
@click="showAvatarDialog(scope.row.avatar.id)"
|
|
||||||
v-text="scope.row.avatar.name"></span>
|
|
||||||
|
|
||||||
<span v-if="!scope.row.inCache" style="color: #aaa"
|
|
||||||
><el-icon><Download /></el-icon> </span
|
|
||||||
>
|
|
||||||
<span v-if="scope.row.avatar.releaseStatus === 'public'" class="avatar-info-public">{{
|
|
||||||
t('dialog.avatar.labels.public')
|
|
||||||
}}</span>
|
|
||||||
<span
|
|
||||||
v-else-if="scope.row.avatar.releaseStatus === 'private'"
|
|
||||||
class="avatar-info-own"
|
|
||||||
>{{ t('dialog.avatar.labels.private') }}</span
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="scope.row.type === 'ChangeStatus'">
|
|
||||||
<template v-if="scope.row.status !== scope.row.previousStatus">
|
|
||||||
<TooltipWrapper side="top">
|
|
||||||
<template #content>
|
|
||||||
<span v-if="scope.row.previousStatus === 'active'">{{
|
|
||||||
t('dialog.user.status.active')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.previousStatus === 'join me'">{{
|
|
||||||
t('dialog.user.status.join_me')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.previousStatus === 'ask me'">{{
|
|
||||||
t('dialog.user.status.ask_me')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.previousStatus === 'busy'">{{
|
|
||||||
t('dialog.user.status.busy')
|
|
||||||
}}</span>
|
|
||||||
<span v-else>{{ t('dialog.user.status.offline') }}</span>
|
|
||||||
</template>
|
|
||||||
<i class="x-user-status" :class="statusClass(scope.row.previousStatus)"></i>
|
|
||||||
</TooltipWrapper>
|
|
||||||
<span>
|
|
||||||
<el-icon><ArrowRight /></el-icon>
|
|
||||||
</span>
|
|
||||||
<TooltipWrapper side="top">
|
|
||||||
<template #content>
|
|
||||||
<span v-if="scope.row.status === 'active'">{{
|
|
||||||
t('dialog.user.status.active')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.status === 'join me'">{{
|
|
||||||
t('dialog.user.status.join_me')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.status === 'ask me'">{{
|
|
||||||
t('dialog.user.status.ask_me')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.status === 'busy'">{{
|
|
||||||
t('dialog.user.status.busy')
|
|
||||||
}}</span>
|
|
||||||
<span v-else>{{ t('dialog.user.status.offline') }}</span>
|
|
||||||
</template>
|
|
||||||
<i
|
|
||||||
class="x-user-status"
|
|
||||||
:class="statusClass(scope.row.status)"
|
|
||||||
style="margin-right: 5px"></i>
|
|
||||||
</TooltipWrapper>
|
|
||||||
</template>
|
|
||||||
<span
|
|
||||||
v-if="scope.row.statusDescription !== scope.row.previousStatusDescription"
|
|
||||||
v-text="scope.row.statusDescription"></span>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="scope.row.type === 'ChangeGroup'">
|
|
||||||
<span
|
|
||||||
v-if="scope.row.previousGroupName"
|
|
||||||
class="x-link"
|
|
||||||
style="margin-right: 5px"
|
|
||||||
@click="showGroupDialog(scope.row.previousGroupId)"
|
|
||||||
v-text="scope.row.previousGroupName"></span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="x-link"
|
|
||||||
style="margin-right: 5px"
|
|
||||||
@click="showGroupDialog(scope.row.previousGroupId)"
|
|
||||||
v-text="scope.row.previousGroupId"></span>
|
|
||||||
<span>
|
|
||||||
<el-icon><ArrowRight /></el-icon>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="scope.row.groupName"
|
|
||||||
class="x-link"
|
|
||||||
style="margin-left: 5px"
|
|
||||||
@click="showGroupDialog(scope.row.groupId)"
|
|
||||||
v-text="scope.row.groupName"></span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="x-link"
|
|
||||||
style="margin-left: 5px"
|
|
||||||
@click="showGroupDialog(scope.row.groupId)"
|
|
||||||
v-text="scope.row.groupId"></span>
|
|
||||||
</template>
|
|
||||||
<span
|
|
||||||
v-else-if="scope.row.type === 'PortalSpawn'"
|
|
||||||
class="x-link"
|
|
||||||
@click="showWorldDialog(scope.row.location, scope.row.shortName)">
|
|
||||||
<Location
|
|
||||||
:location="scope.row.location"
|
|
||||||
:hint="scope.row.worldName"
|
|
||||||
:grouphint="scope.row.groupName"
|
|
||||||
:link="false" />
|
|
||||||
</span>
|
|
||||||
<span v-else-if="scope.row.type === 'ChatBoxMessage'" v-text="scope.row.text"></span>
|
|
||||||
<span v-else-if="scope.row.type === 'OnPlayerJoined'">
|
|
||||||
<span v-if="scope.row.platform === 'Desktop'" style="color: var(--el-color-primary)"
|
|
||||||
>Desktop </span
|
|
||||||
>
|
|
||||||
<span v-else-if="scope.row.platform === 'VR'" style="color: var(--el-color-primary)"
|
|
||||||
>VR </span
|
|
||||||
>
|
|
||||||
<span v-else-if="scope.row.platform === 'Quest'" style="color: var(--el-color-success)"
|
|
||||||
>Android </span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="x-link"
|
|
||||||
@click="showAvatarDialog(scope.row.avatar.id)"
|
|
||||||
v-text="scope.row.avatar.name"></span>
|
|
||||||
|
|
||||||
<span v-if="!scope.row.inCache" style="color: #aaa"
|
|
||||||
><el-icon><Download /></el-icon> </span
|
|
||||||
>
|
|
||||||
<span v-if="scope.row.avatar.releaseStatus === 'public'" class="avatar-info-public">{{
|
|
||||||
t('dialog.avatar.labels.public')
|
|
||||||
}}</span>
|
|
||||||
<span
|
|
||||||
v-else-if="scope.row.avatar.releaseStatus === 'private'"
|
|
||||||
class="avatar-info-own"
|
|
||||||
>{{ t('dialog.avatar.labels.private') }}</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
<span v-else-if="scope.row.type === 'SpawnEmoji'">
|
|
||||||
<span v-if="scope.row.imageUrl">
|
|
||||||
<TooltipWrapper side="right">
|
|
||||||
<template #content>
|
|
||||||
<img
|
|
||||||
:src="scope.row.imageUrl"
|
|
||||||
class="friends-list-avatar"
|
|
||||||
style="height: 500px; cursor: pointer"
|
|
||||||
@click="showFullscreenImageDialog(scope.row.imageUrl)"
|
|
||||||
loading="lazy" />
|
|
||||||
</template>
|
|
||||||
<span v-text="scope.row.fileId"></span>
|
|
||||||
</TooltipWrapper>
|
|
||||||
</span>
|
|
||||||
<span v-else v-text="scope.row.text"></span>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else-if="scope.row.color === 'yellow'"
|
|
||||||
style="color: yellow"
|
|
||||||
v-text="scope.row.text"></span>
|
|
||||||
<span v-else v-text="scope.row.text"></span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</DataTable>
|
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="t('view.player_list.photon.previous')">
|
<el-tab-pane :label="t('view.player_list.photon.previous')">
|
||||||
<DataTable v-bind="photonEventTablePrevious" style="margin-bottom: 10px">
|
<DataTableLayout
|
||||||
<el-table-column :label="t('table.playerList.date')" prop="created_at" width="130">
|
class="min-w-0 w-full"
|
||||||
<template #default="scope">
|
:table="previousTable"
|
||||||
<TooltipWrapper side="right">
|
:loading="false"
|
||||||
<template #content>
|
:table-style="tableStyle"
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
:page-sizes="pageSizes"
|
||||||
</template>
|
:total-items="previousTotal"
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'short') }}</span>
|
:on-page-size-change="handlePreviousPageSizeChange"
|
||||||
</TooltipWrapper>
|
style="margin-bottom: 10px" />
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column :label="t('table.playerList.user')" prop="photonId" width="160">
|
|
||||||
<template #default="scope">
|
|
||||||
<span
|
|
||||||
class="x-link"
|
|
||||||
style="padding-right: 10px"
|
|
||||||
@click="lookupUser(scope.row)"
|
|
||||||
v-text="scope.row.displayName"></span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column :label="t('table.playerList.type')" prop="type" width="140"></el-table-column>
|
|
||||||
<el-table-column :label="t('table.playerList.detail')" prop="text">
|
|
||||||
<template #default="scope">
|
|
||||||
<template v-if="scope.row.type === 'ChangeAvatar'">
|
|
||||||
<span
|
|
||||||
class="x-link"
|
|
||||||
@click="showAvatarDialog(scope.row.avatar.id)"
|
|
||||||
v-text="scope.row.avatar.name"></span>
|
|
||||||
|
|
||||||
<span v-if="!scope.row.inCache" style="color: #aaa"
|
|
||||||
><el-icon><Download /></el-icon> </span
|
|
||||||
>
|
|
||||||
<span v-if="scope.row.avatar.releaseStatus === 'public'" class="avatar-info-public">{{
|
|
||||||
t('dialog.avatar.labels.public')
|
|
||||||
}}</span>
|
|
||||||
<span
|
|
||||||
v-else-if="scope.row.avatar.releaseStatus === 'private'"
|
|
||||||
class="avatar-info-own"
|
|
||||||
>{{ t('dialog.avatar.labels.private') }}</span
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
v-if="
|
|
||||||
scope.row.avatar.description &&
|
|
||||||
scope.row.avatar.name !== scope.row.avatar.description
|
|
||||||
">
|
|
||||||
| - {{ scope.row.avatar.description }}
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="scope.row.type === 'ChangeStatus'">
|
|
||||||
<template v-if="scope.row.status !== scope.row.previousStatus">
|
|
||||||
<TooltipWrapper side="top">
|
|
||||||
<template #content>
|
|
||||||
<span v-if="scope.row.previousStatus === 'active'">{{
|
|
||||||
t('dialog.user.status.active')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.previousStatus === 'join me'">{{
|
|
||||||
t('dialog.user.status.join_me')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.previousStatus === 'ask me'">{{
|
|
||||||
t('dialog.user.status.ask_me')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.previousStatus === 'busy'">{{
|
|
||||||
t('dialog.user.status.busy')
|
|
||||||
}}</span>
|
|
||||||
<span v-else>{{ t('dialog.user.status.offline') }}</span>
|
|
||||||
</template>
|
|
||||||
<i class="x-user-status" :class="statusClass(scope.row.previousStatus)"></i>
|
|
||||||
</TooltipWrapper>
|
|
||||||
<span>
|
|
||||||
<el-icon><ArrowRight /></el-icon>
|
|
||||||
</span>
|
|
||||||
<TooltipWrapper side="top">
|
|
||||||
<template #content>
|
|
||||||
<span v-if="scope.row.status === 'active'">{{
|
|
||||||
t('dialog.user.status.active')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.status === 'join me'">{{
|
|
||||||
t('dialog.user.status.join_me')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.status === 'ask me'">{{
|
|
||||||
t('dialog.user.status.ask_me')
|
|
||||||
}}</span>
|
|
||||||
<span v-else-if="scope.row.status === 'busy'">{{
|
|
||||||
t('dialog.user.status.busy')
|
|
||||||
}}</span>
|
|
||||||
<span v-else>{{ t('dialog.user.status.offline') }}</span>
|
|
||||||
</template>
|
|
||||||
<i
|
|
||||||
class="x-user-status"
|
|
||||||
:class="statusClass(scope.row.status)"
|
|
||||||
style="margin-right: 5px"></i>
|
|
||||||
</TooltipWrapper>
|
|
||||||
</template>
|
|
||||||
<span
|
|
||||||
v-if="scope.row.statusDescription !== scope.row.previousStatusDescription"
|
|
||||||
v-text="scope.row.statusDescription"></span>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="scope.row.type === 'ChangeGroup'">
|
|
||||||
<span
|
|
||||||
v-if="scope.row.previousGroupName"
|
|
||||||
class="x-link"
|
|
||||||
style="margin-right: 5px"
|
|
||||||
@click="showGroupDialog(scope.row.previousGroupId)"
|
|
||||||
v-text="scope.row.previousGroupName"></span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="x-link"
|
|
||||||
style="margin-right: 5px"
|
|
||||||
@click="showGroupDialog(scope.row.previousGroupId)"
|
|
||||||
v-text="scope.row.previousGroupId"></span>
|
|
||||||
<span>
|
|
||||||
<el-icon><ArrowRight /></el-icon>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="scope.row.groupName"
|
|
||||||
class="x-link"
|
|
||||||
style="margin-left: 5px"
|
|
||||||
@click="showGroupDialog(scope.row.groupId)"
|
|
||||||
v-text="scope.row.groupName"></span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="x-link"
|
|
||||||
style="margin-left: 5px"
|
|
||||||
@click="showGroupDialog(scope.row.groupId)"
|
|
||||||
v-text="scope.row.groupId"></span>
|
|
||||||
</template>
|
|
||||||
<span
|
|
||||||
v-else-if="scope.row.type === 'PortalSpawn'"
|
|
||||||
class="x-link"
|
|
||||||
@click="showWorldDialog(scope.row.location, scope.row.shortName)">
|
|
||||||
<Location
|
|
||||||
:location="scope.row.location"
|
|
||||||
:hint="scope.row.worldName"
|
|
||||||
:grouphint="scope.row.groupName"
|
|
||||||
:link="false" />
|
|
||||||
</span>
|
|
||||||
<span v-else-if="scope.row.type === 'ChatBoxMessage'" v-text="scope.row.text"></span>
|
|
||||||
<span v-else-if="scope.row.type === 'SpawnEmoji'">
|
|
||||||
<span v-if="scope.row.imageUrl">
|
|
||||||
<TooltipWrapper side="right">
|
|
||||||
<template #content>
|
|
||||||
<img
|
|
||||||
:src="scope.row.imageUrl"
|
|
||||||
class="friends-list-avatar"
|
|
||||||
style="height: 500px; cursor: pointer"
|
|
||||||
@click="showFullscreenImageDialog(scope.row.imageUrl)"
|
|
||||||
loading="lazy" />
|
|
||||||
</template>
|
|
||||||
<span v-text="scope.row.fileId"></span>
|
|
||||||
</TooltipWrapper>
|
|
||||||
</span>
|
|
||||||
<span v-else v-text="scope.row.text"></span>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else-if="scope.row.color === 'yellow'"
|
|
||||||
style="color: yellow"
|
|
||||||
v-text="scope.row.text"></span>
|
|
||||||
<span v-else v-text="scope.row.text"></span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</DataTable>
|
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ArrowRight, Download } from '@element-plus/icons-vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { DataTableLayout } from '@/components/ui/data-table';
|
||||||
import { InputGroupField } from '@/components/ui/input-group';
|
import { InputGroupField } from '@/components/ui/input-group';
|
||||||
|
import { localeIncludes } from '@/shared/utils';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useSearchStore } from '@/stores';
|
||||||
|
import { useVrcxVueTable } from '@/lib/table/useVrcxVueTable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useAvatarStore,
|
useAvatarStore,
|
||||||
@@ -404,7 +84,7 @@
|
|||||||
useWorldStore
|
useWorldStore
|
||||||
} from '../../../stores';
|
} from '../../../stores';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../components/ui/select';
|
||||||
import { formatDateFilter, statusClass } from '../../../shared/utils';
|
import { createColumns } from './photonEventColumns.jsx';
|
||||||
import { photonEventTableTypeFilterList } from '../../../shared/constants/photon';
|
import { photonEventTableTypeFilterList } from '../../../shared/constants/photon';
|
||||||
|
|
||||||
const emit = defineEmits(['show-chatbox-blacklist']);
|
const emit = defineEmits(['show-chatbox-blacklist']);
|
||||||
@@ -420,6 +100,8 @@
|
|||||||
} = storeToRefs(photonStore);
|
} = storeToRefs(photonStore);
|
||||||
const { photonEventTableFilterChange, showUserFromPhotonId } = photonStore;
|
const { photonEventTableFilterChange, showUserFromPhotonId } = photonStore;
|
||||||
|
|
||||||
|
const { stringComparer } = storeToRefs(useSearchStore());
|
||||||
|
|
||||||
const { lookupUser } = useUserStore();
|
const { lookupUser } = useUserStore();
|
||||||
const { showAvatarDialog } = useAvatarStore();
|
const { showAvatarDialog } = useAvatarStore();
|
||||||
const { showWorldDialog } = useWorldStore();
|
const { showWorldDialog } = useWorldStore();
|
||||||
@@ -427,6 +109,104 @@
|
|||||||
const { showFullscreenImageDialog } = useGalleryStore();
|
const { showFullscreenImageDialog } = useGalleryStore();
|
||||||
const { ipcEnabled } = storeToRefs(useVrcxStore());
|
const { ipcEnabled } = storeToRefs(useVrcxStore());
|
||||||
|
|
||||||
|
const pageSizes = [10, 25, 50, 100];
|
||||||
|
const tableStyle = { maxHeight: '320px' };
|
||||||
|
|
||||||
|
const q = computed(() =>
|
||||||
|
String(photonEventTableFilter.value ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
);
|
||||||
|
const typeFilterSet = computed(() => new Set(photonEventTableTypeFilter.value ?? []));
|
||||||
|
|
||||||
|
const filterRows = (rows) => {
|
||||||
|
const query = q.value;
|
||||||
|
const types = typeFilterSet.value;
|
||||||
|
const comparer = stringComparer.value;
|
||||||
|
const src = Array.isArray(rows) ? rows : [];
|
||||||
|
|
||||||
|
return src.filter((row) => {
|
||||||
|
if (types.size && !types.has(row?.type)) return false;
|
||||||
|
if (!query) return true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
localeIncludes(row?.displayName ?? '', query, comparer) ||
|
||||||
|
localeIncludes(row?.text ?? '', query, comparer)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentRawRows = computed(() =>
|
||||||
|
Array.isArray(photonEventTable.value?.data) ? photonEventTable.value.data.slice() : []
|
||||||
|
);
|
||||||
|
const previousRawRows = computed(() =>
|
||||||
|
Array.isArray(photonEventTablePrevious.value?.data) ? photonEventTablePrevious.value.data.slice() : []
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentRows = computed(() => filterRows(currentRawRows.value));
|
||||||
|
const previousRows = computed(() => filterRows(previousRawRows.value));
|
||||||
|
|
||||||
|
const currentTotal = computed(() => currentRows.value.length);
|
||||||
|
const previousTotal = computed(() => previousRows.value.length);
|
||||||
|
|
||||||
|
const currentPageSize = ref(photonEventTable.value?.pageSize ?? 10);
|
||||||
|
const previousPageSize = ref(photonEventTablePrevious.value?.pageSize ?? 10);
|
||||||
|
|
||||||
|
const currentColumns = computed(() =>
|
||||||
|
createColumns({
|
||||||
|
isPrevious: false,
|
||||||
|
onShowUser: (row) => showUserFromPhotonId(row?.photonId),
|
||||||
|
onShowAvatar: showAvatarDialog,
|
||||||
|
onShowGroup: showGroupDialog,
|
||||||
|
onShowWorld: showWorldDialog,
|
||||||
|
onShowImage: showFullscreenImageDialog
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const previousColumns = computed(() =>
|
||||||
|
createColumns({
|
||||||
|
isPrevious: true,
|
||||||
|
onShowUser: (row) => lookupUser(row),
|
||||||
|
onShowAvatar: showAvatarDialog,
|
||||||
|
onShowGroup: showGroupDialog,
|
||||||
|
onShowWorld: showWorldDialog,
|
||||||
|
onShowImage: showFullscreenImageDialog
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const { table: currentTable } = useVrcxVueTable({
|
||||||
|
persistKey: 'photonEventTable:current',
|
||||||
|
data: currentRows,
|
||||||
|
columns: currentColumns.value,
|
||||||
|
getRowId: (row) => `${row?.photonId ?? ''}:${row?.created_at ?? ''}:${row?.type ?? ''}`,
|
||||||
|
initialSorting: [{ id: 'created_at', desc: true }],
|
||||||
|
initialPagination: { pageIndex: 0, pageSize: currentPageSize.value }
|
||||||
|
});
|
||||||
|
|
||||||
|
const { table: previousTable } = useVrcxVueTable({
|
||||||
|
persistKey: 'photonEventTable:previous',
|
||||||
|
data: previousRows,
|
||||||
|
columns: previousColumns.value,
|
||||||
|
getRowId: (row) => `${row?.photonId ?? ''}:${row?.created_at ?? ''}:${row?.type ?? ''}`,
|
||||||
|
initialSorting: [{ id: 'created_at', desc: true }],
|
||||||
|
initialPagination: { pageIndex: 0, pageSize: previousPageSize.value }
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCurrentPageSizeChange = (size) => {
|
||||||
|
currentPageSize.value = size;
|
||||||
|
};
|
||||||
|
const handlePreviousPageSizeChange = (size) => {
|
||||||
|
previousPageSize.value = size;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(currentColumns, (next) => {
|
||||||
|
currentTable.setOptions((prev) => ({ ...prev, columns: next }));
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(previousColumns, (next) => {
|
||||||
|
previousTable.setOptions((prev) => ({ ...prev, columns: next }));
|
||||||
|
});
|
||||||
|
|
||||||
function emitShowChatboxBlacklist() {
|
function emitShowChatboxBlacklist() {
|
||||||
emit('show-chatbox-blacklist');
|
emit('show-chatbox-blacklist');
|
||||||
}
|
}
|
||||||
|
|||||||
313
src/views/PlayerList/components/photonEventColumns.jsx
Normal file
313
src/views/PlayerList/components/photonEventColumns.jsx
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
import { ArrowRight, Download } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
|
import Location from '@/components/Location.vue';
|
||||||
|
import { TooltipWrapper } from '@/components/ui/tooltip';
|
||||||
|
import { i18n } from '@/plugin';
|
||||||
|
import { formatDateFilter, statusClass } from '@/shared/utils';
|
||||||
|
|
||||||
|
const { t } = i18n.global;
|
||||||
|
|
||||||
|
const statusLabel = (key) => {
|
||||||
|
if (key === 'active') return t('dialog.user.status.active');
|
||||||
|
if (key === 'join me') return t('dialog.user.status.join_me');
|
||||||
|
if (key === 'ask me') return t('dialog.user.status.ask_me');
|
||||||
|
if (key === 'busy') return t('dialog.user.status.busy');
|
||||||
|
return t('dialog.user.status.offline');
|
||||||
|
};
|
||||||
|
|
||||||
|
const avatarStatusLabel = (status) => {
|
||||||
|
if (status === 'public') return t('dialog.avatar.labels.public');
|
||||||
|
if (status === 'private') return t('dialog.avatar.labels.private');
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const avatarStatusClass = (status) => {
|
||||||
|
if (status === 'public') return 'avatar-info-public';
|
||||||
|
if (status === 'private') return 'avatar-info-own';
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function DetailCell({ row, isPrevious, onShowAvatar, onShowGroup, onShowWorld, onShowUser, onShowImage }) {
|
||||||
|
const r = row;
|
||||||
|
if (!r) return null;
|
||||||
|
|
||||||
|
if (r.type === 'ChangeAvatar') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
class="x-link"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onShowAvatar?.(r.avatar?.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{r.avatar?.name}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{!r.inCache ? (
|
||||||
|
<span style="color: #aaa">
|
||||||
|
<el-icon>
|
||||||
|
<Download />
|
||||||
|
</el-icon>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{r.avatar?.releaseStatus ? (
|
||||||
|
<span class={avatarStatusClass(r.avatar.releaseStatus)}>
|
||||||
|
{avatarStatusLabel(r.avatar.releaseStatus)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{isPrevious &&
|
||||||
|
r.avatar?.description &&
|
||||||
|
r.avatar?.name !== r.avatar?.description ? (
|
||||||
|
<>
|
||||||
|
{' | - '}
|
||||||
|
{r.avatar.description}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.type === 'ChangeStatus') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{r.status !== r.previousStatus ? (
|
||||||
|
<>
|
||||||
|
<TooltipWrapper
|
||||||
|
side="top"
|
||||||
|
v-slots={{
|
||||||
|
content: () => (
|
||||||
|
<span>{statusLabel(r.previousStatus)}</span>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class={[
|
||||||
|
'x-user-status',
|
||||||
|
statusClass(r.previousStatus)
|
||||||
|
]}
|
||||||
|
></i>
|
||||||
|
</TooltipWrapper>
|
||||||
|
<span>
|
||||||
|
<el-icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
|
<TooltipWrapper
|
||||||
|
side="top"
|
||||||
|
v-slots={{
|
||||||
|
content: () => (
|
||||||
|
<span>{statusLabel(r.status)}</span>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class={['x-user-status', statusClass(r.status)]}
|
||||||
|
style="margin-right: 5px"
|
||||||
|
></i>
|
||||||
|
</TooltipWrapper>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
{r.statusDescription !== r.previousStatusDescription ? (
|
||||||
|
<span>{r.statusDescription}</span>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.type === 'ChangeGroup') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
class="x-link"
|
||||||
|
style="margin-right: 5px"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onShowGroup?.(r.previousGroupId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{r.previousGroupName || r.previousGroupId}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<el-icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="x-link"
|
||||||
|
style="margin-left: 5px"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onShowGroup?.(r.groupId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{r.groupName || r.groupId}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.type === 'PortalSpawn') {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
class="x-link"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onShowWorld?.(r.location, r.shortName);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Location
|
||||||
|
location={r.location}
|
||||||
|
hint={r.worldName}
|
||||||
|
grouphint={r.groupName}
|
||||||
|
link={false}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.type === 'ChatBoxMessage') {
|
||||||
|
return <span>{r.text}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.type === 'OnPlayerJoined') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{r.platform === 'Desktop' ? (
|
||||||
|
<span style="color: var(--el-color-primary)">
|
||||||
|
Desktop
|
||||||
|
</span>
|
||||||
|
) : r.platform === 'VR' ? (
|
||||||
|
<span style="color: var(--el-color-primary)">VR </span>
|
||||||
|
) : r.platform === 'Quest' ? (
|
||||||
|
<span style="color: var(--el-color-success)">
|
||||||
|
Android
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="x-link"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onShowAvatar?.(r.avatar?.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{r.avatar?.name}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{!r.inCache ? (
|
||||||
|
<span style="color: #aaa">
|
||||||
|
<el-icon>
|
||||||
|
<Download />
|
||||||
|
</el-icon>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{r.avatar?.releaseStatus ? (
|
||||||
|
<span class={avatarStatusClass(r.avatar.releaseStatus)}>
|
||||||
|
{avatarStatusLabel(r.avatar.releaseStatus)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.type === 'SpawnEmoji') {
|
||||||
|
return r.imageUrl ? (
|
||||||
|
<TooltipWrapper
|
||||||
|
side="right"
|
||||||
|
v-slots={{
|
||||||
|
content: () => (
|
||||||
|
<img
|
||||||
|
src={r.imageUrl}
|
||||||
|
class="friends-list-avatar"
|
||||||
|
style="height: 500px; cursor: pointer"
|
||||||
|
loading="lazy"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onShowImage?.(r.imageUrl);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{r.fileId}</span>
|
||||||
|
</TooltipWrapper>
|
||||||
|
) : (
|
||||||
|
<span>{r.text}</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.color === 'yellow') {
|
||||||
|
return <span style="color: yellow">{r.text}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span>{r.text}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createColumns = ({ isPrevious, onShowUser, onShowAvatar, onShowGroup, onShowWorld, onShowImage }) => [
|
||||||
|
{
|
||||||
|
id: 'created_at',
|
||||||
|
accessorFn: (row) => (row?.created_at ? Date.parse(row.created_at) : 0),
|
||||||
|
header: () => t('table.playerList.date'),
|
||||||
|
size: 130,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<TooltipWrapper
|
||||||
|
side="right"
|
||||||
|
v-slots={{
|
||||||
|
content: () => (
|
||||||
|
<span>
|
||||||
|
{formatDateFilter(row.original?.created_at, 'long')}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{formatDateFilter(row.original?.created_at, 'short')}</span>
|
||||||
|
</TooltipWrapper>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'user',
|
||||||
|
header: () => t('table.playerList.user'),
|
||||||
|
size: 160,
|
||||||
|
enableSorting: false,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span
|
||||||
|
class="x-link"
|
||||||
|
style="padding-right: 10px"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onShowUser?.(row.original);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{row.original?.displayName}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'type',
|
||||||
|
accessorKey: 'type',
|
||||||
|
header: () => t('table.playerList.type'),
|
||||||
|
size: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'detail',
|
||||||
|
accessorKey: 'text',
|
||||||
|
header: () => t('table.playerList.detail'),
|
||||||
|
enableSorting: false,
|
||||||
|
meta: {
|
||||||
|
stretch: true
|
||||||
|
},
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<DetailCell
|
||||||
|
row={row.original}
|
||||||
|
isPrevious={!!isPrevious}
|
||||||
|
onShowAvatar={onShowAvatar}
|
||||||
|
onShowGroup={onShowGroup}
|
||||||
|
onShowWorld={onShowWorld}
|
||||||
|
onShowImage={onShowImage}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -22,50 +22,18 @@
|
|||||||
<span class="name" style="margin-right: 24px">{{ t('dialog.registry_backup.ask_to_restore') }}</span>
|
<span class="name" style="margin-right: 24px">{{ t('dialog.registry_backup.ask_to_restore') }}</span>
|
||||||
<Switch :model-value="vrcRegistryAskRestore" @update:modelValue="setVrcRegistryAskRestore" />
|
<Switch :model-value="vrcRegistryAskRestore" @update:modelValue="setVrcRegistryAskRestore" />
|
||||||
</div>
|
</div>
|
||||||
<DataTable v-bind="registryBackupTable" style="margin-top: 10px">
|
<DataTableLayout
|
||||||
<el-table-column :label="t('dialog.registry_backup.name')" prop="name"></el-table-column>
|
class="min-w-0 w-full"
|
||||||
<el-table-column :label="t('dialog.registry_backup.date')" prop="date">
|
:table="table"
|
||||||
<template #default="scope">
|
:loading="false"
|
||||||
<span>{{ formatDateFilter(scope.row.date, 'long') }}</span>
|
:table-style="tableStyle"
|
||||||
</template>
|
:show-pagination="false"
|
||||||
</el-table-column>
|
style="margin-top: 10px" />
|
||||||
<el-table-column :label="t('dialog.registry_backup.action')" width="90" align="right">
|
|
||||||
<template #default="scope">
|
|
||||||
<TooltipWrapper side="top" :content="t('dialog.registry_backup.restore')">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
class="button-pd-0"
|
|
||||||
@click="restoreVrcRegistryBackup(scope.row)">
|
|
||||||
<RotateCcw
|
|
||||||
/></Button>
|
|
||||||
</TooltipWrapper>
|
|
||||||
<TooltipWrapper side="top" :content="t('dialog.registry_backup.save_to_file')">
|
|
||||||
<Button
|
|
||||||
size="icon-sm"
|
|
||||||
variant="ghost"
|
|
||||||
class="button-pd-0"
|
|
||||||
@click="saveVrcRegistryBackupToFile(scope.row)">
|
|
||||||
<Download
|
|
||||||
/></Button>
|
|
||||||
</TooltipWrapper>
|
|
||||||
<TooltipWrapper side="top" :content="t('dialog.registry_backup.delete')">
|
|
||||||
<Button
|
|
||||||
size="icon-sm"
|
|
||||||
variant="ghost"
|
|
||||||
class="button-pd-0"
|
|
||||||
@click="deleteVrcRegistryBackup(scope.row)"
|
|
||||||
><Trash2
|
|
||||||
/></Button>
|
|
||||||
</TooltipWrapper>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</DataTable>
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 10px">
|
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 10px">
|
||||||
<Button size="sm" variant="destructive" @click="deleteVrcRegistry">{{
|
<Button size="sm" variant="destructive" @click="deleteVrcRegistry">{{
|
||||||
t('dialog.registry_backup.reset')
|
t('dialog.registry_backup.reset')
|
||||||
}}</Button>
|
}}</Button>
|
||||||
<div>
|
<div class="flex gap-2">
|
||||||
<Button size="sm" variant="outline" @click="promptVrcRegistryBackupName">{{
|
<Button size="sm" variant="outline" @click="promptVrcRegistryBackupName">{{
|
||||||
t('dialog.registry_backup.backup')
|
t('dialog.registry_backup.backup')
|
||||||
}}</Button>
|
}}</Button>
|
||||||
@@ -79,17 +47,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Download, RotateCcw, Trash2 } from 'lucide-vue-next';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { DataTableLayout } from '@/components/ui/data-table';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { downloadAndSaveJson, formatDateFilter, removeFromArray } from '../../../shared/utils';
|
import { downloadAndSaveJson, removeFromArray } from '../../../shared/utils';
|
||||||
import { useAdvancedSettingsStore, useVrcxStore } from '../../../stores';
|
import { useAdvancedSettingsStore, useVrcxStore } from '../../../stores';
|
||||||
import { Switch } from '../../../components/ui/switch';
|
import { Switch } from '../../../components/ui/switch';
|
||||||
|
import { createColumns } from './registryBackupColumns.jsx';
|
||||||
|
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||||
|
|
||||||
import configRepository from '../../../service/config';
|
import configRepository from '../../../service/config';
|
||||||
|
|
||||||
@@ -113,6 +83,29 @@
|
|||||||
layout: 'table'
|
layout: 'table'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tableStyle = { maxHeight: '320px' };
|
||||||
|
|
||||||
|
const rows = computed(() =>
|
||||||
|
Array.isArray(registryBackupTable.value?.data) ? registryBackupTable.value.data.slice() : []
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = computed(() =>
|
||||||
|
createColumns({
|
||||||
|
onRestore: restoreVrcRegistryBackup,
|
||||||
|
onSaveToFile: saveVrcRegistryBackupToFile,
|
||||||
|
onDelete: deleteVrcRegistryBackup
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const { table } = useVrcxVueTable({
|
||||||
|
persistKey: 'registryBackupDialog',
|
||||||
|
data: rows,
|
||||||
|
columns: columns.value,
|
||||||
|
getRowId: (row) => String(row?.name ?? ''),
|
||||||
|
enablePagination: false,
|
||||||
|
initialSorting: [{ id: 'date', desc: true }]
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => isRegistryBackupDialogVisible.value,
|
() => isRegistryBackupDialogVisible.value,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
|
|||||||
110
src/views/Settings/dialogs/registryBackupColumns.jsx
Normal file
110
src/views/Settings/dialogs/registryBackupColumns.jsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { Download, RotateCcw, Trash2 } from 'lucide-vue-next';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { TooltipWrapper } from '@/components/ui/tooltip';
|
||||||
|
import { i18n } from '@/plugin';
|
||||||
|
import { formatDateFilter } from '@/shared/utils';
|
||||||
|
|
||||||
|
const { t } = i18n.global;
|
||||||
|
|
||||||
|
export const createColumns = ({ onRestore, onSaveToFile, onDelete }) => [
|
||||||
|
{
|
||||||
|
id: 'name',
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: () => t('dialog.registry_backup.name'),
|
||||||
|
meta: {
|
||||||
|
stretch: true
|
||||||
|
},
|
||||||
|
cell: ({ row }) => <span>{row.original?.name}</span>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'date',
|
||||||
|
accessorFn: (row) => {
|
||||||
|
const v = row?.date;
|
||||||
|
if (typeof v === 'number') return v;
|
||||||
|
const ts = Date.parse(String(v ?? ''));
|
||||||
|
return Number.isFinite(ts) ? ts : 0;
|
||||||
|
},
|
||||||
|
header: ({ column }) => (
|
||||||
|
<button
|
||||||
|
class="inline-flex items-center"
|
||||||
|
onClick={() => {
|
||||||
|
const sorted = column.getIsSorted();
|
||||||
|
column.toggleSorting(sorted === 'asc');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('dialog.registry_backup.date')}
|
||||||
|
</button>
|
||||||
|
),
|
||||||
|
size: 170,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span>{formatDateFilter(row.original?.date, 'long')}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'action',
|
||||||
|
header: () => t('dialog.registry_backup.action'),
|
||||||
|
enableSorting: false,
|
||||||
|
size: 90,
|
||||||
|
meta: {
|
||||||
|
tdClass: 'text-right'
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const original = row.original;
|
||||||
|
return (
|
||||||
|
<div class="inline-flex items-center justify-end gap-1">
|
||||||
|
<TooltipWrapper
|
||||||
|
side="top"
|
||||||
|
content={t('dialog.registry_backup.restore')}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="icon-sm"
|
||||||
|
variant="ghost"
|
||||||
|
class="button-pd-0"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onRestore?.(original);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RotateCcw />
|
||||||
|
</Button>
|
||||||
|
</TooltipWrapper>
|
||||||
|
|
||||||
|
<TooltipWrapper
|
||||||
|
side="top"
|
||||||
|
content={t('dialog.registry_backup.save_to_file')}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="icon-sm"
|
||||||
|
variant="ghost"
|
||||||
|
class="button-pd-0"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onSaveToFile?.(original);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Download />
|
||||||
|
</Button>
|
||||||
|
</TooltipWrapper>
|
||||||
|
|
||||||
|
<TooltipWrapper
|
||||||
|
side="top"
|
||||||
|
content={t('dialog.registry_backup.delete')}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="icon-sm"
|
||||||
|
variant="ghost"
|
||||||
|
class="button-pd-0"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDelete?.(original);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
|
</TooltipWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -222,6 +222,7 @@
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const { showGalleryPage } = useGalleryStore();
|
const { showGalleryPage } = useGalleryStore();
|
||||||
const { friends } = storeToRefs(useFriendStore());
|
const { friends } = storeToRefs(useFriendStore());
|
||||||
@@ -239,11 +240,7 @@
|
|||||||
const isExportFriendsListDialogVisible = ref(false);
|
const isExportFriendsListDialogVisible = ref(false);
|
||||||
const isExportAvatarsListDialogVisible = ref(false);
|
const isExportAvatarsListDialogVisible = ref(false);
|
||||||
const isEditInviteMessagesDialogVisible = ref(false);
|
const isEditInviteMessagesDialogVisible = ref(false);
|
||||||
const isToolsTabVisible = computed(() => {
|
const isToolsTabVisible = computed(() => route.name === 'tools');
|
||||||
const route = useRoute();
|
|
||||||
if (!route) return false;
|
|
||||||
return route.name === 'tools';
|
|
||||||
});
|
|
||||||
|
|
||||||
const showGroupCalendarDialog = () => {
|
const showGroupCalendarDialog = () => {
|
||||||
isGroupCalendarDialogVisible.value = true;
|
isGroupCalendarDialogVisible.value = true;
|
||||||
|
|||||||
@@ -18,13 +18,20 @@
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
class="mr-2"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
style="margin-top: 10px"
|
style="margin-top: 10px"
|
||||||
@click="updateNoteExportDialog">
|
@click="updateNoteExportDialog">
|
||||||
{{ t('dialog.note_export.refresh') }}
|
{{ t('dialog.note_export.refresh') }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="outline" :disabled="loading" style="margin-top: 10px" @click="exportNoteExport">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
class="mr-2"
|
||||||
|
variant="outline"
|
||||||
|
:disabled="loading"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
@click="exportNoteExport">
|
||||||
{{ t('dialog.note_export.export') }}
|
{{ t('dialog.note_export.export') }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-if="loading" size="sm" variant="outline" style="margin-top: 10px" @click="cancelNoteExport">
|
<Button v-if="loading" size="sm" variant="outline" style="margin-top: 10px" @click="cancelNoteExport">
|
||||||
@@ -45,60 +52,29 @@
|
|||||||
<pre style="white-space: pre-wrap; font-size: 12px" v-text="errors"></pre>
|
<pre style="white-space: pre-wrap; font-size: 12px" v-text="errors"></pre>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<DataTable :loading="loading" v-bind="noteExportTable" style="margin-top: 10px">
|
<DataTableLayout
|
||||||
<el-table-column :label="t('table.import.image')" width="70" prop="currentAvatarThumbnailImageUrl">
|
class="min-w-0 w-full"
|
||||||
<template #default="{ row }">
|
:table="table"
|
||||||
<el-popover placement="right" :width="500" trigger="hover">
|
:loading="loading"
|
||||||
<template #reference>
|
:table-style="tableStyle"
|
||||||
<img :src="userImage(row.ref)" class="friends-list-avatar" loading="lazy" />
|
:show-pagination="false"
|
||||||
</template>
|
style="margin-top: 10px" />
|
||||||
<img
|
|
||||||
:src="userImageFull(row.ref)"
|
|
||||||
:class="['friends-list-avatar', 'x-popover-image']"
|
|
||||||
style="cursor: pointer"
|
|
||||||
loading="lazy"
|
|
||||||
@click="showFullscreenImageDialog(userImageFull(row.ref))" />
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column :label="t('table.import.name')" width="170" prop="name">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span class="x-link" @click="showUserDialog(row.id)" v-text="row.name"></span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column :label="t('table.import.note')" prop="memo">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<InputGroupTextareaField
|
|
||||||
v-model="row.memo"
|
|
||||||
:maxlength="256"
|
|
||||||
:rows="2"
|
|
||||||
input-class="min-h-0 py-1 resize-none"
|
|
||||||
show-count />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column :label="t('table.import.skip_export')" width="90" align="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<Button size="sm" variant="ghost" @click="removeFromNoteExportTable(row)"></Button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</DataTable>
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
import { DataTableLayout } from '@/components/ui/data-table';
|
||||||
import { Loading } from '@element-plus/icons-vue';
|
import { Loading } from '@element-plus/icons-vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { removeFromArray, userImage, userImageFull } from '../../../shared/utils';
|
import { removeFromArray, userImage, userImageFull } from '../../../shared/utils';
|
||||||
import { useFriendStore, useGalleryStore, useUserStore } from '../../../stores';
|
import { useFriendStore, useGalleryStore, useUserStore } from '../../../stores';
|
||||||
|
import { createColumns } from './noteExportColumns.jsx';
|
||||||
import { miscRequest } from '../../../api';
|
import { miscRequest } from '../../../api';
|
||||||
|
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||||
|
|
||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
|
|
||||||
@@ -123,6 +99,29 @@
|
|||||||
layout: 'table'
|
layout: 'table'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tableStyle = { maxHeight: '500px' };
|
||||||
|
|
||||||
|
const rows = computed(() => (Array.isArray(noteExportTable.value?.data) ? noteExportTable.value.data.slice() : []));
|
||||||
|
|
||||||
|
const columns = computed(() =>
|
||||||
|
createColumns({
|
||||||
|
userImage,
|
||||||
|
userImageFull,
|
||||||
|
onShowFullscreenImage: showFullscreenImageDialog,
|
||||||
|
onShowUser: showUserDialog,
|
||||||
|
onRemove: removeFromNoteExportTable
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const { table } = useVrcxVueTable({
|
||||||
|
persistKey: 'noteExportDialog',
|
||||||
|
data: rows,
|
||||||
|
columns: columns.value,
|
||||||
|
getRowId: (row) => String(row?.id ?? ''),
|
||||||
|
enablePagination: false,
|
||||||
|
enableSorting: false
|
||||||
|
});
|
||||||
|
|
||||||
const progress = ref(0);
|
const progress = ref(0);
|
||||||
const progressTotal = ref(0);
|
const progressTotal = ref(0);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|||||||
119
src/views/Tools/dialogs/noteExportColumns.jsx
Normal file
119
src/views/Tools/dialogs/noteExportColumns.jsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { Trash2 } from 'lucide-vue-next';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||||
|
import { i18n } from '@/plugin';
|
||||||
|
|
||||||
|
const { t } = i18n.global;
|
||||||
|
|
||||||
|
export const createColumns = ({ userImage, userImageFull, onShowFullscreenImage, onShowUser, onRemove }) => [
|
||||||
|
{
|
||||||
|
id: 'image',
|
||||||
|
header: () => t('table.import.image'),
|
||||||
|
enableSorting: false,
|
||||||
|
size: 70,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const original = row.original;
|
||||||
|
const ref = original?.ref;
|
||||||
|
const thumb = userImage?.(ref);
|
||||||
|
const full = userImageFull?.(ref);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<el-popover
|
||||||
|
placement="right"
|
||||||
|
width={500}
|
||||||
|
trigger="hover"
|
||||||
|
v-slots={{
|
||||||
|
reference: () => (
|
||||||
|
<img
|
||||||
|
src={thumb}
|
||||||
|
class="friends-list-avatar"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={full}
|
||||||
|
class={['friends-list-avatar', 'x-popover-image']}
|
||||||
|
style="cursor: pointer"
|
||||||
|
loading="lazy"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (full) {
|
||||||
|
onShowFullscreenImage?.(full);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</el-popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'name',
|
||||||
|
header: () => t('table.import.name'),
|
||||||
|
size: 170,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const original = row.original;
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
class="x-link"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onShowUser?.(original?.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{original?.name}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'memo',
|
||||||
|
accessorKey: 'memo',
|
||||||
|
header: () => t('table.import.note'),
|
||||||
|
enableSorting: false,
|
||||||
|
meta: {
|
||||||
|
stretch: true
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const original = row.original;
|
||||||
|
return (
|
||||||
|
<InputGroupTextareaField
|
||||||
|
modelValue={original?.memo ?? ''}
|
||||||
|
onUpdate:modelValue={(value) => {
|
||||||
|
original.memo = value;
|
||||||
|
}}
|
||||||
|
maxlength={256}
|
||||||
|
rows={2}
|
||||||
|
input-class="min-h-0 py-1 resize-none"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'skip',
|
||||||
|
header: () => t('table.import.skip_export'),
|
||||||
|
size: 90,
|
||||||
|
enableSorting: false,
|
||||||
|
meta: {
|
||||||
|
tdClass: 'text-right'
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const original = row.original;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="icon-sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onRemove?.(original);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user