mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-30 04:03:48 +02:00
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:
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="changeAvatarImageDialogVisible"
|
||||
:model-value="changeAvatarImageDialogVisible"
|
||||
:title="t('dialog.change_content_image.avatar')"
|
||||
width="850px"
|
||||
append-to-body
|
||||
@@ -16,73 +16,47 @@
|
||||
<span>{{ t('dialog.change_content_image.description') }}</span>
|
||||
<br />
|
||||
<el-button-group style="padding-bottom: 10px; padding-top: 10px">
|
||||
<el-button type="default" size="small" icon="el-icon-refresh" @click="refresh">
|
||||
{{ t('dialog.change_content_image.refresh') }}
|
||||
</el-button>
|
||||
<el-button type="default" size="small" icon="el-icon-upload2" @click="uploadAvatarImage">
|
||||
<el-button type="default" size="small" :icon="Upload" @click="uploadAvatarImage">
|
||||
{{ t('dialog.change_content_image.upload') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<br />
|
||||
<div v-for="image in previousImagesTable" :key="image.version" style="display: inline-block">
|
||||
<div
|
||||
v-if="image.file"
|
||||
class="x-change-image-item"
|
||||
style="cursor: pointer"
|
||||
:class="{ 'current-image': compareCurrentImage(image) }"
|
||||
@click="setAvatarImage(image)">
|
||||
<img v-lazy="image.file.url" class="image" />
|
||||
</div>
|
||||
<div class="x-change-image-item">
|
||||
<img :src="currentImageUrl" class="img-size" loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Upload } from '@element-plus/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { imageRequest } from '../../../api';
|
||||
import { AppGlobal } from '../../../service/appConfig';
|
||||
import { $throw } from '../../../service/request';
|
||||
import { extractFileId } from '../../../shared/utils';
|
||||
import { useAvatarStore, useGalleryStore } from '../../../stores';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { avatarRequest } from '../../../api';
|
||||
import { useAvatarStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const { avatarDialog } = storeToRefs(useAvatarStore());
|
||||
const { previousImagesTable } = storeToRefs(useGalleryStore());
|
||||
const { applyAvatar } = useAvatarStore();
|
||||
|
||||
const props = defineProps({
|
||||
changeAvatarImageDialogVisible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
required: true
|
||||
},
|
||||
previousImagesFileId: {
|
||||
previousImageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const changeAvatarImageDialogLoading = ref(false);
|
||||
const avatarImage = ref({
|
||||
base64File: '',
|
||||
fileMd5: '',
|
||||
base64SignatureFile: '',
|
||||
signatureMd5: '',
|
||||
fileId: '',
|
||||
avatarId: ''
|
||||
});
|
||||
const currentImageUrl = computed(() => props.previousImageUrl);
|
||||
|
||||
const emit = defineEmits(['update:changeAvatarImageDialogVisible', 'refresh']);
|
||||
|
||||
function refresh() {
|
||||
emit('refresh', 'Change');
|
||||
}
|
||||
const emit = defineEmits(['update:changeAvatarImageDialogVisible', 'update:previousImageUrl']);
|
||||
|
||||
function closeDialog() {
|
||||
emit('update:changeAvatarImageDialogVisible', false);
|
||||
@@ -93,23 +67,9 @@
|
||||
return response;
|
||||
}
|
||||
|
||||
async function genMd5(file) {
|
||||
const response = await AppApi.MD5File(file);
|
||||
return response;
|
||||
}
|
||||
|
||||
async function genSig(file) {
|
||||
const response = await AppApi.SignFile(file);
|
||||
return response;
|
||||
}
|
||||
|
||||
async function genLength(file) {
|
||||
const response = await AppApi.FileLength(file);
|
||||
return response;
|
||||
}
|
||||
|
||||
function onFileChangeAvatarImage(e) {
|
||||
const clearFile = function () {
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
const fileInput = /** @type{HTMLInputElement} */ (document.querySelector('#AvatarImageUploadButton'));
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
@@ -124,7 +84,7 @@
|
||||
// validate file
|
||||
if (files[0].size >= 100000000) {
|
||||
// 100MB
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.too_large'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -132,7 +92,7 @@
|
||||
return;
|
||||
}
|
||||
if (!files[0].type.match(/image.*/)) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.not_image'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -141,50 +101,14 @@
|
||||
}
|
||||
|
||||
const r = new FileReader();
|
||||
r.onload = async function (file) {
|
||||
r.onload = async function () {
|
||||
try {
|
||||
const base64File = await resizeImageToFitLimits(btoa(r.result.toString()));
|
||||
// 10MB
|
||||
const fileMd5 = await genMd5(base64File);
|
||||
const fileSizeInBytes = parseInt(file.total.toString(), 10);
|
||||
const base64SignatureFile = await genSig(base64File);
|
||||
const signatureMd5 = await genMd5(base64SignatureFile);
|
||||
const signatureSizeInBytes = parseInt(await genLength(base64SignatureFile), 10);
|
||||
|
||||
const avatarId = avatarDialog.value.id;
|
||||
const { imageUrl } = avatarDialog.value.ref;
|
||||
|
||||
const fileId = extractFileId(imageUrl);
|
||||
if (!fileId) {
|
||||
$message({
|
||||
message: t('message.avatar.image_invalid'),
|
||||
type: 'error'
|
||||
});
|
||||
clearFile();
|
||||
return;
|
||||
}
|
||||
|
||||
avatarImage.value = {
|
||||
base64File,
|
||||
fileMd5,
|
||||
base64SignatureFile,
|
||||
signatureMd5,
|
||||
fileId,
|
||||
avatarId
|
||||
};
|
||||
const params = {
|
||||
fileMd5,
|
||||
fileSizeInBytes,
|
||||
signatureMd5,
|
||||
signatureSizeInBytes
|
||||
};
|
||||
|
||||
// Upload chaining
|
||||
await initiateUpload(params, fileId);
|
||||
await initiateUpload(base64File);
|
||||
} catch (error) {
|
||||
console.error('Avatar image upload process failed:', error);
|
||||
} finally {
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
clearFile();
|
||||
}
|
||||
};
|
||||
@@ -193,169 +117,32 @@
|
||||
r.readAsBinaryString(files[0]);
|
||||
}
|
||||
|
||||
// ------------ Upload Process Start ------------
|
||||
|
||||
async function initiateUpload(params, fileId) {
|
||||
const res = await imageRequest.uploadAvatarImage(params, fileId);
|
||||
return avatarImageInit(res);
|
||||
}
|
||||
|
||||
async function avatarImageInit(args) {
|
||||
const fileId = args.json.id;
|
||||
const fileVersion = args.json.versions[args.json.versions.length - 1].version;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadAvatarImageFileStart(params);
|
||||
return avatarImageFileStart(res);
|
||||
}
|
||||
|
||||
async function avatarImageFileStart(args) {
|
||||
const { url } = args.json;
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
url,
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
return uploadAvatarImageFileAWS(params);
|
||||
}
|
||||
|
||||
async function uploadAvatarImageFileAWS(params) {
|
||||
const json = await webApiService.execute({
|
||||
url: params.url,
|
||||
uploadFilePUT: true,
|
||||
fileData: avatarImage.value.base64File,
|
||||
fileMIME: 'image/png',
|
||||
headers: {
|
||||
'Content-MD5': avatarImage.value.fileMd5
|
||||
}
|
||||
async function initiateUpload(base64File) {
|
||||
const args = await avatarRequest.uploadAvatarImage(base64File);
|
||||
const fileUrl = args.json.versions[args.json.versions.length - 1].file.url;
|
||||
const avatarArgs = await avatarRequest.saveAvatar({
|
||||
id: avatarDialog.value.id,
|
||||
imageUrl: fileUrl
|
||||
});
|
||||
|
||||
if (json.status !== 200) {
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
$throw(json.status, 'Avatar image upload failed', params.url);
|
||||
}
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return avatarImageFileAWS(args);
|
||||
}
|
||||
|
||||
async function avatarImageFileAWS(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadAvatarImageFileFinish(params);
|
||||
return avatarImageFileFinish(res);
|
||||
}
|
||||
|
||||
async function avatarImageFileFinish(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadAvatarImageSigStart(params);
|
||||
return avatarImageSigStart(res);
|
||||
}
|
||||
|
||||
async function avatarImageSigStart(args) {
|
||||
const { url } = args.json;
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
url,
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
return uploadAvatarImageSigAWS(params);
|
||||
}
|
||||
|
||||
async function uploadAvatarImageSigAWS(params) {
|
||||
const json = await webApiService.execute({
|
||||
url: params.url,
|
||||
uploadFilePUT: true,
|
||||
fileData: avatarImage.value.base64SignatureFile,
|
||||
fileMIME: 'application/x-rsync-signature',
|
||||
headers: {
|
||||
'Content-MD5': avatarImage.value.signatureMd5
|
||||
}
|
||||
});
|
||||
|
||||
if (json.status !== 200) {
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
$throw(json.status, 'Avatar image upload failed', params.url);
|
||||
}
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return avatarImageSigAWS(args);
|
||||
}
|
||||
|
||||
async function avatarImageSigAWS(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadAvatarImageSigFinish(params);
|
||||
return avatarImageSigFinish(res);
|
||||
}
|
||||
|
||||
async function avatarImageSigFinish(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const parmas = {
|
||||
id: avatarImage.value.avatarId,
|
||||
imageUrl: `${AppGlobal.endpointDomain}/file/${fileId}/${fileVersion}/file`
|
||||
};
|
||||
const res = await imageRequest.setAvatarImage(parmas);
|
||||
return avatarImageSet(res);
|
||||
}
|
||||
|
||||
async function avatarImageSet(args) {
|
||||
applyAvatar(args.json);
|
||||
const ref = applyAvatar(avatarArgs.json);
|
||||
emit('update:previousImageUrl', ref.imageUrl);
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
if (args.json.imageUrl === args.params.imageUrl) {
|
||||
$message({
|
||||
message: t('message.avatar.image_changed'),
|
||||
type: 'success'
|
||||
});
|
||||
refresh();
|
||||
} else {
|
||||
$throw(0, 'Avatar image change failed', args.params.imageUrl);
|
||||
}
|
||||
}
|
||||
ElMessage({
|
||||
message: t('message.avatar.image_changed'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
// ------------ Upload Process End ------------
|
||||
// closeDialog();
|
||||
}
|
||||
|
||||
function uploadAvatarImage() {
|
||||
document.getElementById('AvatarImageUploadButton').click();
|
||||
}
|
||||
|
||||
function setAvatarImage(image) {
|
||||
changeAvatarImageDialogLoading.value = true;
|
||||
const parmas = {
|
||||
id: avatarDialog.value.id,
|
||||
imageUrl: `${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file`
|
||||
};
|
||||
imageRequest
|
||||
.setAvatarImage(parmas)
|
||||
.then((args) => applyAvatar(args.json))
|
||||
.finally(() => {
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
closeDialog();
|
||||
});
|
||||
}
|
||||
|
||||
function compareCurrentImage(image) {
|
||||
return (
|
||||
`${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file` ===
|
||||
avatarDialog.value.ref.imageUrl
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.img-size {
|
||||
width: 500px;
|
||||
height: 375px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
ref="setAvatarStylesDialog"
|
||||
class="x-dialog"
|
||||
:visible.sync="setAvatarStylesDialog.visible"
|
||||
v-model="setAvatarStylesDialog.visible"
|
||||
:title="t('dialog.set_avatar_styles.header')"
|
||||
width="400px"
|
||||
append-to-body>
|
||||
@@ -39,11 +39,11 @@
|
||||
</el-select>
|
||||
</div>
|
||||
<br />
|
||||
<div style="font-size: 12px; margin-top: 10px">{{ $t('dialog.set_world_tags.author_tags') }}<br /></div>
|
||||
<div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.author_tags') }}<br /></div>
|
||||
<el-input
|
||||
v-model="setAvatarStylesDialog.authorTags"
|
||||
type="textarea"
|
||||
size="mini"
|
||||
size="small"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
placeholder=""
|
||||
@@ -57,21 +57,18 @@
|
||||
t('dialog.set_avatar_styles.save')
|
||||
}}</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { watch, getCurrentInstance } from 'vue';
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { arraysMatch } from '../../../shared/utils';
|
||||
import { avatarRequest } from '../../../api';
|
||||
import { useAvatarStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const { applyAvatar } = useAvatarStore();
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
ref="setAvatarTagsDialog"
|
||||
class="x-dialog"
|
||||
:visible.sync="setAvatarTagsDialog.visible"
|
||||
v-model="setAvatarTagsDialog.visible"
|
||||
:title="t('dialog.set_avatar_tags.header')"
|
||||
width="770px"
|
||||
append-to-body>
|
||||
@@ -29,7 +29,7 @@
|
||||
<br />
|
||||
<el-input
|
||||
v-model="setAvatarTagsDialog.selectedTagsCsv"
|
||||
size="mini"
|
||||
size="small"
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
:placeholder="t('dialog.set_avatar_tags.custom_tags_placeholder')"
|
||||
style="margin-top: 10px"
|
||||
@@ -47,19 +47,18 @@
|
||||
<span style="margin-left: 5px"
|
||||
>{{ setAvatarTagsDialog.selectedCount }} / {{ setAvatarTagsDialog.ownAvatars.length }}</span
|
||||
>
|
||||
<span v-if="setAvatarTagsDialog.loading" style="margin-left: 5px">
|
||||
<i class="el-icon-loading"></i>
|
||||
</span>
|
||||
<el-icon v-if="setAvatarTagsDialog.loading" class="is-loading" style="margin-left: 5px"
|
||||
><Loading
|
||||
/></el-icon>
|
||||
<br />
|
||||
<div class="x-friend-list" style="margin-top: 10px; min-height: 60px; max-height: 280px">
|
||||
<div
|
||||
v-for="avatar in setAvatarTagsDialog.ownAvatars"
|
||||
:key="avatar.id"
|
||||
class="x-friend-item x-friend-item-border"
|
||||
style="width: 350px"
|
||||
:class="['item-width', 'x-friend-item', 'x-friend-item-border']"
|
||||
@click="showAvatarDialog(avatar.id)">
|
||||
<div class="avatar">
|
||||
<img v-if="avatar.thumbnailImageUrl" v-lazy="avatar.thumbnailImageUrl" />
|
||||
<img v-if="avatar.thumbnailImageUrl" :src="avatar.thumbnailImageUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="avatar.name"></span>
|
||||
@@ -76,7 +75,7 @@
|
||||
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
|
||||
<span class="extra" v-text="avatar.$tagString"></span>
|
||||
</div>
|
||||
<el-button type="text" size="mini" style="margin-left: 5px" @click.stop>
|
||||
<el-button type="text" size="small" style="margin-left: 5px" @click.stop>
|
||||
<el-checkbox v-model="avatar.$selected" @change="updateAvatarTagsSelection"></el-checkbox>
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -90,21 +89,21 @@
|
||||
t('dialog.set_avatar_tags.save')
|
||||
}}</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { Loading } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { avatarRequest } from '../../../api';
|
||||
import { useAvatarStore } from '../../../stores';
|
||||
|
||||
const { showAvatarDialog, applyAvatar } = useAvatarStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const props = defineProps({
|
||||
setAvatarTagsDialog: {
|
||||
type: Object,
|
||||
@@ -229,7 +228,7 @@
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Error saving avatar tags',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -273,4 +272,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.item-width {
|
||||
width: 335px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user