Upgrade to Vue3 and Element Plus (#1374)

* Update Vue devtools

* upgrade vue pinia element-plus vue-i18n, add vite

* fix: i18n

* global components

* change v-deep

* upgrade vue-lazyload

* data table

* update enlint and safe-dialog

* package.json and vite.config.js

* el-icon

* el-message

* vue 2 -> vue3 migration changes

* $pinia

* dialog

* el-popover slot

* lint

* chore

* slot

* scss

* remote state access

* misc

* jsconfig

* el-button size mini -> small

* :model-value

* ElMessageBox

* datatable

* remove v-lazyload

* template #dropdown

* mini -> small

* css

* byebye hideTooltips

* use sass-embedded

* Update SQLite, remove unneeded libraries

* Fix shift remove local avatar favorites

* Electron arm64

* arm64 support

* bye pug

* f-word vite hah

* misc

* remove safe dialog component

* Add self invite to launch dialog

* Fix errors

* Icons 1

* improve localfavorite loading performance

* improve favorites world item performance

* dialog visibility changes for Element Plus

* clear element plus error

* import performance

* revert App.vue hah

* hah

* Revert "Add self invite to launch dialog"

This reverts commit 4801cfad58.

* Toggle self invite/open in-game

* Self invite on launch dialog

* el-button icon

* el-icon

* fix user dialog tab switching logic

* fix PlayerList

* Formatting changes

* More icons

* Fix friend log table

* loading margin

* fix markdown

* fix world dialog tab switching issue

* Fixes and formatting

* fix: global i18n.t export

* fix favorites world tab not working

* Create instance, displayName

* Remove group members sort by userId

* Fix loading dialog tabs on swtich

* Star

* charts console.warn

* wip: fix charts

* wip: fix charts

* wip: charts composables

* fix favorite item tooltip warning

* Fixes and formatting

* Clean up image dialogs

* Remove unused method

* Fix platform/size border

* Fix platform/size border

* $vr

* fix friendExportDialogVisible binding

* ElMessageBox and Settings

* Login formatting

* Rename VR overlay query

* Fix image popover and userdialog badges

* Formatting

* Big buttons

* Fixes, update Cef

* Fix gameLog table nav buttons jumping around while using nav buttons

* Fix z-index

* vr overlay

* vite input add theme

* defineAsyncComponent

* ISO 639-1

* fix i18n

* clean t

* Formatting, fix calendar, rotate arrows

* Show user status when user is offline

* Fix VR overlay

* fix theme and clean up

* split InstanceActivity

* tweak

* Fix VR overlay formatting

* fix scss var

* AppDebug hahahaha

* Years

* remove reactive

* improve perf

* state hah…

* fix user rendering poblems when user object is not yet loaded

* improve perf

* Update avatar/world image uploader, licenses, remove previous images dialog (old images are now deleted)

* improve perf 1

* Suppress stray errors

* fix traveling location display issue

* Fix empty instance creator

* improve friend list refresh performance

* fix main charts

* fix chart

* Fix darkmode

* Fix avatar dialog tags

---------

Co-authored-by: pa <maplenagisa@gmail.com>
This commit is contained in:
Natsumi
2025-09-12 10:45:24 +12:00
committed by GitHub
parent b233bbc299
commit 3324d0d279
249 changed files with 12948 additions and 19815 deletions
@@ -1,92 +1,96 @@
<template>
<safe-dialog :visible.sync="isDialogVisible" :title="t('dialog.avatar_export.header')" width="650px">
<el-dialog v-model="isDialogVisible" :title="t('dialog.avatar_export.header')" width="650px">
<el-checkbox-group
v-model="exportSelectedOptions"
style="margin-bottom: 10px"
@change="updateAvatarExportDialog()">
<template v-for="option in exportSelectOptions">
<el-checkbox :key="option.value" :label="option.label"></el-checkbox>
<template v-for="option in exportSelectOptions" :key="option.value">
<el-checkbox :label="option.label"></el-checkbox>
</template>
</el-checkbox-group>
<el-dropdown trigger="click" size="small" @click.native.stop>
<el-button size="mini">
<el-dropdown trigger="click" size="small">
<el-button size="small">
<span v-if="avatarExportFavoriteGroup">
{{ avatarExportFavoriteGroup.displayName }} ({{ avatarExportFavoriteGroup.count }}/{{
avatarExportFavoriteGroup.capacity
}})
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
<span v-else>
All Favorites
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item style="display: block; margin: 10px 0" @click.native="selectAvatarExportGroup(null)">
All Favorites
</el-dropdown-item>
<template v-for="groupAPI in favoriteAvatarGroups">
<el-dropdown-item
:key="groupAPI.name"
style="display: block; margin: 10px 0"
@click.native="selectAvatarExportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item style="display: block; margin: 10px 0" @click="selectAvatarExportGroup(null)">
All Favorites
</el-dropdown-item>
</template>
</el-dropdown-menu>
<template v-for="groupAPI in favoriteAvatarGroups" :key="groupAPI.name">
<el-dropdown-item
style="display: block; margin: 10px 0"
@click="selectAvatarExportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown trigger="click" size="small" style="margin-left: 10px" @click.native.stop>
<el-button size="mini">
<el-dropdown trigger="click" size="small" style="margin-left: 10px">
<el-button size="small">
<span v-if="avatarExportLocalFavoriteGroup">
{{ avatarExportLocalFavoriteGroup }} ({{
getLocalAvatarFavoriteGroupLength(avatarExportLocalFavoriteGroup)
}})
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
<span v-else>
Select Group
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
style="display: block; margin: 10px 0"
@click.native="selectAvatarExportLocalGroup(null)">
None
</el-dropdown-item>
<template v-for="group in localAvatarFavoriteGroups">
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
:key="group"
style="display: block; margin: 10px 0"
@click.native="selectAvatarExportLocalGroup(group)">
{{ group }} ({{ getLocalAvatarFavoriteGroupLength(group) }})
@click="selectAvatarExportLocalGroup(null)">
None
</el-dropdown-item>
</template>
</el-dropdown-menu>
<template v-for="group in localAvatarFavoriteGroups" :key="group">
<el-dropdown-item
style="display: block; margin: 10px 0"
@click="selectAvatarExportLocalGroup(group)">
{{ group }} ({{ getLocalAvatarFavoriteGroupLength(group) }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<br />
<el-input
v-model="avatarExportContent"
type="textarea"
size="mini"
rows="15"
size="small"
:rows="15"
resize="none"
readonly
style="margin-top: 15px"
@click.native="handleCopyAvatarExportData"></el-input>
</safe-dialog>
@click="handleCopyAvatarExportData"></el-input>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { ArrowDown } from '@element-plus/icons-vue';
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useAvatarStore, useFavoriteStore } from '../../../stores';
const { t } = useI18n();
const { proxy } = getCurrentInstance();
const props = defineProps({
avatarExportDialogVisible: {
@@ -106,8 +110,7 @@
localAvatarFavoriteGroups
} = storeToRefs(favoriteStore);
const { getLocalAvatarFavoriteGroupLength } = favoriteStore;
const avatarStore = useAvatarStore();
const { cachedAvatars } = storeToRefs(avatarStore);
const { cachedAvatars } = useAvatarStore();
const avatarExportContent = ref('');
const avatarExportFavoriteGroup = ref(null);
@@ -151,7 +154,7 @@
navigator.clipboard
.writeText(avatarExportContent.value)
.then(() => {
proxy.$message({
ElMessage({
message: 'Copied successfully!',
type: 'success',
duration: 2000
@@ -159,7 +162,7 @@
})
.catch((err) => {
console.error('Copy failed:', err);
proxy.$message.error('Copy failed!');
ElMessage.error('Copy failed!');
});
}
function updateAvatarExportDialog() {
@@ -209,7 +212,7 @@
});
for (let i = 0; i < localAvatarFavoritesList.value.length; ++i) {
const avatarId = localAvatarFavoritesList.value[i];
const ref = cachedAvatars.value.get(avatarId);
const ref = cachedAvatars.get(avatarId);
if (typeof ref !== 'undefined') {
lines.push(resText(ref));
}
@@ -1,7 +1,7 @@
<template>
<safe-dialog
ref="avatarImportDialogRef"
:visible.sync="isVisible"
<el-dialog
:z-index="avatarImportDialogIndex"
v-model="isVisible"
:title="t('dialog.avatar_import.header')"
width="650px">
<div style="display: flex; align-items: center; justify-content: space-between">
@@ -10,7 +10,7 @@
<div v-if="avatarImportDialog.progress">
{{ t('dialog.avatar_import.process_progress') }} {{ avatarImportDialog.progress }} /
{{ avatarImportDialog.progressTotal }}
<i class="el-icon-loading" style="margin: 0 5px"></i>
<el-icon style="margin: 0 5px"><Loading /></el-icon>
</div>
<el-button v-if="avatarImportDialog.loading" size="small" @click="cancelAvatarImport">
{{ t('dialog.avatar_import.cancel') }}
@@ -23,60 +23,62 @@
<el-input
v-model="avatarImportDialog.input"
type="textarea"
size="mini"
rows="10"
size="small"
:rows="10"
resize="none"
style="margin-top: 10px"></el-input>
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
<div>
<el-dropdown trigger="click" size="small" @click.native.stop>
<el-button size="mini">
<el-dropdown trigger="click" size="small" style="margin-right: 5px" @click.stop>
<el-button size="small">
<span v-if="avatarImportDialog.avatarImportFavoriteGroup">
{{ avatarImportDialog.avatarImportFavoriteGroup.displayName }} ({{
avatarImportDialog.avatarImportFavoriteGroup.count
}}/{{ avatarImportDialog.avatarImportFavoriteGroup.capacity }})
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
<span v-else>
{{ t('dialog.avatar_import.select_group_placeholder') }}
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
</el-button>
<el-dropdown-menu slot="dropdown">
<template v-for="groupAPI in favoriteAvatarGroups">
<el-dropdown-item
:key="groupAPI.name"
style="display: block; margin: 10px 0"
:disabled="groupAPI.count >= groupAPI.capacity"
@click.native="selectAvatarImportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
<template #dropdown>
<el-dropdown-menu>
<template v-for="groupAPI in favoriteAvatarGroups" :key="groupAPI.name">
<el-dropdown-item
style="display: block; margin: 10px 0"
:disabled="groupAPI.count >= groupAPI.capacity"
@click="selectAvatarImportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown trigger="click" size="small" style="margin: 5px" @click.native.stop>
<el-button size="mini">
<el-dropdown trigger="click" size="small">
<el-button size="small">
<span v-if="avatarImportDialog.avatarImportLocalFavoriteGroup">
{{ avatarImportDialog.avatarImportLocalFavoriteGroup }} ({{
getLocalAvatarFavoriteGroupLength(avatarImportDialog.avatarImportLocalFavoriteGroup)
}})
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
<span v-else>
{{ t('dialog.avatar_import.select_group_placeholder') }}
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
</el-button>
<el-dropdown-menu slot="dropdown">
<template v-for="group in localAvatarFavoriteGroups">
<el-dropdown-item
:key="group"
style="display: block; margin: 10px 0"
@click.native="selectAvatarImportLocalGroup(group)">
{{ group }} ({{ getLocalAvatarFavoriteGroupLength(group) }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
<template #dropdown>
<el-dropdown-menu>
<template v-for="group in localAvatarFavoriteGroups" :key="group">
<el-dropdown-item
style="display: block; margin: 10px 0"
@click="selectAvatarImportLocalGroup(group)">
{{ group }} ({{ getLocalAvatarFavoriteGroupLength(group) }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<span v-if="avatarImportDialog.avatarImportFavoriteGroup" style="margin-left: 5px">
{{ avatarImportTable.data.length }} /
@@ -105,7 +107,7 @@
</div>
</div>
<span v-if="avatarImportDialog.importProgress" style="margin: 10px">
<i class="el-icon-loading" style="margin-right: 5px"></i>
<el-icon style="margin-right: 5px"><Loading /></el-icon>
{{ t('dialog.avatar_import.import_progress') }}
{{ avatarImportDialog.importProgress }}/{{ avatarImportDialog.importProgressTotal }}
</span>
@@ -119,70 +121,73 @@
</h2>
<pre style="white-space: pre-wrap; font-size: 12px" v-text="avatarImportDialog.errors"></pre>
</template>
<data-tables v-loading="avatarImportDialog.loading" v-bind="avatarImportTable" style="margin-top: 10px">
<DataTable v-loading="avatarImportDialog.loading" v-bind="avatarImportTable" style="margin-top: 10px">
<el-table-column :label="t('table.import.image')" width="70" prop="thumbnailImageUrl">
<template slot-scope="scope">
<el-popover placement="right" height="500px" trigger="hover">
<img slot="reference" v-lazy="scope.row.thumbnailImageUrl" class="friends-list-avatar" />
<template #default="{ row }">
<el-popover placement="right" :width="500" trigger="hover">
<template #reference>
<img :src="row.thumbnailImageUrl" class="friends-list-avatar" loading="lazy" />
</template>
<img
v-lazy="scope.row.imageUrl"
class="friends-list-avatar"
style="height: 500px; cursor: pointer"
@click="showFullscreenImageDialog(scope.row.imageUrl)" />
:src="row.imageUrl"
:class="['friends-list-avatar', 'x-popover-image']"
style="cursor: pointer"
@click="showFullscreenImageDialog(row.imageUrl)"
loading="lazy" />
</el-popover>
</template>
</el-table-column>
<el-table-column :label="t('table.import.name')" prop="name">
<template slot-scope="scope">
<span class="x-link" @click="showAvatarDialog(scope.row.id)">
{{ scope.row.name }}
<template #default="{ row }">
<span class="x-link" @click="showAvatarDialog(row.id)">
{{ row.name }}
</span>
</template>
</el-table-column>
<el-table-column :label="t('table.import.author')" width="120" prop="authorName">
<template slot-scope="scope">
<span class="x-link" @click="showUserDialog(scope.row.authorId)">
{{ scope.row.authorName }}
<template #default="{ row }">
<span class="x-link" @click="showUserDialog(row.authorId)">
{{ row.authorName }}
</span>
</template>
</el-table-column>
<el-table-column :label="t('table.import.status')" width="70" prop="releaseStatus">
<template slot-scope="scope">
<template #default="{ row }">
<span
:style="{
color:
scope.row.releaseStatus === 'public'
row.releaseStatus === 'public'
? '#67c23a'
: scope.row.releaseStatus === 'private'
: row.releaseStatus === 'private'
? '#f56c6c'
: undefined
}">
{{ scope.row.releaseStatus.charAt(0).toUpperCase() + scope.row.releaseStatus.slice(1) }}
{{ row.releaseStatus.charAt(0).toUpperCase() + row.releaseStatus.slice(1) }}
</span>
</template>
</el-table-column>
<el-table-column :label="t('table.import.action')" width="90" align="right">
<template slot-scope="scope">
<el-button type="text" icon="el-icon-close" size="mini" @click="deleteItemAvatarImport(scope.row)">
</el-button>
<template #default="{ row }">
<el-button type="text" :icon="Close" size="small" @click="deleteItemAvatarImport(row)"> </el-button>
</template>
</el-table-column>
</data-tables>
</safe-dialog>
</DataTable>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { Close, Loading, ArrowDown } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { ref, computed, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { avatarRequest, favoriteRequest } from '../../../api';
import { adjustDialogZ, removeFromArray } from '../../../shared/utils';
import { getNextDialogIndex, removeFromArray } from '../../../shared/utils';
import { useAvatarStore, useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores';
const emit = defineEmits(['update:avatarImportDialogInput']);
const { t } = useI18n();
const { proxy } = getCurrentInstance();
const { showUserDialog } = useUserStore();
const { favoriteAvatarGroups, avatarImportDialogInput, avatarImportDialogVisible, localAvatarFavoriteGroups } =
storeToRefs(useFavoriteStore());
@@ -212,7 +217,7 @@
layout: 'table'
});
const avatarImportDialogRef = ref(null);
const avatarImportDialogIndex = ref(2000);
const isVisible = computed({
get() {
@@ -227,7 +232,7 @@
() => avatarImportDialogVisible.value,
(value) => {
if (value) {
adjustDialogZ(avatarImportDialogRef.value.$el);
avatarImportDialogIndex.value = getNextDialogIndex();
clearAvatarImportTable();
resetAvatarImport();
if (avatarImportDialogInput.value) {
@@ -318,7 +323,7 @@
})
.then((args) => {
if (message) {
proxy.$message({
ElMessage({
message: 'Avatar added to favorites',
type: 'success'
});
@@ -334,7 +339,7 @@
D.loading = true;
const data = [...avatarImportTable.value.data].reverse();
D.importProgressTotal = data.length;
let ref = '';
let ref = null;
try {
for (let i = data.length - 1; i >= 0; i--) {
if (!D.loading || !isVisible.value) {
@@ -351,7 +356,7 @@
D.importProgress++;
}
} catch (err) {
D.errors = `Name: ${ref.name}\nAvatarId: ${ref.id}\n${err}\n\n`;
D.errors = `Name: ${ref?.name}\nAvatarId: ${ref?.id}\n${err}\n\n`;
} finally {
D.importProgress = 0;
D.importProgressTotal = 0;
@@ -1,55 +1,60 @@
<template>
<safe-dialog
:visible.sync="isDialogVisible"
<el-dialog
v-model="isDialogVisible"
class="x-dialog"
:title="t('dialog.friend_export.header')"
width="650px"
destroy-on-close>
<el-dropdown trigger="click" size="small" @click.native.stop>
<el-button size="mini">
<el-dropdown trigger="click" size="small">
<el-button size="small">
<span v-if="friendExportFavoriteGroup">
{{ friendExportFavoriteGroup.displayName }} ({{ friendExportFavoriteGroup.count }}/{{
friendExportFavoriteGroup.capacity
}})
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
<span v-else>All Favorites <i class="el-icon-arrow-down el-icon--right"></i></span>
<span v-else
>All Favorites <el-icon class="el-icon--right"><ArrowDown /></el-icon
></span>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item style="display: block; margin: 10px 0" @click.native="selectFriendExportGroup(null)">
All Favorites
</el-dropdown-item>
<template v-for="groupAPI in favoriteFriendGroups">
<el-dropdown-item
:key="groupAPI.name"
style="display: block; margin: 10px 0"
@click.native="selectFriendExportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item style="display: block; margin: 10px 0" @click="selectFriendExportGroup(null)">
All Favorites
</el-dropdown-item>
</template>
</el-dropdown-menu>
<template v-for="groupAPI in favoriteFriendGroups" :key="groupAPI.name">
<el-dropdown-item
style="display: block; margin: 10px 0"
@click="selectFriendExportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<br />
<el-input
v-model="friendExportContent"
type="textarea"
size="mini"
rows="15"
size="small"
:rows="15"
resize="none"
readonly
style="margin-top: 15px"
@click.native="handleCopyFriendExportData"></el-input>
</safe-dialog>
@click="handleCopyFriendExportData"></el-input>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { ArrowDown } from '@element-plus/icons-vue';
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useFavoriteStore } from '../../../stores';
const { t } = useI18n();
const { proxy } = getCurrentInstance();
const props = defineProps({
friendExportDialogVisible: {
@@ -96,7 +101,7 @@
navigator.clipboard
.writeText(friendExportContent.value)
.then(() => {
proxy.$message({
ElMessage({
message: 'Copied successfully!',
type: 'success',
duration: 2000
@@ -104,7 +109,7 @@
})
.catch((err) => {
console.error('Copy failed:', err);
proxy.$message.error('Copy failed!');
ElMessage.error('Copy failed!');
});
}
@@ -1,7 +1,7 @@
<template>
<safe-dialog
ref="friendImportDialogRef"
:visible.sync="isVisible"
<el-dialog
:z-index="friendImportDialogIndex"
v-model="isVisible"
:title="t('dialog.friend_import.header')"
width="650px">
<div style="display: flex; align-items: center; justify-content: space-between">
@@ -10,7 +10,7 @@
<div v-if="friendImportDialog.progress">
{{ t('dialog.friend_import.process_progress') }} {{ friendImportDialog.progress }} /
{{ friendImportDialog.progressTotal }}
<i class="el-icon-loading" style="margin: 0 5px"></i>
<el-icon style="margin: 0 5px"><Loading /></el-icon>
</div>
<el-button v-if="friendImportDialog.loading" size="small" @click="cancelFriendImport">
{{ t('dialog.friend_import.cancel') }}
@@ -23,36 +23,37 @@
<el-input
v-model="friendImportDialog.input"
type="textarea"
size="mini"
rows="10"
size="small"
:rows="10"
resize="none"
style="margin-top: 10px" />
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
<div>
<el-dropdown trigger="click" size="small" @click.native.stop>
<el-button size="mini">
<el-dropdown trigger="click" size="small">
<el-button size="small">
<span v-if="friendImportDialog.friendImportFavoriteGroup">
{{ friendImportDialog.friendImportFavoriteGroup.displayName }} ({{
friendImportDialog.friendImportFavoriteGroup.count
}}/{{ friendImportDialog.friendImportFavoriteGroup.capacity }})
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
<span v-else
>{{ t('dialog.friend_import.select_group_placeholder') }}
<i class="el-icon-arrow-down el-icon--right"></i
<el-icon class="el-icon--right"><ArrowDown /></el-icon
></span>
</el-button>
<el-dropdown-menu slot="dropdown">
<template v-for="groupAPI in favoriteFriendGroups">
<el-dropdown-item
:key="groupAPI.name"
style="display: block; margin: 10px 0"
:disabled="groupAPI.count >= groupAPI.capacity"
@click.native="selectFriendImportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
<template #dropdown>
<el-dropdown-menu>
<template v-for="groupAPI in favoriteFriendGroups" :key="groupAPI.name">
<el-dropdown-item
style="display: block; margin: 10px 0"
:disabled="groupAPI.count >= groupAPI.capacity"
@click="selectFriendImportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<span v-if="friendImportDialog.friendImportFavoriteGroup" style="margin-left: 5px">
{{ friendImportTable.data.length }} /
@@ -77,7 +78,7 @@
</div>
</div>
<span v-if="friendImportDialog.importProgress" style="margin: 10px">
<i class="el-icon-loading" style="margin-right: 5px"></i>
<el-icon style="margin-right: 5px"><Loading /></el-icon>
{{ t('dialog.friend_import.import_progress') }} {{ friendImportDialog.importProgress }}/{{
friendImportDialog.importProgressTotal
}}
@@ -90,47 +91,47 @@
<h2 style="font-weight: bold; margin: 5px 0">{{ t('dialog.friend_import.errors') }}</h2>
<pre style="white-space: pre-wrap; font-size: 12px" v-text="friendImportDialog.errors"></pre>
</template>
<data-tables v-loading="friendImportDialog.loading" v-bind="friendImportTable" style="margin-top: 10px">
<DataTable v-loading="friendImportDialog.loading" v-bind="friendImportTable" style="margin-top: 10px">
<el-table-column :label="t('table.import.image')" width="70" prop="currentAvatarThumbnailImageUrl">
<template slot-scope="scope">
<el-popover placement="right" height="500px" trigger="hover">
<template slot="reference">
<img class="friends-list-avatar" :src="userImage(scope.row)" />
<template #default="{ row }">
<el-popover placement="right" :width="500" trigger="hover">
<template #reference>
<img class="friends-list-avatar" :src="userImage(row)" />
</template>
<img
class="friends-list-avatar"
:src="userImageFull(scope.row)"
style="height: 500px; cursor: pointer"
@click="showFullscreenImageDialog(userImageFull(scope.row))" />
:src="userImageFull(row)"
:class="['friends-list-avatar', 'x-popover-image']"
style="cursor: pointer"
@click="showFullscreenImageDialog(userImageFull(row))" />
</el-popover>
</template>
</el-table-column>
<el-table-column :label="t('table.import.name')" prop="displayName">
<template slot-scope="scope">
<span class="x-link" :title="scope.row.displayName" @click="showUserDialog(scope.row.id)">
{{ scope.row.displayName }}
<template #default="{ row }">
<span class="x-link" :title="row.displayName" @click="showUserDialog(row.id)">
{{ row.displayName }}
</span>
</template>
</el-table-column>
<el-table-column :label="t('table.import.action')" width="90" align="right">
<template slot-scope="scope">
<el-button type="text" icon="el-icon-close" size="mini" @click="deleteItemFriendImport(scope.row)">
</el-button>
<template #default="{ row }">
<el-button type="text" :icon="Close" size="small" @click="deleteItemFriendImport(row)"> </el-button>
</template>
</el-table-column>
</data-tables>
</safe-dialog>
</DataTable>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch, getCurrentInstance } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n-bridge';
import { favoriteRequest, userRequest } from '../../../api';
import { adjustDialogZ, removeFromArray, userImage, userImageFull } from '../../../shared/utils';
import { useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores';
import { Close, Loading, ArrowDown } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
const { proxy } = getCurrentInstance();
import { ref, computed, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { favoriteRequest, userRequest } from '../../../api';
import { getNextDialogIndex, removeFromArray, userImage, userImageFull } from '../../../shared/utils';
import { useFavoriteStore, useGalleryStore, useUserStore } from '../../../stores';
const { t } = useI18n();
@@ -157,12 +158,12 @@
data: [],
tableProps: {
stripe: true,
size: 'mini'
size: 'small'
},
layout: 'table'
});
const friendImportDialogRef = ref(null);
const friendImportDialogIndex = ref(2000);
const isVisible = computed({
get() {
@@ -177,7 +178,7 @@
() => friendImportDialogVisible.value,
(value) => {
if (value) {
adjustDialogZ(friendImportDialogRef.value.$el);
friendImportDialogIndex.value = getNextDialogIndex();
clearFriendImportTable();
resetFriendImport();
if (friendImportDialogInput.value) {
@@ -211,7 +212,7 @@
}
const data = [...friendImportTable.value.data].reverse();
D.importProgressTotal = data.length;
let ref = '';
let ref = null;
try {
for (let i = data.length - 1; i >= 0; i--) {
if (!D.loading || !isVisible.value) {
@@ -224,7 +225,7 @@
D.importProgress++;
}
} catch (err) {
D.errors = `Name: ${ref.displayName}\nUserId: ${ref.id}\n${err}\n\n`;
D.errors = `Name: ${ref?.displayName}\nUserId: ${ref?.id}\n${err}\n\n`;
} finally {
D.importProgress = 0;
D.importProgressTotal = 0;
@@ -240,7 +241,7 @@
})
.then((args) => {
if (message) {
proxy.$message({
ElMessage({
message: 'Friend added to favorites',
type: 'success'
});
@@ -1,70 +1,70 @@
<template>
<safe-dialog :visible.sync="isDialogVisible" :title="t('dialog.world_export.header')" width="650px">
<el-dialog v-model="isDialogVisible" :title="t('dialog.world_export.header')" width="650px">
<el-checkbox-group
v-model="exportSelectedOptions"
style="margin-bottom: 10px"
@change="updateWorldExportDialog">
<template v-for="option in exportSelectOptions">
<el-checkbox :key="option.value" :label="option.label"></el-checkbox>
<template v-for="option in exportSelectOptions" :key="option.value">
<el-checkbox :label="option.label"></el-checkbox>
</template>
</el-checkbox-group>
<el-dropdown trigger="click" size="small" @click.native.stop>
<el-button size="mini">
<el-dropdown trigger="click" size="small">
<el-button size="small">
<span v-if="worldExportFavoriteGroup">
{{ worldExportFavoriteGroup.displayName }} ({{ worldExportFavoriteGroup.count }}/{{
worldExportFavoriteGroup.capacity
}})
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
<span v-else>
All Favorites
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item style="display: block; margin: 10px 0" @click.native="selectWorldExportGroup(null)">
None
</el-dropdown-item>
<template v-for="groupAPI in favoriteWorldGroups">
<el-dropdown-item
:key="groupAPI.name"
style="display: block; margin: 10px 0"
@click.native="selectWorldExportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item style="display: block; margin: 10px 0" @click="selectWorldExportGroup(null)">
None
</el-dropdown-item>
</template>
</el-dropdown-menu>
<template v-for="groupAPI in favoriteWorldGroups" :key="groupAPI.name">
<el-dropdown-item
style="display: block; margin: 10px 0"
@click="selectWorldExportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown trigger="click" size="small" style="margin-left: 10px" @click.native.stop>
<el-button size="mini">
<el-dropdown trigger="click" size="small" style="margin-left: 10px">
<el-button size="small">
<span v-if="worldExportLocalFavoriteGroup">
{{ worldExportLocalFavoriteGroup }} ({{
getLocalWorldFavoriteGroupLength(worldExportLocalFavoriteGroup)
}})
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
<span v-else>
Select Group
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
style="display: block; margin: 10px 0"
@click.native="selectWorldExportLocalGroup(null)">
None
</el-dropdown-item>
<template v-for="group in localWorldFavoriteGroups">
<el-dropdown-item
:key="group"
style="display: block; margin: 10px 0"
@click.native="selectWorldExportLocalGroup(group)">
{{ group }} ({{ localWorldFavorites[group].length }})
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item style="display: block; margin: 10px 0" @click="selectWorldExportLocalGroup(null)">
None
</el-dropdown-item>
</template>
</el-dropdown-menu>
<template v-for="group in localWorldFavoriteGroups" :key="group">
<el-dropdown-item
style="display: block; margin: 10px 0"
@click="selectWorldExportLocalGroup(group)">
{{ group }} ({{ localWorldFavorites[group].length }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<br />
@@ -72,18 +72,21 @@
<el-input
v-model="worldExportContent"
type="textarea"
size="mini"
rows="15"
size="small"
:rows="15"
resize="none"
readonly
style="margin-top: 15px"
@click.native="handleCopyWorldExportData"></el-input>
</safe-dialog>
@click="handleCopyWorldExportData"></el-input>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { ArrowDown } from '@element-plus/icons-vue';
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useFavoriteStore, useWorldStore } from '../../../stores';
@@ -97,7 +100,6 @@
const emit = defineEmits(['update:worldExportDialogVisible']);
const { t } = useI18n();
const { proxy } = getCurrentInstance();
const favoriteStore = useFavoriteStore();
const {
@@ -108,7 +110,7 @@
localWorldFavoritesList
} = storeToRefs(favoriteStore);
const { getLocalWorldFavoriteGroupLength } = favoriteStore;
const { cachedWorlds } = storeToRefs(useWorldStore());
const { cachedWorlds } = useWorldStore();
const worldExportContent = ref('');
const worldExportFavoriteGroup = ref(null);
@@ -154,7 +156,7 @@
navigator.clipboard
.writeText(worldExportContent.value)
.then(() => {
proxy.$message({
ElMessage({
message: 'Copied successfully!',
type: 'success',
duration: 2000
@@ -162,7 +164,7 @@
})
.catch((err) => {
console.error('Copy failed:', err);
proxy.$message.error('Copy failed!');
ElMessage.error('Copy failed!');
});
}
@@ -214,7 +216,7 @@
});
for (let i = 0; i < localWorldFavoritesList.value.length; ++i) {
const worldId = localWorldFavoritesList.value[i];
const ref = cachedWorlds.value.get(worldId);
const ref = cachedWorlds.get(worldId);
if (typeof ref !== 'undefined') {
lines.push(resText(ref));
}
@@ -1,7 +1,7 @@
<template>
<safe-dialog
ref="worldImportDialogRef"
:visible.sync="isVisible"
<el-dialog
:z-index="worldImportDialogIndex"
v-model="isVisible"
:title="t('dialog.world_import.header')"
width="650px"
class="x-dialog">
@@ -11,7 +11,7 @@
<div v-if="worldImportDialog.progress">
{{ t('dialog.world_import.process_progress') }}
{{ worldImportDialog.progress }} / {{ worldImportDialog.progressTotal }}
<i class="el-icon-loading" style="margin: 0 5px"></i>
<el-icon style="margin: 0 5px"><Loading /></el-icon>
</div>
<el-button v-if="worldImportDialog.loading" size="small" @click="cancelWorldImport">
{{ t('dialog.world_import.cancel') }}
@@ -24,60 +24,62 @@
<el-input
v-model="worldImportDialog.input"
type="textarea"
size="mini"
rows="10"
size="small"
:rows="10"
resize="none"
style="margin-top: 10px"></el-input>
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 5px">
<div>
<el-dropdown trigger="click" size="small" style="margin-right: 5px" @click.native.stop>
<el-button size="mini">
<el-dropdown trigger="click" size="small" style="margin-right: 5px" @click.stop>
<el-button size="small">
<span v-if="worldImportDialog.worldImportFavoriteGroup">
{{ worldImportDialog.worldImportFavoriteGroup.displayName }}
({{ worldImportDialog.worldImportFavoriteGroup.count }}/{{
worldImportDialog.worldImportFavoriteGroup.capacity
}})
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
<span v-else>
{{ t('dialog.world_import.select_vrchat_group_placeholder') }}
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
</el-button>
<el-dropdown-menu slot="dropdown">
<template v-for="groupAPI in favoriteWorldGroups">
<el-dropdown-item
:key="groupAPI.name"
style="display: block; margin: 10px 0"
:disabled="groupAPI.count >= groupAPI.capacity"
@click.native="selectWorldImportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
<template #dropdown>
<el-dropdown-menu>
<template v-for="groupAPI in favoriteWorldGroups" :key="groupAPI.name">
<el-dropdown-item
style="display: block; margin: 10px 0"
:disabled="groupAPI.count >= groupAPI.capacity"
@click="selectWorldImportGroup(groupAPI)">
{{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown trigger="click" size="small" style="margin: 5px" @click.native.stop>
<el-button size="mini">
<el-dropdown trigger="click" size="small" @click.stop>
<el-button size="small">
<span v-if="worldImportDialog.worldImportLocalFavoriteGroup">
{{ worldImportDialog.worldImportLocalFavoriteGroup }}
({{ getLocalWorldFavoriteGroupLength(worldImportDialog.worldImportLocalFavoriteGroup) }})
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
<span v-else>
{{ t('dialog.world_import.select_local_group_placeholder') }}
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</span>
</el-button>
<el-dropdown-menu slot="dropdown">
<template v-for="group in localWorldFavoriteGroups">
<el-dropdown-item
:key="group"
style="display: block; margin: 10px 0"
@click.native="selectWorldImportLocalGroup(group)">
{{ group }} ({{ getLocalWorldFavoriteGroupLength(group) }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
<template #dropdown>
<el-dropdown-menu>
<template v-for="group in localWorldFavoriteGroups" :key="group">
<el-dropdown-item
style="display: block; margin: 10px 0"
@click="selectWorldImportLocalGroup(group)">
{{ group }} ({{ getLocalWorldFavoriteGroupLength(group) }})
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<span v-if="worldImportDialog.worldImportFavoriteGroup" style="margin-left: 5px">
{{ worldImportTable.data.length }} /
@@ -106,7 +108,7 @@
</div>
</div>
<span v-if="worldImportDialog.importProgress" style="margin: 10px">
<i class="el-icon-loading" style="margin-right: 5px"></i>
<el-icon style="margin-right: 5px"><Loading /></el-icon>
{{ t('dialog.world_import.import_progress') }}
{{ worldImportDialog.importProgress }}/{{ worldImportDialog.importProgressTotal }}
</span>
@@ -120,67 +122,64 @@
</h2>
<pre style="white-space: pre-wrap; font-size: 12px" v-text="worldImportDialog.errors"></pre>
</template>
<data-tables v-loading="worldImportDialog.loading" v-bind="worldImportTable" style="margin-top: 10px">
<DataTable v-loading="worldImportDialog.loading" v-bind="worldImportTable" style="margin-top: 10px">
<el-table-column :label="t('table.import.image')" width="70" prop="thumbnailImageUrl">
<template slot-scope="scope">
<el-popover placement="right" height="500px" trigger="hover">
<img slot="reference" v-lazy="scope.row.thumbnailImageUrl" class="friends-list-avatar" />
<template #default="{ row }">
<el-popover placement="right" :width="500" trigger="hover">
<template #reference>
<img :src="row.thumbnailImageUrl" class="friends-list-avatar" loading="lazy" />
</template>
<img
v-lazy="scope.row.imageUrl"
class="friends-list-avatar"
style="height: 500px; cursor: pointer"
@click="showFullscreenImageDialog(scope.row.imageUrl)" />
:src="row.imageUrl"
:class="['friends-list-avatar', 'x-popover-image']"
style="cursor: pointer"
@click="showFullscreenImageDialog(row.imageUrl)"
loading="lazy" />
</el-popover>
</template>
</el-table-column>
<el-table-column :label="t('table.import.name')" prop="name">
<template slot-scope="scope">
<span class="x-link" @click="showWorldDialog(scope.row.id)" v-text="scope.row.name"></span>
<template #default="{ row }">
<span class="x-link" @click="showWorldDialog(row.id)" v-text="row.name"></span>
</template>
</el-table-column>
<el-table-column :label="t('table.import.author')" width="120" prop="authorName">
<template slot-scope="scope">
<span
class="x-link"
@click="showUserDialog(scope.row.authorId)"
v-text="scope.row.authorName"></span>
<template #default="{ row }">
<span class="x-link" @click="showUserDialog(row.authorId)" v-text="row.authorName"></span>
</template>
</el-table-column>
<el-table-column :label="t('table.import.status')" width="70" prop="releaseStatus">
<template slot-scope="scope">
<template #default="{ row }">
<span
:style="{
color:
scope.row.releaseStatus === 'public'
row.releaseStatus === 'public'
? '#67c23a'
: scope.row.releaseStatus === 'private'
: row.releaseStatus === 'private'
? '#f56c6c'
: undefined
}"
v-text="
scope.row.releaseStatus.charAt(0).toUpperCase() + scope.row.releaseStatus.slice(1)
"></span>
v-text="row.releaseStatus.charAt(0).toUpperCase() + row.releaseStatus.slice(1)"></span>
</template>
</el-table-column>
<el-table-column :label="t('table.import.action')" width="90" align="right">
<template slot-scope="scope">
<el-button
type="text"
icon="el-icon-close"
size="mini"
@click="deleteItemWorldImport(scope.row)"></el-button>
<template #default="{ row }">
<el-button type="text" :icon="Close" size="small" @click="deleteItemWorldImport(row)"></el-button>
</template>
</el-table-column>
</data-tables>
</safe-dialog>
</DataTable>
</el-dialog>
</template>
<script setup>
import { ref, watch, computed, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { Close, Loading, ArrowDown } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { ref, watch, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { favoriteRequest, worldRequest } from '../../../api';
import { adjustDialogZ, removeFromArray } from '../../../shared/utils';
import { getNextDialogIndex, removeFromArray } from '../../../shared/utils';
import { useFavoriteStore, useGalleryStore, useUserStore, useWorldStore } from '../../../stores';
const { showUserDialog } = useUserStore();
@@ -192,10 +191,9 @@
const emit = defineEmits(['update:worldImportDialogInput']);
const { proxy } = getCurrentInstance();
const { t } = useI18n();
const worldImportDialogRef = ref(null);
const worldImportDialogIndex = ref(2000);
const worldImportDialog = ref({
loading: false,
@@ -214,7 +212,7 @@
data: [],
tableProps: {
stripe: true,
size: 'mini'
size: 'small'
},
layout: 'table'
});
@@ -232,7 +230,7 @@
() => worldImportDialogVisible.value,
(visible) => {
if (visible) {
adjustDialogZ(worldImportDialogRef.value.$el);
worldImportDialogIndex.value = getNextDialogIndex();
clearWorldImportTable();
resetWorldImport();
if (worldImportDialogInput.value) {
@@ -356,7 +354,7 @@
})
.then((args) => {
if (message) {
proxy.$message({
ElMessage({
message: 'World added to favorites',
type: 'success'
});