mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-18 06:13:52 +02:00
rewrite tables
This commit is contained in:
@@ -38,361 +38,41 @@
|
||||
</div>
|
||||
<el-tabs type="card">
|
||||
<el-tab-pane :label="t('view.player_list.photon.current')">
|
||||
<DataTable v-bind="photonEventTable" style="margin-bottom: 10px">
|
||||
<el-table-column :label="t('table.playerList.date')" prop="created_at" width="130">
|
||||
<template #default="scope">
|
||||
<TooltipWrapper side="right">
|
||||
<template #content>
|
||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||
</template>
|
||||
<span>{{ formatDateFilter(scope.row.created_at, 'short') }}</span>
|
||||
</TooltipWrapper>
|
||||
</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>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="currentTable"
|
||||
:loading="false"
|
||||
:table-style="tableStyle"
|
||||
:page-sizes="pageSizes"
|
||||
:total-items="currentTotal"
|
||||
:on-page-size-change="handleCurrentPageSizeChange"
|
||||
style="margin-bottom: 10px" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('view.player_list.photon.previous')">
|
||||
<DataTable v-bind="photonEventTablePrevious" style="margin-bottom: 10px">
|
||||
<el-table-column :label="t('table.playerList.date')" prop="created_at" width="130">
|
||||
<template #default="scope">
|
||||
<TooltipWrapper side="right">
|
||||
<template #content>
|
||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||
</template>
|
||||
<span>{{ formatDateFilter(scope.row.created_at, 'short') }}</span>
|
||||
</TooltipWrapper>
|
||||
</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>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="previousTable"
|
||||
:loading="false"
|
||||
:table-style="tableStyle"
|
||||
:page-sizes="pageSizes"
|
||||
:total-items="previousTotal"
|
||||
:on-page-size-change="handlePreviousPageSizeChange"
|
||||
style="margin-bottom: 10px" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ArrowRight, Download } from '@element-plus/icons-vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
import { InputGroupField } from '@/components/ui/input-group';
|
||||
import { localeIncludes } from '@/shared/utils';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useSearchStore } from '@/stores';
|
||||
import { useVrcxVueTable } from '@/lib/table/useVrcxVueTable';
|
||||
|
||||
import {
|
||||
useAvatarStore,
|
||||
@@ -404,7 +84,7 @@
|
||||
useWorldStore
|
||||
} from '../../../stores';
|
||||
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';
|
||||
|
||||
const emit = defineEmits(['show-chatbox-blacklist']);
|
||||
@@ -420,6 +100,8 @@
|
||||
} = storeToRefs(photonStore);
|
||||
const { photonEventTableFilterChange, showUserFromPhotonId } = photonStore;
|
||||
|
||||
const { stringComparer } = storeToRefs(useSearchStore());
|
||||
|
||||
const { lookupUser } = useUserStore();
|
||||
const { showAvatarDialog } = useAvatarStore();
|
||||
const { showWorldDialog } = useWorldStore();
|
||||
@@ -427,6 +109,104 @@
|
||||
const { showFullscreenImageDialog } = useGalleryStore();
|
||||
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() {
|
||||
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>
|
||||
<Switch :model-value="vrcRegistryAskRestore" @update:modelValue="setVrcRegistryAskRestore" />
|
||||
</div>
|
||||
<DataTable v-bind="registryBackupTable" style="margin-top: 10px">
|
||||
<el-table-column :label="t('dialog.registry_backup.name')" prop="name"></el-table-column>
|
||||
<el-table-column :label="t('dialog.registry_backup.date')" prop="date">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateFilter(scope.row.date, 'long') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="false"
|
||||
:table-style="tableStyle"
|
||||
:show-pagination="false"
|
||||
style="margin-top: 10px" />
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 10px">
|
||||
<Button size="sm" variant="destructive" @click="deleteVrcRegistry">{{
|
||||
t('dialog.registry_backup.reset')
|
||||
}}</Button>
|
||||
<div>
|
||||
<div class="flex gap-2">
|
||||
<Button size="sm" variant="outline" @click="promptVrcRegistryBackupName">{{
|
||||
t('dialog.registry_backup.backup')
|
||||
}}</Button>
|
||||
@@ -79,17 +47,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Download, RotateCcw, Trash2 } from 'lucide-vue-next';
|
||||
import { ref, watch } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { downloadAndSaveJson, formatDateFilter, removeFromArray } from '../../../shared/utils';
|
||||
import { downloadAndSaveJson, removeFromArray } from '../../../shared/utils';
|
||||
import { useAdvancedSettingsStore, useVrcxStore } from '../../../stores';
|
||||
import { Switch } from '../../../components/ui/switch';
|
||||
import { createColumns } from './registryBackupColumns.jsx';
|
||||
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||
|
||||
import configRepository from '../../../service/config';
|
||||
|
||||
@@ -113,6 +83,29 @@
|
||||
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(
|
||||
() => isRegistryBackupDialogVisible.value,
|
||||
(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 router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const { showGalleryPage } = useGalleryStore();
|
||||
const { friends } = storeToRefs(useFriendStore());
|
||||
@@ -239,11 +240,7 @@
|
||||
const isExportFriendsListDialogVisible = ref(false);
|
||||
const isExportAvatarsListDialogVisible = ref(false);
|
||||
const isEditInviteMessagesDialogVisible = ref(false);
|
||||
const isToolsTabVisible = computed(() => {
|
||||
const route = useRoute();
|
||||
if (!route) return false;
|
||||
return route.name === 'tools';
|
||||
});
|
||||
const isToolsTabVisible = computed(() => route.name === 'tools');
|
||||
|
||||
const showGroupCalendarDialog = () => {
|
||||
isGroupCalendarDialogVisible.value = true;
|
||||
|
||||
@@ -18,13 +18,20 @@
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
class="mr-2"
|
||||
variant="outline"
|
||||
:disabled="loading"
|
||||
style="margin-top: 10px"
|
||||
@click="updateNoteExportDialog">
|
||||
{{ t('dialog.note_export.refresh') }}
|
||||
</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') }}
|
||||
</Button>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<DataTable :loading="loading" v-bind="noteExportTable" style="margin-top: 10px">
|
||||
<el-table-column :label="t('table.import.image')" width="70" prop="currentAvatarThumbnailImageUrl">
|
||||
<template #default="{ row }">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img :src="userImage(row.ref)" class="friends-list-avatar" loading="lazy" />
|
||||
</template>
|
||||
<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>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="table"
|
||||
:loading="loading"
|
||||
:table-style="tableStyle"
|
||||
:show-pagination="false"
|
||||
style="margin-top: 10px" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
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 { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { removeFromArray, userImage, userImageFull } from '../../../shared/utils';
|
||||
import { useFriendStore, useGalleryStore, useUserStore } from '../../../stores';
|
||||
import { createColumns } from './noteExportColumns.jsx';
|
||||
import { miscRequest } from '../../../api';
|
||||
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
|
||||
|
||||
import * as workerTimers from 'worker-timers';
|
||||
|
||||
@@ -123,6 +99,29 @@
|
||||
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 progressTotal = ref(0);
|
||||
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