mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-06 22:46:06 +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:
+143
-65
@@ -1,11 +1,99 @@
|
||||
<script>
|
||||
// @ts-ignore
|
||||
import template from './app.pug';
|
||||
import Vue, { onMounted } from 'vue';
|
||||
<template>
|
||||
<!DOCTYPE html>
|
||||
<el-config-provider :locale="currentLocale">
|
||||
<div
|
||||
id="x-app"
|
||||
class="x-app"
|
||||
ondragenter="event.preventDefault()"
|
||||
ondragover="event.preventDefault()"
|
||||
ondrop="event.preventDefault()">
|
||||
<!-- ### Login ### -->
|
||||
<Login v-if="!watchState.isLoggedIn"></Login>
|
||||
|
||||
<VRCXUpdateDialog></VRCXUpdateDialog>
|
||||
|
||||
<template v-if="watchState.isLoggedIn">
|
||||
<!-- ### Menu ### -->
|
||||
<NavMenu></NavMenu>
|
||||
|
||||
<!-- ### Sidebar ### -->
|
||||
<Sidebar></Sidebar>
|
||||
|
||||
<!-- ### Tabs ### -->
|
||||
<Feed></Feed>
|
||||
|
||||
<GameLog></GameLog>
|
||||
|
||||
<PlayerList></PlayerList>
|
||||
|
||||
<Search></Search>
|
||||
|
||||
<Favorites></Favorites>
|
||||
|
||||
<FriendLog></FriendLog>
|
||||
|
||||
<Moderation></Moderation>
|
||||
|
||||
<Notification></Notification>
|
||||
|
||||
<FriendList></FriendList>
|
||||
|
||||
<Charts></Charts>
|
||||
|
||||
<Tools></Tools>
|
||||
|
||||
<Profile></Profile>
|
||||
|
||||
<Settings></Settings>
|
||||
|
||||
<!-- ## Dialogs ## -->
|
||||
<UserDialog></UserDialog>
|
||||
|
||||
<WorldDialog></WorldDialog>
|
||||
|
||||
<AvatarDialog></AvatarDialog>
|
||||
|
||||
<GroupDialog></GroupDialog>
|
||||
|
||||
<GroupMemberModerationDialog></GroupMemberModerationDialog>
|
||||
|
||||
<FullscreenImageDialog></FullscreenImageDialog>
|
||||
|
||||
<PreviousInstancesInfoDialog></PreviousInstancesInfoDialog>
|
||||
|
||||
<LaunchDialog></LaunchDialog>
|
||||
|
||||
<LaunchOptionsDialog></LaunchOptionsDialog>
|
||||
|
||||
<FriendImportDialog></FriendImportDialog>
|
||||
|
||||
<WorldImportDialog></WorldImportDialog>
|
||||
|
||||
<AvatarImportDialog></AvatarImportDialog>
|
||||
|
||||
<ChooseFavoriteGroupDialog></ChooseFavoriteGroupDialog>
|
||||
|
||||
<EditInviteMessageDialog></EditInviteMessageDialog>
|
||||
|
||||
<VRChatConfigDialog></VRChatConfigDialog>
|
||||
|
||||
<PrimaryPasswordDialog></PrimaryPasswordDialog>
|
||||
</template>
|
||||
</div>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { createGlobalStores } from './stores';
|
||||
import { watchState } from './service/watchState';
|
||||
|
||||
import '@fontsource/noto-sans-kr';
|
||||
import '@fontsource/noto-sans-jp';
|
||||
import '@fontsource/noto-sans-sc';
|
||||
import '@fontsource/noto-sans-tc';
|
||||
|
||||
import Login from './views/Login/Login.vue';
|
||||
import NavMenu from './components/NavMenu.vue';
|
||||
import Sidebar from './views/Sidebar/Sidebar.vue';
|
||||
@@ -28,7 +116,6 @@
|
||||
import AvatarDialog from './components/dialogs/AvatarDialog/AvatarDialog.vue';
|
||||
import GroupDialog from './components/dialogs/GroupDialog/GroupDialog.vue';
|
||||
import GroupMemberModerationDialog from './components/dialogs/GroupDialog/GroupMemberModerationDialog.vue';
|
||||
import GalleryDialog from './components/dialogs/GalleryDialog.vue';
|
||||
import FullscreenImageDialog from './components/dialogs/FullscreenImageDialog.vue';
|
||||
import PreviousInstancesInfoDialog from './components/dialogs/PreviousInstancesDialog/PreviousInstancesInfoDialog.vue';
|
||||
import LaunchDialog from './components/dialogs/LaunchDialog.vue';
|
||||
@@ -42,66 +129,57 @@
|
||||
import VRChatConfigDialog from './views/Settings/dialogs/VRChatConfigDialog.vue';
|
||||
import PrimaryPasswordDialog from './views/Settings/dialogs/PrimaryPasswordDialog.vue';
|
||||
|
||||
import { utils } from './shared/utils/_utils';
|
||||
import en from 'element-plus/es/locale/lang/en';
|
||||
import es from 'element-plus/es/locale/lang/es';
|
||||
import fr from 'element-plus/es/locale/lang/fr';
|
||||
import hu from 'element-plus/es/locale/lang/hu';
|
||||
import ja from 'element-plus/es/locale/lang/ja';
|
||||
import ko from 'element-plus/es/locale/lang/ko';
|
||||
import pl from 'element-plus/es/locale/lang/pl';
|
||||
import pt from 'element-plus/es/locale/lang/pt';
|
||||
import cs from 'element-plus/es/locale/lang/cs';
|
||||
import ru from 'element-plus/es/locale/lang/ru';
|
||||
import vi from 'element-plus/es/locale/lang/vi';
|
||||
import zhCN from 'element-plus/es/locale/lang/zh-CN';
|
||||
import zhTW from 'element-plus/es/locale/lang/zh-TW';
|
||||
import th from 'element-plus/es/locale/lang/th';
|
||||
|
||||
export default {
|
||||
template,
|
||||
components: {
|
||||
Login,
|
||||
NavMenu,
|
||||
Sidebar,
|
||||
Feed,
|
||||
GameLog,
|
||||
PlayerList,
|
||||
Search,
|
||||
Favorites,
|
||||
FriendLog,
|
||||
Moderation,
|
||||
Notification,
|
||||
FriendList,
|
||||
Charts,
|
||||
Tools,
|
||||
Profile,
|
||||
Settings,
|
||||
|
||||
UserDialog,
|
||||
WorldDialog,
|
||||
AvatarDialog,
|
||||
GroupDialog,
|
||||
GroupMemberModerationDialog,
|
||||
GalleryDialog,
|
||||
FullscreenImageDialog,
|
||||
PreviousInstancesInfoDialog,
|
||||
LaunchDialog,
|
||||
LaunchOptionsDialog,
|
||||
FriendImportDialog,
|
||||
WorldImportDialog,
|
||||
AvatarImportDialog,
|
||||
ChooseFavoriteGroupDialog,
|
||||
EditInviteMessageDialog,
|
||||
VRCXUpdateDialog,
|
||||
VRChatConfigDialog,
|
||||
PrimaryPasswordDialog
|
||||
},
|
||||
setup() {
|
||||
const store = createGlobalStores();
|
||||
Vue.prototype.store = store;
|
||||
Vue.prototype.utils = utils;
|
||||
|
||||
store.updateLoop.updateLoop();
|
||||
|
||||
onMounted(async () => {
|
||||
store.gameLog.getGameLogTable();
|
||||
await store.auth.migrateStoredUsers();
|
||||
store.auth.autoLoginAfterMounted();
|
||||
store.vrcx.checkAutoBackupRestoreVrcRegistry();
|
||||
store.game.checkVRChatDebugLogging();
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
watchState
|
||||
};
|
||||
}
|
||||
const langMap = {
|
||||
en: en,
|
||||
es: es,
|
||||
fr: fr,
|
||||
hu: hu,
|
||||
ja: ja,
|
||||
ko: ko,
|
||||
pl: pl,
|
||||
pt: pt,
|
||||
cs: cs,
|
||||
ru: ru,
|
||||
vi: vi,
|
||||
'zh-CN': zhCN,
|
||||
'zh-TW': zhTW,
|
||||
th: th
|
||||
};
|
||||
|
||||
const currentLocale = computed(() => {
|
||||
return langMap[locale.value] || en;
|
||||
});
|
||||
|
||||
const { locale } = useI18n();
|
||||
|
||||
const store = createGlobalStores();
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.$pinia = store;
|
||||
}
|
||||
|
||||
store.updateLoop.updateLoop();
|
||||
|
||||
onMounted(async () => {
|
||||
store.gameLog.getGameLogTable();
|
||||
await store.auth.migrateStoredUsers();
|
||||
store.auth.autoLoginAfterMounted();
|
||||
store.vrcx.checkAutoBackupRestoreVrcRegistry();
|
||||
store.game.checkVRChatDebugLogging();
|
||||
});
|
||||
</script>
|
||||
|
||||
-681
@@ -1,681 +0,0 @@
|
||||
<script>
|
||||
import '@fontsource/noto-sans-kr';
|
||||
import '@fontsource/noto-sans-jp';
|
||||
import '@fontsource/noto-sans-sc';
|
||||
import '@fontsource/noto-sans-tc';
|
||||
// @ts-ignore
|
||||
import pugTemplate from './vr.pug';
|
||||
import Vue, { onMounted, reactive, toRefs, nextTick } from 'vue';
|
||||
import Noty from 'noty';
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import MarqueeText from 'vue-marquee-text-component';
|
||||
import VrLocation from './components/VrLocation.vue';
|
||||
import { displayLocation } from './shared/utils/location';
|
||||
import { escapeTag, escapeTagRecursive } from './shared/utils/base/string';
|
||||
import { removeFromArray } from './shared/utils/base/array';
|
||||
import { timeToText } from './shared/utils/base/format';
|
||||
|
||||
import { i18n, t as $t } from './plugin/i18n.js';
|
||||
|
||||
export default {
|
||||
name: 'vr',
|
||||
template: pugTemplate,
|
||||
components: {
|
||||
'marquee-text': MarqueeText,
|
||||
location: VrLocation
|
||||
},
|
||||
setup() {
|
||||
const vrState = reactive({
|
||||
appType: location.href.substr(-1),
|
||||
appLanguage: 'en',
|
||||
currentCulture: 'en-nz',
|
||||
isRunningUnderWine: false,
|
||||
currentTime: new Date().toJSON(),
|
||||
cpuUsageEnabled: false,
|
||||
cpuUsage: '0',
|
||||
pcUptimeEnabled: false,
|
||||
pcUptime: '',
|
||||
customInfo: '',
|
||||
config: {},
|
||||
onlineFriendCount: 0,
|
||||
nowPlaying: {
|
||||
url: '',
|
||||
name: '',
|
||||
length: 0,
|
||||
startTime: 0,
|
||||
elapsed: 0,
|
||||
percentage: 0,
|
||||
remainingText: '',
|
||||
playing: false
|
||||
},
|
||||
lastLocation: {
|
||||
date: 0,
|
||||
location: '',
|
||||
name: '',
|
||||
playerList: [],
|
||||
friendList: [],
|
||||
progressPie: false,
|
||||
onlineFor: 0
|
||||
},
|
||||
lastLocationTimer: '',
|
||||
onlineForTimer: '',
|
||||
wristFeed: [],
|
||||
devices: [],
|
||||
deviceCount: 0,
|
||||
notificationOpacity: 100,
|
||||
hudFeed: [],
|
||||
hudTimeout: [],
|
||||
cleanHudFeedLoopStatus: false
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (LINUX) {
|
||||
updateVrElectronLoop();
|
||||
}
|
||||
if (vrState.appType === '1') {
|
||||
refreshCustomScript();
|
||||
updateStatsLoop();
|
||||
}
|
||||
setDatetimeFormat();
|
||||
|
||||
nextTick(() => {
|
||||
window.$app.configUpdate = configUpdate;
|
||||
window.$app.updateOnlineFriendCount = updateOnlineFriendCount;
|
||||
window.$app.nowPlayingUpdate = nowPlayingUpdate;
|
||||
window.$app.lastLocationUpdate = lastLocationUpdate;
|
||||
window.$app.wristFeedUpdate = wristFeedUpdate;
|
||||
window.$app.refreshCustomScript = refreshCustomScript;
|
||||
window.$app.playNoty = playNoty;
|
||||
window.$app.statusClass = statusClass;
|
||||
window.$app.notyClear = notyClear;
|
||||
window.$app.addEntryHudFeed = addEntryHudFeed;
|
||||
window.$app.updateHudFeedTag = updateHudFeedTag;
|
||||
window.$app.updateHudTimeout = updateHudTimeout;
|
||||
window.$app.setDatetimeFormat = setDatetimeFormat;
|
||||
window.$app.setAppLanguage = setAppLanguage;
|
||||
window.$app.trackingResultToClass = trackingResultToClass;
|
||||
window.$app.updateFeedLength = updateFeedLength;
|
||||
window.$app.updateStatsLoop = updateStatsLoop;
|
||||
window.$app.updateVrElectronLoop = updateVrElectronLoop;
|
||||
window.$app.cleanHudFeedLoop = cleanHudFeedLoop;
|
||||
window.$app.cleanHudFeed = cleanHudFeed;
|
||||
|
||||
window.$app.vrState = vrState;
|
||||
|
||||
AppApiVr.VrInit();
|
||||
});
|
||||
});
|
||||
|
||||
function configUpdate(json) {
|
||||
vrState.config = JSON.parse(json);
|
||||
vrState.hudFeed = [];
|
||||
vrState.hudTimeout = [];
|
||||
setDatetimeFormat();
|
||||
setAppLanguage(vrState.config.appLanguage);
|
||||
updateFeedLength();
|
||||
if (
|
||||
vrState.config.vrOverlayCpuUsage !== vrState.cpuUsageEnabled ||
|
||||
vrState.config.pcUptimeOnFeed !== vrState.pcUptimeEnabled
|
||||
) {
|
||||
vrState.cpuUsageEnabled = vrState.config.vrOverlayCpuUsage;
|
||||
vrState.pcUptimeEnabled = vrState.config.pcUptimeOnFeed;
|
||||
AppApiVr.ToggleSystemMonitor(vrState.cpuUsageEnabled || vrState.pcUptimeEnabled);
|
||||
}
|
||||
if (vrState.config.notificationOpacity !== vrState.notificationOpacity) {
|
||||
vrState.notificationOpacity = vrState.config.notificationOpacity;
|
||||
setNotyOpacity(vrState.notificationOpacity);
|
||||
}
|
||||
}
|
||||
|
||||
function updateOnlineFriendCount(count) {
|
||||
vrState.onlineFriendCount = parseInt(count, 10);
|
||||
}
|
||||
|
||||
function nowPlayingUpdate(json) {
|
||||
vrState.nowPlaying = JSON.parse(json);
|
||||
if (vrState.appType === '2') {
|
||||
const circle = /** @type {SVGCircleElement} */ (
|
||||
document.querySelector('.np-progress-circle-stroke')
|
||||
);
|
||||
|
||||
if (vrState.lastLocation.progressPie && vrState.nowPlaying.percentage !== 0) {
|
||||
circle.style.opacity = (0.5).toString();
|
||||
const circumference = circle.getTotalLength();
|
||||
circle.style.strokeDashoffset = (
|
||||
circumference -
|
||||
(vrState.nowPlaying.percentage / 100) * circumference
|
||||
).toString();
|
||||
} else {
|
||||
circle.style.opacity = '0';
|
||||
}
|
||||
}
|
||||
updateFeedLength();
|
||||
}
|
||||
|
||||
function lastLocationUpdate(json) {
|
||||
vrState.lastLocation = JSON.parse(json);
|
||||
}
|
||||
|
||||
function wristFeedUpdate(json) {
|
||||
vrState.wristFeed = JSON.parse(json);
|
||||
updateFeedLength();
|
||||
}
|
||||
|
||||
function updateFeedLength() {
|
||||
if (vrState.appType === '2' || vrState.wristFeed.length === 0) {
|
||||
return;
|
||||
}
|
||||
let length = 16;
|
||||
if (!vrState.config.hideDevicesFromFeed) {
|
||||
length -= 2;
|
||||
if (vrState.deviceCount > 8) {
|
||||
length -= 1;
|
||||
}
|
||||
}
|
||||
if (vrState.nowPlaying.playing) {
|
||||
length -= 1;
|
||||
}
|
||||
if (length < vrState.wristFeed.length) {
|
||||
vrState.wristFeed.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshCustomScript() {
|
||||
if (document.contains(document.getElementById('vr-custom-script'))) {
|
||||
document.getElementById('vr-custom-script').remove();
|
||||
}
|
||||
const customScript = await AppApiVr.CustomVrScript();
|
||||
if (customScript) {
|
||||
const head = document.head;
|
||||
const $vrCustomScript = document.createElement('script');
|
||||
$vrCustomScript.setAttribute('id', 'vr-custom-script');
|
||||
$vrCustomScript.type = 'text/javascript';
|
||||
$vrCustomScript.textContent = customScript;
|
||||
head.appendChild($vrCustomScript);
|
||||
}
|
||||
}
|
||||
|
||||
function setNotyOpacity(value) {
|
||||
const opacity = (value / 100).toFixed(2);
|
||||
let element = document.getElementById('noty-opacity');
|
||||
if (!element) {
|
||||
document.body.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`<style id="noty-opacity">.noty_layout { opacity: ${opacity}; }</style>`
|
||||
);
|
||||
element = document.getElementById('noty-opacity');
|
||||
}
|
||||
element.innerHTML = `.noty_layout { opacity: ${opacity}; }`;
|
||||
}
|
||||
|
||||
async function updateStatsLoop() {
|
||||
try {
|
||||
vrState.currentTime = new Date()
|
||||
.toLocaleDateString(vrState.currentCulture, {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
hourCycle: vrState.config.dtHour12 ? 'h12' : 'h23'
|
||||
})
|
||||
.replace(' AM', ' am')
|
||||
.replace(' PM', ' pm')
|
||||
.replace(',', '');
|
||||
|
||||
if (vrState.cpuUsageEnabled) {
|
||||
const cpuUsage = await AppApiVr.CpuUsage();
|
||||
vrState.cpuUsage = cpuUsage.toFixed(0);
|
||||
}
|
||||
if (vrState.lastLocation.date !== 0) {
|
||||
vrState.lastLocationTimer = timeToText(Date.now() - vrState.lastLocation.date);
|
||||
} else {
|
||||
vrState.lastLocationTimer = '';
|
||||
}
|
||||
if (vrState.lastLocation.onlineFor) {
|
||||
vrState.onlineForTimer = timeToText(Date.now() - vrState.lastLocation.onlineFor);
|
||||
} else {
|
||||
vrState.onlineForTimer = '';
|
||||
}
|
||||
|
||||
if (!vrState.config.hideDevicesFromFeed) {
|
||||
AppApiVr.GetVRDevices().then((devices) => {
|
||||
let deviceList = [];
|
||||
let baseStations = 0;
|
||||
devices.forEach((device) => {
|
||||
device[3] = parseInt(device[3], 10).toString();
|
||||
if (device[0] === 'base' && device[1] === 'connected') {
|
||||
baseStations++;
|
||||
} else {
|
||||
deviceList.push(device);
|
||||
}
|
||||
});
|
||||
vrState.deviceCount = deviceList.length;
|
||||
const deviceValue = (dev) => {
|
||||
if (dev[0] === 'headset') return 0;
|
||||
if (dev[0] === 'leftController') return 1;
|
||||
if (dev[0] === 'rightController') return 2;
|
||||
if (dev[0].toLowerCase().includes('controller')) return 3;
|
||||
if (dev[0] === 'tracker' || dev[0] === 'base') return 4;
|
||||
return 5;
|
||||
};
|
||||
deviceList.sort((a, b) => deviceValue(a) - deviceValue(b));
|
||||
deviceList.sort((a, b) => {
|
||||
if (a[1] === b[1]) {
|
||||
return 0;
|
||||
}
|
||||
if (a[1] === 'connected') {
|
||||
return -1;
|
||||
}
|
||||
if (a[1] === 'disconnected') {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
if (baseStations > 0) {
|
||||
deviceList.push(['base', 'connected', '', baseStations]);
|
||||
vrState.deviceCount += 1;
|
||||
}
|
||||
vrState.devices = deviceList;
|
||||
});
|
||||
} else {
|
||||
vrState.devices = [];
|
||||
}
|
||||
if (vrState.config.pcUptimeOnFeed) {
|
||||
AppApiVr.GetUptime().then((uptime) => {
|
||||
if (uptime) {
|
||||
vrState.pcUptime = timeToText(uptime);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
vrState.pcUptime = '';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
workerTimers.setTimeout(() => updateStatsLoop(), 500);
|
||||
}
|
||||
|
||||
async function updateVrElectronLoop() {
|
||||
try {
|
||||
if (vrState.appType === '1') {
|
||||
const wristOverlayQueue = await AppApiVr.GetExecuteVrFeedFunctionQueue();
|
||||
if (wristOverlayQueue) {
|
||||
wristOverlayQueue.forEach((item) => {
|
||||
// item[0] is the function name, item[1] is already an object
|
||||
const fullFunctionName = item[0];
|
||||
const jsonArg = item[1];
|
||||
|
||||
if (
|
||||
typeof window.$app === 'object' &&
|
||||
typeof window.$app[fullFunctionName] === 'function'
|
||||
) {
|
||||
window.$app[fullFunctionName](jsonArg);
|
||||
} else {
|
||||
console.error(`$app.${fullFunctionName} is not defined or is not a function`);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const hmdOverlayQueue = await AppApiVr.GetExecuteVrOverlayFunctionQueue();
|
||||
if (hmdOverlayQueue) {
|
||||
hmdOverlayQueue.forEach((item) => {
|
||||
// item[0] is the function name, item[1] is already an object
|
||||
const fullFunctionName = item[0];
|
||||
const jsonArg = item[1];
|
||||
|
||||
if (
|
||||
typeof window.$app === 'object' &&
|
||||
typeof window.$app[fullFunctionName] === 'function'
|
||||
) {
|
||||
window.$app[fullFunctionName](jsonArg);
|
||||
} else {
|
||||
console.error(`$app.${fullFunctionName} is not defined or is not a function`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
workerTimers.setTimeout(() => updateVrElectronLoop(), 500);
|
||||
}
|
||||
|
||||
function playNoty(json) {
|
||||
let { noty, message, image } = JSON.parse(json);
|
||||
if (typeof noty === 'undefined') {
|
||||
console.error('noty is undefined');
|
||||
return;
|
||||
}
|
||||
noty = escapeTagRecursive(noty);
|
||||
message = escapeTag(message) || '';
|
||||
let text = '';
|
||||
let img = '';
|
||||
if (image) {
|
||||
img = `<img class="noty-img" src="${image}"></img>`;
|
||||
}
|
||||
switch (noty.type) {
|
||||
case 'OnPlayerJoined':
|
||||
text = `<strong>${noty.displayName}</strong> has joined`;
|
||||
break;
|
||||
case 'OnPlayerLeft':
|
||||
text = `<strong>${noty.displayName}</strong> has left`;
|
||||
break;
|
||||
case 'OnPlayerJoining':
|
||||
text = `<strong>${noty.displayName}</strong> is joining`;
|
||||
break;
|
||||
case 'GPS':
|
||||
text = `<strong>${noty.displayName}</strong> is in ${displayLocation(
|
||||
noty.location,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
)}`;
|
||||
break;
|
||||
case 'Online':
|
||||
let locationName = '';
|
||||
if (noty.worldName) {
|
||||
locationName = ` to ${displayLocation(noty.location, noty.worldName, noty.groupName)}`;
|
||||
}
|
||||
text = `<strong>${noty.displayName}</strong> has logged in${locationName}`;
|
||||
break;
|
||||
case 'Offline':
|
||||
text = `<strong>${noty.displayName}</strong> has logged out`;
|
||||
break;
|
||||
case 'Status':
|
||||
text = `<strong>${noty.displayName}</strong> status is now <i>${noty.status}</i> ${noty.statusDescription}`;
|
||||
break;
|
||||
case 'invite':
|
||||
text = `<strong>${noty.senderUsername}</strong> has invited you to ${displayLocation(
|
||||
noty.details.worldId,
|
||||
noty.details.worldName,
|
||||
''
|
||||
)}${message}`;
|
||||
break;
|
||||
case 'requestInvite':
|
||||
text = `<strong>${noty.senderUsername}</strong> has requested an invite ${message}`;
|
||||
break;
|
||||
case 'inviteResponse':
|
||||
text = `<strong>${noty.senderUsername}</strong> has responded to your invite ${message}`;
|
||||
break;
|
||||
case 'requestInviteResponse':
|
||||
text = `<strong>${noty.senderUsername}</strong> has responded to your invite request ${message}`;
|
||||
break;
|
||||
case 'friendRequest':
|
||||
text = `<strong>${noty.senderUsername}</strong> has sent you a friend request`;
|
||||
break;
|
||||
case 'Friend':
|
||||
text = `<strong>${noty.displayName}</strong> is now your friend`;
|
||||
break;
|
||||
case 'Unfriend':
|
||||
text = `<strong>${noty.displayName}</strong> is no longer your friend`;
|
||||
break;
|
||||
case 'TrustLevel':
|
||||
text = `<strong>${noty.displayName}</strong> trust level is now ${noty.trustLevel}`;
|
||||
break;
|
||||
case 'DisplayName':
|
||||
text = `<strong>${noty.previousDisplayName}</strong> changed their name to ${noty.displayName}`;
|
||||
break;
|
||||
case 'boop':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'groupChange':
|
||||
text = `<strong>${noty.senderUsername}</strong> ${noty.message}`;
|
||||
break;
|
||||
case 'group.announcement':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'group.informative':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'group.invite':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'group.joinRequest':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'group.transfer':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'group.queueReady':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'instance.closed':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'PortalSpawn':
|
||||
if (noty.displayName) {
|
||||
text = `<strong>${noty.displayName}</strong> has spawned a portal to ${displayLocation(
|
||||
noty.instanceId,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
)}`;
|
||||
} else {
|
||||
text = 'User has spawned a portal';
|
||||
}
|
||||
break;
|
||||
case 'AvatarChange':
|
||||
text = `<strong>${noty.displayName}</strong> changed into avatar ${noty.name}`;
|
||||
break;
|
||||
case 'ChatBoxMessage':
|
||||
text = `<strong>${noty.displayName}</strong> said ${noty.text}`;
|
||||
break;
|
||||
case 'Event':
|
||||
text = noty.data;
|
||||
break;
|
||||
case 'External':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'VideoPlay':
|
||||
text = `<strong>Now playing:</strong> ${noty.notyName}`;
|
||||
break;
|
||||
case 'BlockedOnPlayerJoined':
|
||||
text = `Blocked user <strong>${noty.displayName}</strong> has joined`;
|
||||
break;
|
||||
case 'BlockedOnPlayerLeft':
|
||||
text = `Blocked user <strong>${noty.displayName}</strong> has left`;
|
||||
break;
|
||||
case 'MutedOnPlayerJoined':
|
||||
text = `Muted user <strong>${noty.displayName}</strong> has joined`;
|
||||
break;
|
||||
case 'MutedOnPlayerLeft':
|
||||
text = `Muted user <strong>${noty.displayName}</strong> has left`;
|
||||
break;
|
||||
case 'Blocked':
|
||||
text = `<strong>${noty.displayName}</strong> has blocked you`;
|
||||
break;
|
||||
case 'Unblocked':
|
||||
text = `<strong>${noty.displayName}</strong> has unblocked you`;
|
||||
break;
|
||||
case 'Muted':
|
||||
text = `<strong>${noty.displayName}</strong> has muted you`;
|
||||
break;
|
||||
case 'Unmuted':
|
||||
text = `<strong>${noty.displayName}</strong> has unmuted you`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (text) {
|
||||
new Noty({
|
||||
type: 'alert',
|
||||
theme: vrState.config.notificationTheme,
|
||||
timeout: vrState.config.notificationTimeout,
|
||||
layout: vrState.config.notificationPosition,
|
||||
text: `${img}<div class="noty-text">${text}</div>`
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
|
||||
function statusClass(status) {
|
||||
let style = {};
|
||||
if (typeof status === 'undefined') {
|
||||
return style;
|
||||
}
|
||||
if (status === 'active') {
|
||||
// Online
|
||||
style.online = true;
|
||||
} else if (status === 'join me') {
|
||||
// Join Me
|
||||
style.joinme = true;
|
||||
} else if (status === 'ask me') {
|
||||
// Ask Me
|
||||
style.askme = true;
|
||||
} else if (status === 'busy') {
|
||||
// Do Not Disturb
|
||||
style.busy = true;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
function notyClear() {
|
||||
Noty.closeAll();
|
||||
}
|
||||
|
||||
function cleanHudFeedLoop() {
|
||||
if (!vrState.cleanHudFeedLoopStatus) {
|
||||
return;
|
||||
}
|
||||
cleanHudFeed();
|
||||
if (vrState.hudFeed.length === 0) {
|
||||
vrState.cleanHudFeedLoopStatus = false;
|
||||
return;
|
||||
}
|
||||
workerTimers.setTimeout(() => cleanHudFeedLoop(), 500);
|
||||
}
|
||||
|
||||
function cleanHudFeed() {
|
||||
const dt = Date.now();
|
||||
vrState.hudFeed.forEach((item) => {
|
||||
if (item.time + vrState.config.photonOverlayMessageTimeout < dt) {
|
||||
removeFromArray(vrState.hudFeed, item);
|
||||
}
|
||||
});
|
||||
if (vrState.hudFeed.length > 10) {
|
||||
vrState.hudFeed.length = 10;
|
||||
}
|
||||
if (!vrState.cleanHudFeedLoopStatus) {
|
||||
vrState.cleanHudFeedLoopStatus = true;
|
||||
cleanHudFeedLoop();
|
||||
}
|
||||
}
|
||||
|
||||
function addEntryHudFeed(json) {
|
||||
const data = JSON.parse(json);
|
||||
let combo = 1;
|
||||
vrState.hudFeed.forEach((item) => {
|
||||
if (item.displayName === data.displayName && item.text === data.text) {
|
||||
combo = item.combo + 1;
|
||||
removeFromArray(vrState.hudFeed, item);
|
||||
}
|
||||
});
|
||||
vrState.hudFeed.unshift({
|
||||
time: Date.now(),
|
||||
combo,
|
||||
...data
|
||||
});
|
||||
cleanHudFeed();
|
||||
}
|
||||
|
||||
function updateHudFeedTag(json) {
|
||||
const ref = JSON.parse(json);
|
||||
vrState.hudFeed.forEach((item) => {
|
||||
if (item.userId === ref.userId) {
|
||||
item.colour = ref.colour;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateHudTimeout(json) {
|
||||
vrState.hudTimeout = JSON.parse(json);
|
||||
}
|
||||
|
||||
async function setDatetimeFormat() {
|
||||
vrState.currentCulture = await AppApiVr.CurrentCulture();
|
||||
const formatDate = (date) => {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
const dt = new Date(date);
|
||||
return dt
|
||||
.toLocaleTimeString(vrState.currentCulture, {
|
||||
hour: '2-digit',
|
||||
minute: 'numeric',
|
||||
hourCycle: vrState.config.dtHour12 ? 'h12' : 'h23'
|
||||
})
|
||||
.replace(' am', '')
|
||||
.replace(' pm', '');
|
||||
};
|
||||
Vue.filter('formatDate', formatDate);
|
||||
}
|
||||
|
||||
function setAppLanguage(appLanguage) {
|
||||
if (!appLanguage) {
|
||||
return;
|
||||
}
|
||||
if (appLanguage !== vrState.appLanguage) {
|
||||
vrState.appLanguage = appLanguage;
|
||||
// @ts-ignore
|
||||
i18n.locale = vrState.appLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
function trackingResultToClass(deviceStatus) {
|
||||
switch (deviceStatus) {
|
||||
case 'Uninitialized':
|
||||
case 'Calibrating_OutOfRange':
|
||||
case 'Fallback_RotationOnly':
|
||||
return 'tracker-error';
|
||||
|
||||
case 'Calibrating_InProgress':
|
||||
case 'Running_OutOfRange':
|
||||
return 'tracker-warning';
|
||||
|
||||
case 'Running_OK':
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
const {
|
||||
appType,
|
||||
config,
|
||||
wristFeed,
|
||||
devices,
|
||||
nowPlaying,
|
||||
lastLocation,
|
||||
lastLocationTimer,
|
||||
onlineForTimer,
|
||||
pcUptime,
|
||||
currentTime,
|
||||
cpuUsageEnabled,
|
||||
cpuUsage,
|
||||
onlineFriendCount,
|
||||
customInfo,
|
||||
hudFeed,
|
||||
hudTimeout
|
||||
} = toRefs(vrState);
|
||||
|
||||
return {
|
||||
appType,
|
||||
config,
|
||||
wristFeed,
|
||||
devices,
|
||||
nowPlaying,
|
||||
lastLocation,
|
||||
lastLocationTimer,
|
||||
onlineForTimer,
|
||||
pcUptime,
|
||||
currentTime,
|
||||
cpuUsageEnabled,
|
||||
cpuUsage,
|
||||
onlineFriendCount,
|
||||
customInfo,
|
||||
hudFeed,
|
||||
hudTimeout,
|
||||
statusClass,
|
||||
trackingResultToClass,
|
||||
$t
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
+20
-2
@@ -33,9 +33,9 @@ const avatarReq = {
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{ id: string, releaseStatus?: 'public' | 'private', name?: string, description?: string,tags?: string[] }} params
|
||||
* @returns {Promise<{json: any, params}>}
|
||||
* @type {import('../types/api/avatar').SaveAvatar}
|
||||
*/
|
||||
saveAvatar(params) {
|
||||
return request(`avatars/${params.id}`, {
|
||||
@@ -173,6 +173,24 @@ const avatarReq = {
|
||||
});
|
||||
},
|
||||
|
||||
uploadAvatarImage(imageData) {
|
||||
const params = {
|
||||
tag: 'avatarimage'
|
||||
};
|
||||
return request('file/image', {
|
||||
uploadImage: true,
|
||||
matchingDimensions: false,
|
||||
postData: JSON.stringify(params),
|
||||
imageData
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{ imageData: string, avatarId: string }}
|
||||
* @returns {Promise<{json: any, params}>}
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
import { request } from '../service/request';
|
||||
import { useAvatarStore, useWorldStore } from '../stores';
|
||||
|
||||
const imageReq = {
|
||||
async uploadAvatarFailCleanup(id) {
|
||||
const avatarStore = useAvatarStore();
|
||||
try {
|
||||
const json = await request(`file/${id}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
const fileId = json.id;
|
||||
const fileVersion = json.versions[json.versions.length - 1].version;
|
||||
request(`file/${fileId}/${fileVersion}/signature/finish`, {
|
||||
method: 'PUT'
|
||||
}).catch(err => console.error('Failed to finish signature:', err));
|
||||
request(`file/${fileId}/${fileVersion}/file/finish`, {
|
||||
method: 'PUT'
|
||||
}).catch(err => console.error('Failed to finish file:', err));
|
||||
} catch (error) {
|
||||
console.error('Failed to cleanup avatar upload:', error);
|
||||
}
|
||||
avatarStore.avatarDialog.loading = false;
|
||||
},
|
||||
|
||||
async uploadAvatarImage(params, fileId) {
|
||||
try {
|
||||
return await request(`file/${fileId}`, {
|
||||
method: 'POST',
|
||||
params
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params,
|
||||
fileId
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadAvatarFailCleanup(fileId);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
async uploadAvatarImageFileStart(params) {
|
||||
try {
|
||||
return await request(
|
||||
`file/${params.fileId}/${params.fileVersion}/file/start`,
|
||||
{
|
||||
method: 'PUT'
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadAvatarFailCleanup(params.fileId);
|
||||
}
|
||||
},
|
||||
|
||||
uploadAvatarImageFileFinish(params) {
|
||||
return request(
|
||||
`file/${params.fileId}/${params.fileVersion}/file/finish`,
|
||||
{
|
||||
method: 'PUT',
|
||||
params: {
|
||||
maxParts: 0,
|
||||
nextPartNumber: 0
|
||||
}
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
async uploadAvatarImageSigStart(params) {
|
||||
try {
|
||||
return await request(
|
||||
`file/${params.fileId}/${params.fileVersion}/signature/start`,
|
||||
{
|
||||
method: 'PUT'
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadAvatarFailCleanup(params.fileId);
|
||||
}
|
||||
},
|
||||
|
||||
uploadAvatarImageSigFinish(params) {
|
||||
return request(
|
||||
`file/${params.fileId}/${params.fileVersion}/signature/finish`,
|
||||
{
|
||||
method: 'PUT',
|
||||
params: {
|
||||
maxParts: 0,
|
||||
nextPartNumber: 0
|
||||
}
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
setAvatarImage(params) {
|
||||
return request(`avatars/${params.id}`, {
|
||||
method: 'PUT',
|
||||
params
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
async uploadWorldFailCleanup(id) {
|
||||
const worldStore = useWorldStore();
|
||||
try {
|
||||
const json = await request(`file/${id}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
const fileId = json.id;
|
||||
const fileVersion = json.versions[json.versions.length - 1].version;
|
||||
request(`file/${fileId}/${fileVersion}/signature/finish`, {
|
||||
method: 'PUT'
|
||||
}).catch(err => console.error('Failed to finish signature:', err));
|
||||
request(`file/${fileId}/${fileVersion}/file/finish`, {
|
||||
method: 'PUT'
|
||||
}).catch(err => console.error('Failed to finish file:', err));
|
||||
} catch (error) {
|
||||
console.error('Failed to cleanup world upload:', error);
|
||||
}
|
||||
worldStore.worldDialog.loading = false;
|
||||
},
|
||||
|
||||
async uploadWorldImage(params, fileId) {
|
||||
try {
|
||||
return await request(`file/${fileId}`, {
|
||||
method: 'POST',
|
||||
params
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params,
|
||||
fileId
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadWorldFailCleanup(fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
|
||||
async uploadWorldImageFileStart(params) {
|
||||
try {
|
||||
return await request(
|
||||
`file/${params.fileId}/${params.fileVersion}/file/start`,
|
||||
{
|
||||
method: 'PUT'
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadWorldFailCleanup(params.fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
|
||||
uploadWorldImageFileFinish(params) {
|
||||
return request(
|
||||
`file/${params.fileId}/${params.fileVersion}/file/finish`,
|
||||
{
|
||||
method: 'PUT',
|
||||
params: {
|
||||
maxParts: 0,
|
||||
nextPartNumber: 0
|
||||
}
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
async uploadWorldImageSigStart(params) {
|
||||
try {
|
||||
return await request(
|
||||
`file/${params.fileId}/${params.fileVersion}/signature/start`,
|
||||
{
|
||||
method: 'PUT'
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadWorldFailCleanup(params.fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
|
||||
uploadWorldImageSigFinish(params) {
|
||||
return request(
|
||||
`file/${params.fileId}/${params.fileVersion}/signature/finish`,
|
||||
{
|
||||
method: 'PUT',
|
||||
params: {
|
||||
maxParts: 0,
|
||||
nextPartNumber: 0
|
||||
}
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
setWorldImage(params) {
|
||||
const worldStore = useWorldStore();
|
||||
return request(`worlds/${params.id}`, {
|
||||
method: 'PUT',
|
||||
params
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
args.ref = worldStore.applyWorld(json);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
getAvatarImages(params) {
|
||||
return request(`file/${params.fileId}`, {
|
||||
method: 'GET'
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
getWorldImages(params) {
|
||||
return request(`file/${params.fileId}`, {
|
||||
method: 'GET',
|
||||
params
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default imageReq;
|
||||
@@ -16,7 +16,6 @@ import favoriteRequest from './favorite';
|
||||
import vrcPlusIconRequest from './vrcPlusIcon';
|
||||
import vrcPlusImageRequest from './vrcPlusImage';
|
||||
import inviteMessagesRequest from './inviteMessages';
|
||||
import imageRequest from './image';
|
||||
import miscRequest from './misc';
|
||||
import groupRequest from './group';
|
||||
import authRequest from './auth';
|
||||
@@ -37,7 +36,6 @@ window.request = {
|
||||
vrcPlusIconRequest,
|
||||
vrcPlusImageRequest,
|
||||
inviteMessagesRequest,
|
||||
imageRequest,
|
||||
miscRequest,
|
||||
authRequest,
|
||||
groupRequest,
|
||||
@@ -59,7 +57,6 @@ export {
|
||||
vrcPlusIconRequest,
|
||||
vrcPlusImageRequest,
|
||||
inviteMessagesRequest,
|
||||
imageRequest,
|
||||
miscRequest,
|
||||
authRequest,
|
||||
groupRequest,
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
import { $app } from '../app';
|
||||
import { t } from '../plugin';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { i18n } from '../plugin/i18n';
|
||||
import { request } from '../service/request';
|
||||
import { useInstanceStore } from '../stores';
|
||||
|
||||
@@ -110,14 +110,14 @@ const instanceReq = {
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err?.error?.message) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: err.error.message,
|
||||
type: 'error'
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
$app.$message({
|
||||
message: t('message.instance.not_allowed'),
|
||||
ElMessage({
|
||||
message: i18n.global.t('message.instance.not_allowed'),
|
||||
type: 'error'
|
||||
});
|
||||
throw err;
|
||||
|
||||
+4
-3
@@ -6,12 +6,13 @@ function getCurrentUserId() {
|
||||
}
|
||||
|
||||
const miscReq = {
|
||||
getBundles(fileId) {
|
||||
return request(`file/${fileId}`, {
|
||||
getFile(params) {
|
||||
return request(`file/${params.fileId}`, {
|
||||
method: 'GET'
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
|
||||
@@ -140,6 +140,24 @@ const worldReq = {
|
||||
args.ref = worldStore.applyWorld(json);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
uploadWorldImage(imageData) {
|
||||
const params = {
|
||||
tag: 'worldimage'
|
||||
};
|
||||
return request('file/image', {
|
||||
uploadImage: true,
|
||||
matchingDimensions: false,
|
||||
postData: JSON.stringify(params),
|
||||
imageData
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+14
-5
@@ -4,20 +4,29 @@
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
import Vue from 'vue';
|
||||
import { createApp } from 'vue';
|
||||
import './bootstrap';
|
||||
import { i18n } from './plugin/i18n';
|
||||
import App from './App.vue';
|
||||
import { pinia } from './stores';
|
||||
import ElementPlus from 'element-plus';
|
||||
|
||||
import './app.scss';
|
||||
import registerComponents from './plugin/components';
|
||||
|
||||
console.log(`isLinux: ${LINUX}`);
|
||||
|
||||
// #region | Hey look it's most of VRCX!
|
||||
// prompt: 'Please clean up and refactor the VRCX codebase.'
|
||||
|
||||
const $app = new Vue({
|
||||
pinia,
|
||||
render: (h) => h(App)
|
||||
}).$mount('#root');
|
||||
const $app = createApp(App);
|
||||
$app.use(pinia);
|
||||
$app.use(i18n);
|
||||
$app.use(ElementPlus);
|
||||
|
||||
registerComponents($app);
|
||||
|
||||
$app.mount('#root');
|
||||
|
||||
window.$app = $app;
|
||||
export { $app };
|
||||
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
doctype html
|
||||
#x-app.x-app(@dragenter.prevent @dragover.prevent @drop.prevent)
|
||||
//- ### Login ###
|
||||
Login(v-if='!watchState.isLoggedIn')
|
||||
|
||||
VRCXUpdateDialog
|
||||
|
||||
template(v-if='watchState.isLoggedIn')
|
||||
//- ### Menu ###
|
||||
NavMenu
|
||||
|
||||
//- ### Sidebar ###
|
||||
Sidebar
|
||||
|
||||
//- ### Tabs ###
|
||||
Feed
|
||||
|
||||
GameLog
|
||||
|
||||
PlayerList
|
||||
|
||||
Search
|
||||
|
||||
Favorites
|
||||
|
||||
FriendLog
|
||||
|
||||
Moderation
|
||||
|
||||
Notification
|
||||
|
||||
FriendList
|
||||
|
||||
Charts
|
||||
|
||||
Tools
|
||||
|
||||
Profile
|
||||
|
||||
Settings
|
||||
|
||||
//- ## Dialogs ## -\\
|
||||
UserDialog
|
||||
|
||||
WorldDialog
|
||||
|
||||
AvatarDialog
|
||||
|
||||
GroupDialog
|
||||
|
||||
GroupMemberModerationDialog
|
||||
|
||||
GalleryDialog
|
||||
|
||||
FullscreenImageDialog
|
||||
|
||||
PreviousInstancesInfoDialog
|
||||
|
||||
LaunchDialog
|
||||
|
||||
LaunchOptionsDialog
|
||||
|
||||
FriendImportDialog
|
||||
|
||||
WorldImportDialog
|
||||
|
||||
AvatarImportDialog
|
||||
|
||||
ChooseFavoriteGroupDialog
|
||||
|
||||
EditInviteMessageDialog
|
||||
|
||||
VRChatConfigDialog
|
||||
|
||||
PrimaryPasswordDialog
|
||||
+218
-188
@@ -10,10 +10,12 @@
|
||||
|
||||
@use './assets/scss/flags.scss';
|
||||
@use './assets/scss/animated-emoji.scss';
|
||||
@use 'element-plus/theme-chalk/src/index.scss' as *;
|
||||
|
||||
@import '~animate.css/animate.min.css';
|
||||
@import '~noty/lib/noty.css';
|
||||
@import '~element-ui/lib/theme-chalk/index.css';
|
||||
@import 'element-plus/theme-chalk/src/dark/css-vars.scss';
|
||||
@import 'animate.css/animate.min.css';
|
||||
@import 'noty/lib/noty.css';
|
||||
@import 'remixicon/fonts/remixicon.css';
|
||||
|
||||
.color-palettes {
|
||||
background: #409eff;
|
||||
@@ -26,6 +28,10 @@
|
||||
background: #c0c4cc;
|
||||
}
|
||||
|
||||
.el-icon.is-loading {
|
||||
animation: rotating 2s linear infinite;
|
||||
}
|
||||
|
||||
.noty_layout {
|
||||
word-break: break-all;
|
||||
}
|
||||
@@ -78,42 +84,46 @@
|
||||
border-bottom: 1px solid #a0b55c;
|
||||
}
|
||||
|
||||
.el-table + .pagination-bar {
|
||||
margin-top: 15px;
|
||||
// .el-table + .pagination-bar {
|
||||
// margin-top: 15px;
|
||||
// }
|
||||
|
||||
.el-table__expanded-cell[class*='cell'] {
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
.el-table--mini .el-table__expanded-cell[class*='cell'] {
|
||||
padding: 20px 50px;
|
||||
.el-table--small .cell {
|
||||
// fix expand arrow position
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.el-table--mini .el-table__cell {
|
||||
padding: 5px 0;
|
||||
}
|
||||
// .el-table--mini .el-table__cell {
|
||||
// padding: 5px 0;
|
||||
// }
|
||||
|
||||
// expand table cell on hover, TODO: replace with a better solution
|
||||
.el-table .cell {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
// .el-table .cell {
|
||||
// display: -webkit-box;
|
||||
// -webkit-box-orient: vertical;
|
||||
// -webkit-line-clamp: 1;
|
||||
// }
|
||||
|
||||
.el-table__row:hover .el-table__cell .cell {
|
||||
display: revert;
|
||||
-webkit-line-clamp: unset;
|
||||
}
|
||||
// .el-table__row:hover .el-table__cell .cell {
|
||||
// display: revert;
|
||||
// -webkit-line-clamp: unset;
|
||||
// }
|
||||
|
||||
.el-table th.is-sortable .cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
// .el-table th.is-sortable .cell {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// }
|
||||
|
||||
.el-table .caret-wrapper {
|
||||
margin-top: 4.5px;
|
||||
}
|
||||
// .el-table .caret-wrapper {
|
||||
// margin-top: 4.5px;
|
||||
// }
|
||||
|
||||
.notification-table .el-table .cell {
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
// .notification-table .el-table .cell {
|
||||
// -webkit-line-clamp: 2;
|
||||
// }
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 20px;
|
||||
@@ -146,6 +156,10 @@
|
||||
unicode-range: U+2026;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
'ellipsis-font', 'Noto Sans KR', 'Noto Sans JP', 'Noto Sans TC',
|
||||
@@ -313,11 +327,11 @@ hr.x-vertical-divider {
|
||||
order: 99;
|
||||
}
|
||||
|
||||
.el-popper.x-quick-search {
|
||||
width: 225px;
|
||||
min-width: 0 !important;
|
||||
border: none;
|
||||
}
|
||||
// .el-popper.x-quick-search {
|
||||
// width: 225px;
|
||||
// min-width: 0 !important;
|
||||
// border: none;
|
||||
// }
|
||||
|
||||
.el-popper.x-quick-search .el-select-dropdown__item {
|
||||
width: 100%;
|
||||
@@ -383,13 +397,13 @@ hr.x-vertical-divider {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item .x-friend-item:hover {
|
||||
background: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
// .el-select-dropdown__item .x-friend-item:hover {
|
||||
// background: none;
|
||||
// border-radius: 0;
|
||||
// }
|
||||
|
||||
.x-dialog .x-friend-item {
|
||||
width: 175px;
|
||||
width: 167px;
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar {
|
||||
@@ -438,7 +452,7 @@ img.friends-list-avatar {
|
||||
.x-friend-item > .avatar.joinme.mobile > img,
|
||||
.x-friend-item > .avatar.askme.mobile > img,
|
||||
.x-friend-item > .avatar.busy.mobile > img {
|
||||
mask-image: url(assets/images/masks/usercutoutmobile.svg);
|
||||
mask-image: url(./assets/images/masks/usercutoutmobile.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.online.mobile::after,
|
||||
@@ -452,7 +466,7 @@ img.friends-list-avatar {
|
||||
height: 14px;
|
||||
content: '';
|
||||
border-radius: 0px;
|
||||
mask-image: url(assets/images/masks/phone.svg);
|
||||
mask-image: url(./assets/images/masks/phone.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.active > img,
|
||||
@@ -461,7 +475,7 @@ img.friends-list-avatar {
|
||||
.x-friend-item > .avatar.askme > img,
|
||||
.x-friend-item > .avatar.busy > img,
|
||||
.x-friend-item > .avatar.offline > img {
|
||||
mask-image: url(assets/images/masks/usercutout.svg);
|
||||
mask-image: url(./assets/images/masks/usercutout.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.active::after,
|
||||
@@ -490,17 +504,17 @@ img.friends-list-avatar {
|
||||
|
||||
.x-friend-item > .avatar.joinme::after {
|
||||
background: #409eff;
|
||||
mask-image: url(assets/images/masks/joinme.svg);
|
||||
mask-image: url(./assets/images/masks/joinme.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.askme::after {
|
||||
background: #ff9500;
|
||||
mask-image: url(assets/images/masks/askme.svg);
|
||||
mask-image: url(./assets/images/masks/askme.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.busy::after {
|
||||
background: #ff2c2c;
|
||||
mask-image: url(assets/images/masks/busy.svg);
|
||||
mask-image: url(./assets/images/masks/busy.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.offline::after {
|
||||
@@ -565,16 +579,11 @@ img.friends-list-avatar {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.x-change-image-item > img,
|
||||
.x-change-image-item > .el-popover__reference-wrapper > img {
|
||||
width: 240px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.current-image {
|
||||
border: 2px solid #67c23a;
|
||||
padding: 2px 2px 0 2px;
|
||||
}
|
||||
// .x-change-image-item > img,
|
||||
// .x-change-image-item > .el-popover__reference-wrapper > img {
|
||||
// width: 240px;
|
||||
// height: 180px;
|
||||
// }
|
||||
|
||||
.x-dialog > .el-dialog {
|
||||
max-width: 100%;
|
||||
@@ -595,12 +604,12 @@ img.friends-list-avatar {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.el-popper.hex {
|
||||
min-width: auto;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
text-align: center;
|
||||
}
|
||||
// .el-popper.hex {
|
||||
// min-width: auto;
|
||||
// padding: 10px;
|
||||
// font-family: monospace;
|
||||
// text-align: center;
|
||||
// }
|
||||
|
||||
i.x-user-status,
|
||||
i.x-status-icon {
|
||||
@@ -621,17 +630,17 @@ i.x-user-status.online {
|
||||
|
||||
i.x-user-status.joinme {
|
||||
background: #409eff;
|
||||
mask-image: url(assets/images/masks/joinme.svg);
|
||||
mask-image: url(./assets/images/masks/joinme.svg);
|
||||
}
|
||||
|
||||
i.x-user-status.askme {
|
||||
background: #ff9500;
|
||||
mask-image: url(assets/images/masks/askme.svg);
|
||||
mask-image: url(./assets/images/masks/askme.svg);
|
||||
}
|
||||
|
||||
i.x-user-status.busy {
|
||||
background: #ff2c2c;
|
||||
mask-image: url(assets/images/masks/busy.svg);
|
||||
mask-image: url(./assets/images/masks/busy.svg);
|
||||
}
|
||||
|
||||
i.x-status-icon.green {
|
||||
@@ -651,67 +660,79 @@ i.x-status-icon.red {
|
||||
}
|
||||
|
||||
.x-tag-friend {
|
||||
color: rgb(255, 208, 0) !important;
|
||||
color: rgb(255, 208, 0);
|
||||
border-color: rgb(255, 208, 0) !important;
|
||||
}
|
||||
|
||||
.x-tag-vrcplus {
|
||||
color: rgb(255, 208, 0) !important;
|
||||
color: rgb(255, 208, 0);
|
||||
border-color: rgb(255, 208, 0) !important;
|
||||
}
|
||||
|
||||
.x-tag-platform-pc {
|
||||
color: #409eff !important;
|
||||
color: #409eff;
|
||||
border-color: #409eff !important;
|
||||
}
|
||||
|
||||
.x-tag-platform-quest {
|
||||
color: #67c23a !important;
|
||||
color: #67c23a;
|
||||
border-color: #67c23a !important;
|
||||
}
|
||||
|
||||
.x-tag-platform-ios {
|
||||
color: #c7c7ce !important;
|
||||
color: #c7c7ce;
|
||||
border-color: #c7c7ce !important;
|
||||
}
|
||||
|
||||
.x-tag-platform-other {
|
||||
color: #ff4177 !important;
|
||||
color: #ff4177;
|
||||
border-color: #ff4177 !important;
|
||||
}
|
||||
|
||||
.x-tag-age-verification {
|
||||
color: #5c70ec !important;
|
||||
color: #5c70ec;
|
||||
border-color: #5c70ec !important;
|
||||
}
|
||||
|
||||
.x-tag-border-left {
|
||||
border-left: 0.8px solid;
|
||||
margin-left: 5px;
|
||||
padding-left: 5px;
|
||||
padding-bottom: 0.5px;
|
||||
}
|
||||
|
||||
.x-grey {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.el-tree-node {
|
||||
white-space: normal;
|
||||
.x-popover-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.el-tree-node__content {
|
||||
height: auto;
|
||||
}
|
||||
// .el-tree-node {
|
||||
// white-space: normal;
|
||||
// }
|
||||
|
||||
.el-progress-bar {
|
||||
padding-right: 80px;
|
||||
margin-right: -85px;
|
||||
}
|
||||
// .el-tree-node__content {
|
||||
// height: auto;
|
||||
// }
|
||||
|
||||
.el-progress__text {
|
||||
color: #c8c8c8;
|
||||
}
|
||||
// .el-progress-bar {
|
||||
// padding-right: 80px;
|
||||
// margin-right: -85px;
|
||||
// }
|
||||
|
||||
.x-user-dialog .el-textarea__inner {
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
// .el-progress__text {
|
||||
// color: #c8c8c8;
|
||||
// }
|
||||
|
||||
// .x-user-dialog .el-textarea__inner {
|
||||
// padding: 0;
|
||||
// background: none;
|
||||
// border: 0;
|
||||
// border-radius: 2px;
|
||||
// }
|
||||
|
||||
.options-container {
|
||||
margin-top: 30px;
|
||||
@@ -767,28 +788,28 @@ i.x-status-icon.red {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.el-color-picker__trigger {
|
||||
border: unset;
|
||||
}
|
||||
// .el-color-picker__trigger {
|
||||
// border: unset;
|
||||
// }
|
||||
|
||||
.el-color-picker__color {
|
||||
border: 0.5px solid #999;
|
||||
}
|
||||
// .el-color-picker__color {
|
||||
// border: 0.5px solid #999;
|
||||
// }
|
||||
|
||||
.el-button--success {
|
||||
background-color: #67c23a !important;
|
||||
border-color: #67c23a !important;
|
||||
}
|
||||
// .el-button--success {
|
||||
// background-color: #67c23a !important;
|
||||
// border-color: #67c23a !important;
|
||||
// }
|
||||
|
||||
.x-dialog .el-button--danger {
|
||||
background-color: #f56c6c !important;
|
||||
border-color: #f56c6c !important;
|
||||
}
|
||||
// .x-dialog .el-button--danger {
|
||||
// background-color: #f56c6c !important;
|
||||
// border-color: #f56c6c !important;
|
||||
// }
|
||||
|
||||
.el-button--warning {
|
||||
background-color: #e6a23c !important;
|
||||
border-color: #e6a23c !important;
|
||||
}
|
||||
// .el-button--warning {
|
||||
// background-color: #e6a23c !important;
|
||||
// border-color: #e6a23c !important;
|
||||
// }
|
||||
|
||||
.avatar-info {
|
||||
margin-top: 2px;
|
||||
@@ -812,14 +833,14 @@ i.x-status-icon.red {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
// .el-form-item {
|
||||
// margin-bottom: 4px;
|
||||
// }
|
||||
|
||||
.photon-event-table .el-table--mini .el-table__cell,
|
||||
.current-instance-table .el-table--mini .el-table__cell {
|
||||
padding: 0;
|
||||
}
|
||||
// .photon-event-table .el-table--mini .el-table__cell,
|
||||
// .current-instance-table .el-table--mini .el-table__cell {
|
||||
// padding: 0;
|
||||
// }
|
||||
|
||||
.photon-event-table {
|
||||
margin-top: 20px;
|
||||
@@ -835,14 +856,14 @@ i.x-status-icon.red {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.el-pagination .el-select .el-input .el-input__inner,
|
||||
.el-input--mini .el-input__icon {
|
||||
height: 22px;
|
||||
}
|
||||
// .el-pagination .el-select .el-input .el-input__inner,
|
||||
// .el-input--mini .el-input__icon {
|
||||
// height: 22px;
|
||||
// }
|
||||
|
||||
.el-pagination .btn-next {
|
||||
margin-right: 10px;
|
||||
}
|
||||
// .el-pagination .btn-next {
|
||||
// margin-right: 10px;
|
||||
// }
|
||||
|
||||
.el-dialog,
|
||||
.el-message-box {
|
||||
@@ -850,35 +871,31 @@ i.x-status-icon.red {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.el-tabs__nav-wrap::after {
|
||||
background-color: #e4e7ed14;
|
||||
}
|
||||
// .el-tabs__nav-wrap::after {
|
||||
// background-color: #e4e7ed14;
|
||||
// }
|
||||
|
||||
.dialog-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.group-banner-image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vrc-instance-queue-message {
|
||||
padding: 3px;
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
.vrc-instance-queue-message .el-message__content {
|
||||
margin-right: 20px;
|
||||
}
|
||||
// .vrc-instance-queue-message .el-message__content {
|
||||
// margin-right: 20px;
|
||||
// }
|
||||
|
||||
.el-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
// .el-tabs {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// }
|
||||
|
||||
.el-tabs__content {
|
||||
flex: 1;
|
||||
max-height: 100%;
|
||||
// flex: 1;
|
||||
// max-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -889,60 +906,56 @@ i.x-status-icon.red {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.el-tabs__header {
|
||||
padding: 0 1px;
|
||||
}
|
||||
// .el-tabs__header {
|
||||
// padding: 0 1px;
|
||||
// }
|
||||
|
||||
.zero-margin-tabs .el-tabs__header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
// .zero-margin-tabs .el-tabs__header {
|
||||
// margin-bottom: 0;
|
||||
// }
|
||||
|
||||
.x-friend-item .el-checkbox__inner,
|
||||
.el-table__row .el-checkbox__inner {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
// .x-friend-item .el-checkbox__inner,
|
||||
// .el-table__row .el-checkbox__inner {
|
||||
// width: 28px;
|
||||
// height: 28px;
|
||||
// border-radius: 4px;
|
||||
// }
|
||||
|
||||
.x-friend-item .el-checkbox__inner::after,
|
||||
.el-table__row .el-checkbox__inner::after {
|
||||
width: 8px;
|
||||
height: 14px;
|
||||
left: 8px;
|
||||
top: 2px;
|
||||
}
|
||||
// .x-friend-item .el-checkbox__inner::after,
|
||||
// .el-table__row .el-checkbox__inner::after {
|
||||
// width: 8px;
|
||||
// height: 14px;
|
||||
// left: 8px;
|
||||
// top: 2px;
|
||||
// }
|
||||
|
||||
.max-height-el-select .el-select-dropdown__wrap {
|
||||
max-height: 83vh;
|
||||
}
|
||||
// .max-height-el-select .el-select-dropdown__wrap {
|
||||
// max-height: 83vh;
|
||||
// }
|
||||
|
||||
.el-pagination .el-input .el-input__icon {
|
||||
line-height: 22px;
|
||||
}
|
||||
// .el-pagination .el-input .el-input__icon {
|
||||
// line-height: 22px;
|
||||
// }
|
||||
|
||||
// User dialog memo: tag line-height
|
||||
.el-dialog__body .el-tag--mini {
|
||||
line-height: 17px;
|
||||
}
|
||||
// .el-dialog__body .el-tag--mini {
|
||||
// line-height: 17px;
|
||||
// }
|
||||
|
||||
// feed table detail time tag line-height
|
||||
.el-table__expanded-cell .el-tag--mini {
|
||||
line-height: 18px;
|
||||
}
|
||||
// .el-table__expanded-cell .el-tag--mini {
|
||||
// line-height: 18px;
|
||||
// }
|
||||
|
||||
// User dialog memo: input count background color
|
||||
.x-friend-item:hover .el-input__count {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
// .x-friend-item:hover .el-input__count {
|
||||
// background: #f0f0f0;
|
||||
// }
|
||||
|
||||
// Align the left page with the right friend bar
|
||||
.x-app > .x-container {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
line-height: 17px;
|
||||
}
|
||||
// .el-collapse-item .el-tag--mini {
|
||||
// line-height: 17px;
|
||||
// }
|
||||
|
||||
.x-text-removed {
|
||||
text-decoration: line-through;
|
||||
@@ -959,21 +972,38 @@ i.x-status-icon.red {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.el-carousel__mask {
|
||||
// looks bad
|
||||
display: none;
|
||||
// expand table cell on hover, TODO: replace with a better solution
|
||||
.el-table .cell {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
|
||||
.el-table__row:hover .el-table__cell .cell {
|
||||
display: revert;
|
||||
-webkit-line-clamp: unset;
|
||||
}
|
||||
|
||||
// .el-carousel__mask {
|
||||
// // looks bad
|
||||
// display: none;
|
||||
// }
|
||||
|
||||
// FIXME
|
||||
// Something changed the CSS of element-ui
|
||||
// The other parts are the same
|
||||
// It feels like can't fundamentally modify it
|
||||
// And can only fix it bit by bit by overriding
|
||||
.el-switch__core:after {
|
||||
top: 1.5px;
|
||||
}
|
||||
// .el-switch__core:after {
|
||||
// top: 1.5px;
|
||||
// }
|
||||
|
||||
.el-popover {
|
||||
text-align: left;
|
||||
word-break: break-word;
|
||||
// .el-popover {
|
||||
// text-align: left;
|
||||
// word-break: break-word;
|
||||
// }
|
||||
|
||||
.el-dropdown-menu__item:not(.is-disabled):focus {
|
||||
// dropdown item focus blue background looks bad
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -3,7 +3,7 @@
|
||||
--offy: calc(var(--offx) / 72 * 52);
|
||||
}
|
||||
.flags {
|
||||
background: url('/images/flags.png') no-repeat;
|
||||
background: url('../images/flags.png') no-repeat;
|
||||
background-size: calc(var(--offx) * 6);
|
||||
width: var(--offx);
|
||||
height: var(--offy);
|
||||
|
||||
@@ -145,7 +145,7 @@ button {
|
||||
div.x-friend-list
|
||||
> div:nth-child(1)
|
||||
> div
|
||||
> div.el-textarea.el-input--mini
|
||||
> div.el-textarea.el-input--small
|
||||
> span.el-input__count {
|
||||
background-color: $--theme-bg-4;
|
||||
}
|
||||
@@ -168,7 +168,7 @@ div.x-friend-list
|
||||
background-color: $--theme-bg-5;
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
.el-collapse-item .el-tag--small {
|
||||
background-color: $--theme-bg-5;
|
||||
border: transparent;
|
||||
}
|
||||
@@ -220,11 +220,11 @@ div.x-friend-list
|
||||
background-color: hsl($--theme-hue, $--theme-saturation, 27%);
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::after {
|
||||
border-bottom-color: $--theme-bg-5;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
|
||||
border-bottom-color: $--theme-border-2;
|
||||
}
|
||||
|
||||
@@ -303,11 +303,11 @@ div.x-friend-list
|
||||
border-color: hsl($--theme-hue, $--theme-saturation, 37%);
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow::after {
|
||||
border-right-color: hsl($--theme-hue, $--theme-saturation, 37%);
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow {
|
||||
border-right-color: hsl($--theme-hue, $--theme-saturation, 37%);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,104 @@ $--theme-warning: #e6a23c;
|
||||
$--theme-danger: #f56c6c;
|
||||
$--theme-info: #909399;
|
||||
|
||||
// Mapping
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
|
||||
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||
$colors: (
|
||||
'white': $--theme-text-1,
|
||||
'black': $--theme-bg-1,
|
||||
'primary': (
|
||||
'base': $--theme-primary
|
||||
),
|
||||
'success': (
|
||||
'base': $--theme-success
|
||||
),
|
||||
'warning': (
|
||||
'base': $--theme-warning
|
||||
),
|
||||
'danger': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'error': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'info': (
|
||||
'base': $--theme-info
|
||||
)
|
||||
),
|
||||
$text-color: (
|
||||
'primary': $--theme-text-1,
|
||||
'regular': $--theme-text-2,
|
||||
'secondary': $--theme-info,
|
||||
'placeholder': $--theme-text-4,
|
||||
'disabled': $--theme-text-4
|
||||
),
|
||||
$border-color: (
|
||||
'': $--theme-border-1,
|
||||
'light': $--theme-border-2,
|
||||
'lighter': $--border-color-lighter,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-border-1,
|
||||
'darker': $--theme-border-2
|
||||
),
|
||||
$fill-color: (
|
||||
'': $--background-color-base,
|
||||
'light': $--theme-bg-4,
|
||||
'lighter': $--theme-bg-5,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-bg-2,
|
||||
'darker': $--theme-bg-1,
|
||||
'blank': $--theme-bg-4
|
||||
),
|
||||
$bg-color: (
|
||||
'': $--theme-bg-2,
|
||||
'page': $--theme-bg-1,
|
||||
'overlay': $--theme-bg-4
|
||||
),
|
||||
$box-shadow: (
|
||||
'': (
|
||||
0px 12px 32px 4px rgba(0, 0, 0, 0.36),
|
||||
0px 8px 20px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'light': (
|
||||
0px 0px 12px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'lighter': (
|
||||
0px 0px 6px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'dark': (
|
||||
0px 16px 48px 16px rgba(0, 0, 0, 0.72),
|
||||
0px 12px 32px rgba(0, 0, 0, 0.72),
|
||||
0px 8px 16px -8px rgba(0, 0, 0, 0.96)
|
||||
)
|
||||
),
|
||||
$disabled: (
|
||||
'bg-color': $--theme-bg-5,
|
||||
'text-color': $--theme-text-4,
|
||||
'border-color': $--theme-border-2
|
||||
)
|
||||
);
|
||||
|
||||
$--color-primary: $--theme-primary;
|
||||
$--color-success: $--theme-success;
|
||||
@@ -52,10 +149,6 @@ $--color-text-placeholder: $--theme-text-4;
|
||||
|
||||
$--border-color-base: $--theme-border-1;
|
||||
$--border-color-light: $--theme-bg-5;
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
|
||||
$--button-default-background-color: $--theme-bg-5;
|
||||
$--button-default-border-color: $--theme-border-2;
|
||||
@@ -64,20 +157,13 @@ $--button-default-font-color: $--theme-text-2;
|
||||
$--input-background-color: $--theme-bg-5;
|
||||
$--input-border-color: $--theme-border-1;
|
||||
$--input-font-color: $--theme-text-1;
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-input-focus-border-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--select-input-focus-border-color: $--input-focus-border;
|
||||
|
||||
$--dialog-background-color: $--theme-bg-4;
|
||||
$--popover-background-color: $--theme-bg-4;
|
||||
|
||||
$--table-border-color: $--theme-border-2;
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
|
||||
$--pagination-font-color: $--theme-text-2;
|
||||
$--pagination-background-color: $--theme-bg-4;
|
||||
@@ -95,21 +181,11 @@ $--datepicker-header-font-color: $--theme-text-1;
|
||||
$--datepicker-icon-color: $--theme-text-1;
|
||||
|
||||
$--skeleton-color: $--theme-bg-5;
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-dropdown-background: $--theme-bg-4;
|
||||
$--select-dropdown-border: 1px solid $--theme-border-1;
|
||||
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
|
||||
$--tree-font-color: $--theme-text-2;
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
|
||||
$--menu-item-font-color: $--theme-text-2;
|
||||
$--menu-background-color: $--theme-bg-2;
|
||||
@@ -118,10 +194,8 @@ $--collapse-header-background-color: $--theme-bg-5;
|
||||
$--collapse-content-background-color: $--theme-bg-4;
|
||||
$--collapse-border-color: $--theme-border-2;
|
||||
$--collapse-header-font-color: $--theme-text-2;
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
|
||||
$--message-background-color: $--theme-bg-5;
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--message-close-hover-color: $--theme-text-2;
|
||||
$--message-success-font-color: #52c41a;
|
||||
$--message-info-font-color: #1890ff;
|
||||
@@ -130,13 +204,8 @@ $--message-danger-font-color: #ff4d4f;
|
||||
|
||||
$--pagination-hover-color: $--theme-text-4;
|
||||
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
$--card-background-color: $--theme-bg-4;
|
||||
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@import '~element-ui/packages/theme-chalk/src/index';
|
||||
|
||||
:root {
|
||||
--group-calendar-event-bg: #{$--calendar-selected-background-color};
|
||||
--group-calendar-badge-following: #{darken($--theme-primary, 20%)};
|
||||
@@ -218,35 +287,35 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
background-color: #{darken($--theme-primary, 20%)} !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow::after {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::after {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow::after {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow::after {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,105 @@ $--theme-warning: #e6a23c;
|
||||
$--theme-danger: #f56c6c;
|
||||
$--theme-info: #909399;
|
||||
|
||||
// Mapping
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px
|
||||
hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
|
||||
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||
$colors: (
|
||||
'white': $--theme-text-1,
|
||||
'black': $--theme-bg-1,
|
||||
'primary': (
|
||||
'base': $--theme-primary
|
||||
),
|
||||
'success': (
|
||||
'base': $--theme-success
|
||||
),
|
||||
'warning': (
|
||||
'base': $--theme-warning
|
||||
),
|
||||
'danger': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'error': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'info': (
|
||||
'base': $--theme-info
|
||||
)
|
||||
),
|
||||
$text-color: (
|
||||
'primary': $--theme-text-1,
|
||||
'regular': $--theme-text-2,
|
||||
'secondary': $--theme-info,
|
||||
'placeholder': $--theme-text-4,
|
||||
'disabled': $--theme-text-4
|
||||
),
|
||||
$border-color: (
|
||||
'': $--theme-border-1,
|
||||
'light': $--theme-border-2,
|
||||
'lighter': $--border-color-lighter,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-border-1,
|
||||
'darker': $--theme-border-2
|
||||
),
|
||||
$fill-color: (
|
||||
'': $--background-color-base,
|
||||
'light': $--theme-bg-4,
|
||||
'lighter': $--theme-bg-5,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-bg-2,
|
||||
'darker': $--theme-bg-1,
|
||||
'blank': $--theme-bg-4
|
||||
),
|
||||
$bg-color: (
|
||||
'': $--theme-bg-2,
|
||||
'page': $--theme-bg-1,
|
||||
'overlay': $--theme-bg-4
|
||||
),
|
||||
$box-shadow: (
|
||||
'': (
|
||||
0px 12px 32px 4px rgba(0, 0, 0, 0.36),
|
||||
0px 8px 20px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'light': (
|
||||
0px 0px 12px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'lighter': (
|
||||
0px 0px 6px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'dark': (
|
||||
0px 16px 48px 16px rgba(0, 0, 0, 0.72),
|
||||
0px 12px 32px rgba(0, 0, 0, 0.72),
|
||||
0px 8px 16px -8px rgba(0, 0, 0, 0.96)
|
||||
)
|
||||
),
|
||||
$disabled: (
|
||||
'bg-color': $--theme-bg-5,
|
||||
'text-color': $--theme-text-4,
|
||||
'border-color': $--theme-border-2
|
||||
)
|
||||
);
|
||||
|
||||
$--color-primary: $--theme-primary;
|
||||
$--color-success: $--theme-success;
|
||||
@@ -50,10 +148,6 @@ $--color-text-placeholder: $--theme-text-4;
|
||||
|
||||
$--border-color-base: $--theme-border-1;
|
||||
$--border-color-light: $--theme-bg-5;
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
|
||||
$--button-default-background-color: $--theme-bg-5;
|
||||
$--button-default-border-color: $--theme-border-2;
|
||||
@@ -62,20 +156,13 @@ $--button-default-font-color: $--theme-text-2;
|
||||
$--input-background-color: $--theme-bg-5;
|
||||
$--input-border-color: $--theme-border-1;
|
||||
$--input-font-color: $--theme-text-1;
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-input-focus-border-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--select-input-focus-border-color: $--input-focus-border;
|
||||
|
||||
$--dialog-background-color: $--theme-bg-4;
|
||||
$--popover-background-color: $--theme-bg-4;
|
||||
|
||||
$--table-border-color: $--theme-border-2;
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
|
||||
$--pagination-font-color: $--theme-text-2;
|
||||
$--pagination-background-color: $--theme-bg-4;
|
||||
@@ -93,22 +180,11 @@ $--datepicker-header-font-color: $--theme-text-1;
|
||||
$--datepicker-icon-color: $--theme-text-1;
|
||||
|
||||
$--skeleton-color: $--theme-bg-5;
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-dropdown-background: $--theme-bg-4;
|
||||
$--select-dropdown-border: 1px solid $--theme-border-1;
|
||||
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px
|
||||
hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
|
||||
$--tree-font-color: $--theme-text-2;
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
|
||||
$--menu-item-font-color: $--theme-text-2;
|
||||
$--menu-background-color: $--theme-bg-2;
|
||||
@@ -117,10 +193,8 @@ $--collapse-header-background-color: $--theme-bg-5;
|
||||
$--collapse-content-background-color: $--theme-bg-4;
|
||||
$--collapse-border-color: $--theme-border-2;
|
||||
$--collapse-header-font-color: $--theme-text-2;
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
|
||||
$--message-background-color: $--theme-bg-5;
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--message-close-hover-color: $--theme-text-2;
|
||||
$--message-success-font-color: #52c41a;
|
||||
$--message-info-font-color: #1890ff;
|
||||
@@ -129,13 +203,8 @@ $--message-danger-font-color: #ff4d4f;
|
||||
|
||||
$--pagination-hover-color: $--theme-text-4;
|
||||
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
$--card-background-color: $--theme-bg-4;
|
||||
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@import '~element-ui/packages/theme-chalk/src/index';
|
||||
|
||||
:root {
|
||||
--group-calendar-event-bg: #{$--calendar-selected-background-color};
|
||||
--group-calendar-badge-following: #{$--color-success};
|
||||
@@ -170,35 +239,35 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
background-color: $--theme-border-2 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow::after {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::after {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow::after {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow::after {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,105 @@ $--theme-warning: #e6a23c;
|
||||
$--theme-danger: #f56c6c;
|
||||
$--theme-info: #909399;
|
||||
|
||||
// Mapping
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px
|
||||
hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
|
||||
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||
$colors: (
|
||||
'white': $--theme-text-1,
|
||||
'black': $--theme-bg-1,
|
||||
'primary': (
|
||||
'base': $--theme-primary
|
||||
),
|
||||
'success': (
|
||||
'base': $--theme-success
|
||||
),
|
||||
'warning': (
|
||||
'base': $--theme-warning
|
||||
),
|
||||
'danger': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'error': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'info': (
|
||||
'base': $--theme-info
|
||||
)
|
||||
),
|
||||
$text-color: (
|
||||
'primary': $--theme-text-1,
|
||||
'regular': $--theme-text-2,
|
||||
'secondary': $--theme-info,
|
||||
'placeholder': $--theme-text-4,
|
||||
'disabled': $--theme-text-4
|
||||
),
|
||||
$border-color: (
|
||||
'': $--theme-border-1,
|
||||
'light': $--theme-border-2,
|
||||
'lighter': $--border-color-lighter,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-border-1,
|
||||
'darker': $--theme-border-2
|
||||
),
|
||||
$fill-color: (
|
||||
'': $--background-color-base,
|
||||
'light': $--theme-bg-4,
|
||||
'lighter': $--theme-bg-5,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-bg-2,
|
||||
'darker': $--theme-bg-1,
|
||||
'blank': $--theme-bg-4
|
||||
),
|
||||
$bg-color: (
|
||||
'': $--theme-bg-2,
|
||||
'page': $--theme-bg-1,
|
||||
'overlay': $--theme-bg-4
|
||||
),
|
||||
$box-shadow: (
|
||||
'': (
|
||||
0px 12px 32px 4px hsla($--theme-hue, $--theme-saturation, 0%, 0.36),
|
||||
0px 8px 20px hsla($--theme-hue, $--theme-saturation, 0%, 0.72)
|
||||
),
|
||||
'light': (
|
||||
0px 0px 12px hsla($--theme-hue, $--theme-saturation, 0%, 0.72)
|
||||
),
|
||||
'lighter': (
|
||||
0px 0px 6px hsla($--theme-hue, $--theme-saturation, 0%, 0.72)
|
||||
),
|
||||
'dark': (
|
||||
0px 16px 48px 16px hsla($--theme-hue, $--theme-saturation, 0%, 0.72),
|
||||
0px 12px 32px hsla($--theme-hue, $--theme-saturation, 0%, 0.72),
|
||||
0px 8px 16px -8px hsla($--theme-hue, $--theme-saturation, 0%, 0.96)
|
||||
)
|
||||
),
|
||||
$disabled: (
|
||||
'bg-color': $--theme-bg-5,
|
||||
'text-color': $--theme-text-4,
|
||||
'border-color': $--theme-border-2
|
||||
)
|
||||
);
|
||||
|
||||
$--color-primary: $--theme-primary;
|
||||
$--color-success: $--theme-success;
|
||||
@@ -50,10 +148,6 @@ $--color-text-placeholder: $--theme-text-4;
|
||||
|
||||
$--border-color-base: $--theme-border-1;
|
||||
$--border-color-light: $--theme-bg-5;
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
|
||||
$--button-default-background-color: $--theme-bg-5;
|
||||
$--button-default-border-color: $--theme-border-2;
|
||||
@@ -62,20 +156,13 @@ $--button-default-font-color: $--theme-text-2;
|
||||
$--input-background-color: $--theme-bg-5;
|
||||
$--input-border-color: $--theme-border-1;
|
||||
$--input-font-color: $--theme-text-1;
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-input-focus-border-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--select-input-focus-border-color: $--input-focus-border;
|
||||
|
||||
$--dialog-background-color: $--theme-bg-4;
|
||||
$--popover-background-color: $--theme-bg-4;
|
||||
|
||||
$--table-border-color: $--theme-border-2;
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
|
||||
$--pagination-font-color: $--theme-text-2;
|
||||
$--pagination-background-color: $--theme-bg-4;
|
||||
@@ -93,22 +180,11 @@ $--datepicker-header-font-color: $--theme-text-1;
|
||||
$--datepicker-icon-color: $--theme-text-1;
|
||||
|
||||
$--skeleton-color: $--theme-bg-5;
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-dropdown-background: $--theme-bg-4;
|
||||
$--select-dropdown-border: 1px solid $--theme-border-1;
|
||||
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px
|
||||
hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
|
||||
$--tree-font-color: $--theme-text-2;
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
|
||||
$--menu-item-font-color: $--theme-text-2;
|
||||
$--menu-background-color: $--theme-bg-2;
|
||||
@@ -117,10 +193,8 @@ $--collapse-header-background-color: $--theme-bg-5;
|
||||
$--collapse-content-background-color: $--theme-bg-4;
|
||||
$--collapse-border-color: $--theme-border-2;
|
||||
$--collapse-header-font-color: $--theme-text-2;
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
|
||||
$--message-background-color: $--theme-bg-5;
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--message-close-hover-color: $--theme-text-2;
|
||||
$--message-success-font-color: #52c41a;
|
||||
$--message-info-font-color: #1890ff;
|
||||
@@ -129,13 +203,8 @@ $--message-danger-font-color: #ff4d4f;
|
||||
|
||||
$--pagination-hover-color: $--theme-text-4;
|
||||
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
$--card-background-color: $--theme-bg-4;
|
||||
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@import '~element-ui/packages/theme-chalk/src/index';
|
||||
|
||||
:root {
|
||||
--group-calendar-event-bg: #{$--calendar-selected-background-color};
|
||||
--group-calendar-badge-following: #{$--color-success};
|
||||
@@ -170,35 +239,35 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
background-color: $--theme-border-2 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow::after {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::after {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow::after {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow::after {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -598,7 +598,7 @@ img.friends-list-avatar {
|
||||
border-radius: var(--dv_lg-rounded) !important;
|
||||
}
|
||||
|
||||
.el-tag--mini {
|
||||
.el-tag--small {
|
||||
height: 30px;
|
||||
padding: 5px 15px;
|
||||
font-size: 10pt;
|
||||
@@ -627,7 +627,7 @@ img.friends-list-avatar {
|
||||
background-color: var(--dv_muted);
|
||||
}
|
||||
|
||||
.el-input--mini .el-textarea__inner:hover {
|
||||
.el-input--small .el-textarea__inner:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
@@ -730,7 +730,7 @@ i[class='el-icon-star-off']:not(.el-menu-item div.el-tooltip i) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
.el-collapse-item .el-tag--small {
|
||||
border: transparent;
|
||||
background-color: var(--dv_bg-bot);
|
||||
}
|
||||
@@ -786,7 +786,7 @@ i[class='el-icon-star-off']:not(.el-menu-item div.el-tooltip i) {
|
||||
div.x-friend-list
|
||||
> div:nth-child(1)
|
||||
> div
|
||||
> div.el-textarea.el-input--mini
|
||||
> div.el-textarea.el-input--small
|
||||
> span.el-input__count {
|
||||
background-color: var(--mid) !important;
|
||||
}
|
||||
@@ -831,3 +831,27 @@ div.x-friend-list
|
||||
.group-header {
|
||||
border-bottom: 2px solid var(--dv_muted) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper {
|
||||
background-color: var(--dv_bg-top) !important;
|
||||
border: 1px solid var(--dv_muted) !important;
|
||||
color: var(--dv_bright);
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-focused {
|
||||
border-color: var(--dv_bright) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-disabled {
|
||||
background-color: var(--dv_bg-bot) !important;
|
||||
color: var(--dv_muted) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: var(--dv_bg-top) !important;
|
||||
border: 1px solid var(--dv_muted) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper.is-focus {
|
||||
border-color: var(--dv_bright) !important;
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ path[stroke='#e5e9f2'] {
|
||||
stroke: var(--farback) !important;
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
.el-collapse-item .el-tag--small {
|
||||
border: transparent;
|
||||
background-color: #333 !important;
|
||||
}
|
||||
@@ -372,7 +372,7 @@ path[stroke='#e5e9f2'] {
|
||||
div.x-friend-list
|
||||
> div:nth-child(1)
|
||||
> div
|
||||
> div.el-textarea.el-input--mini
|
||||
> div.el-textarea.el-input--small
|
||||
> span.el-input__count {
|
||||
background-color: var(--mid) !important;
|
||||
}
|
||||
@@ -402,3 +402,27 @@ div.x-friend-list
|
||||
.el-timeline-item__node {
|
||||
background-color: var(--theme-text-muted) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper {
|
||||
background-color: var(--top) !important;
|
||||
border: 1px solid var(--top-border) !important;
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-focused {
|
||||
border-color: var(--theme-text) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-disabled {
|
||||
background-color: var(--farback) !important;
|
||||
color: var(--theme-text-muted) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: var(--top) !important;
|
||||
border: 1px solid var(--top-border) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper.is-focus {
|
||||
border-color: var(--theme-text) !important;
|
||||
}
|
||||
|
||||
@@ -1651,35 +1651,35 @@ img.x-link.el-popover__reference {
|
||||
border-color: rgb(var(--md-sys-color-outline-variant)) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow {
|
||||
border-top-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow::after {
|
||||
border-top-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
|
||||
border-bottom-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::after {
|
||||
border-bottom-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow {
|
||||
border-left-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow::after {
|
||||
border-left-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow {
|
||||
border-right-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow::after {
|
||||
border-right-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
@@ -2065,7 +2065,7 @@ i.x-user-status {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
.el-collapse-item .el-tag--small {
|
||||
background-color: transparent;
|
||||
border: transparent;
|
||||
padding-top: 6px;
|
||||
@@ -2074,7 +2074,7 @@ i.x-user-status {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.el-dialog__body .el-tag--mini {
|
||||
.el-dialog__body .el-tag--small {
|
||||
line-height: 28px;
|
||||
}
|
||||
.el-divider {
|
||||
@@ -2149,7 +2149,7 @@ i.x-user-status {
|
||||
div.x-friend-list
|
||||
> div:nth-child(1)
|
||||
> div
|
||||
> div.el-textarea.el-input--mini
|
||||
> div.el-textarea.el-input--small
|
||||
> span.el-input__count {
|
||||
background-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
@@ -2192,3 +2192,44 @@ div.x-friend-list
|
||||
.group-header {
|
||||
border-bottom: 2px solid rgba(var(--md-sys-color-outline), 0.5) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper {
|
||||
background-color: rgb(var(--md-sys-color-surface-variant)) !important;
|
||||
border: 1px solid rgb(var(--md-sys-color-outline-variant)) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-focused {
|
||||
border-color: rgb(var(--md-sys-color-primary)) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-disabled {
|
||||
background-color: rgba(var(--md-sys-color-on-surface), 0.12) !important;
|
||||
color: rgba(var(--md-sys-color-on-surface), 0.38) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: rgb(var(--md-sys-color-surface-variant)) !important;
|
||||
border: 1px solid rgb(var(--md-sys-color-outline-variant)) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper.is-focus {
|
||||
border-color: rgb(var(--md-sys-color-primary)) !important;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
background-color: rgb(var(--md-sys-color-surface)) !important;
|
||||
}
|
||||
|
||||
.el-table tr,
|
||||
.el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell {
|
||||
background-color: rgba(var(--md-sys-color-primary), 0.05) !important;
|
||||
}
|
||||
|
||||
.el-table__body-wrapper .el-table__row:hover > .el-table__cell {
|
||||
background-color: rgba(var(--md-sys-color-primary), 0.08) !important;
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ input[type='checkbox']:checked + .el-switch__core {
|
||||
background-color: var(--lighter-lighter-bg);
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
.el-collapse-item .el-tag--small {
|
||||
border: transparent;
|
||||
background-color: var(--lighter-lighter-bg);
|
||||
}
|
||||
@@ -506,3 +506,45 @@ input[type='checkbox']:checked + .el-switch__core {
|
||||
.group-header {
|
||||
border-bottom: 2px solid var(--lighter-bg) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper {
|
||||
background-color: var(--light-bg) !important;
|
||||
border: 1px solid var(--lighter-bg) !important;
|
||||
color: var(--theme);
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-focused {
|
||||
border-color: var(--theme) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-disabled {
|
||||
background-color: var(--bg) !important;
|
||||
color: var(--lighter-border) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: var(--light-bg) !important;
|
||||
border: 1px solid var(--lighter-bg) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper.is-focus {
|
||||
border-color: var(--theme) !important;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
background-color: var(--bg) !important;
|
||||
}
|
||||
|
||||
.el-table tr,
|
||||
.el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell {
|
||||
background-color: var(--light-bg) !important;
|
||||
}
|
||||
|
||||
.el-table__body-wrapper .el-table__row:hover > .el-table__cell {
|
||||
background-color: var(--lighter-bg) !important;
|
||||
}
|
||||
|
||||
Vendored
+5
-39
@@ -1,16 +1,6 @@
|
||||
import '@fontsource/noto-sans-kr';
|
||||
import '@fontsource/noto-sans-jp';
|
||||
import '@fontsource/noto-sans-sc';
|
||||
import '@fontsource/noto-sans-tc';
|
||||
|
||||
import 'remixicon/fonts/remixicon.css';
|
||||
|
||||
import Vue from 'vue';
|
||||
import { PiniaVuePlugin } from 'pinia';
|
||||
import { DataTables } from 'vue-data-tables';
|
||||
import VueLazyload from 'vue-lazyload';
|
||||
import configRepository from './service/config';
|
||||
import vrcxJsonStorage from './service/jsonStorage';
|
||||
|
||||
import {
|
||||
changeAppDarkStyle,
|
||||
changeAppThemeStyle,
|
||||
@@ -18,8 +8,10 @@ import {
|
||||
refreshCustomScript,
|
||||
systemIsDarkMode
|
||||
} from './shared/utils';
|
||||
import { i18n } from './plugin';
|
||||
import { i18n } from './plugin/i18n';
|
||||
import { initNoty } from './plugin/noty';
|
||||
import './plugin/ipc';
|
||||
import './plugin/dayjs';
|
||||
|
||||
initNoty(false);
|
||||
|
||||
@@ -42,7 +34,7 @@ function setLoginContainerStyle(isDarkMode) {
|
||||
background-color: ${backgroundColor} !important;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
.x-login-container .el-input__inner {
|
||||
background-color: ${inputBackgroundColor} !important;
|
||||
border: ${inputBorder} !important;
|
||||
@@ -88,33 +80,7 @@ try {
|
||||
refreshCustomCss();
|
||||
refreshCustomScript();
|
||||
|
||||
Vue.use(PiniaVuePlugin);
|
||||
Vue.use(DataTables);
|
||||
|
||||
Vue.use(VueLazyload, {
|
||||
preLoad: 1,
|
||||
observer: true,
|
||||
observerOptions: {
|
||||
rootMargin: '0px',
|
||||
threshold: 0
|
||||
},
|
||||
attempt: 3
|
||||
});
|
||||
|
||||
new vrcxJsonStorage(VRCXStorage);
|
||||
|
||||
// some workaround for failing to get voice list first run
|
||||
speechSynthesis.getVoices();
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Vue.config.errorHandler = function (err, vm, info) {
|
||||
console.error('Vue Error:', err);
|
||||
console.error('Component:', vm);
|
||||
console.error('Error Info:', info);
|
||||
};
|
||||
Vue.config.warnHandler = function (msg, vm, trace) {
|
||||
console.warn('Vue Warning:', msg);
|
||||
console.warn('Component:', vm);
|
||||
console.warn('Trace:', trace);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<div class="data-table-wrapper">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="paginatedData"
|
||||
v-bind="mergedTableProps"
|
||||
default-sort-prop="created_at"
|
||||
default-sort-order="descending"
|
||||
lazy
|
||||
@sort-change="handleSortChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-click="handleRowClick">
|
||||
<slot></slot>
|
||||
</el-table>
|
||||
|
||||
<div v-if="showPagination" class="pagination-wrapper">
|
||||
<el-pagination
|
||||
:current-page="internalCurrentPage"
|
||||
:page-size="internalPageSize"
|
||||
:total="filteredData.length"
|
||||
v-bind="mergedPaginationProps"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, ref, watch, toRefs } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'DataTable',
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
tableProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
paginationProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
filters: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'table, pagination'
|
||||
},
|
||||
defaultSort: {
|
||||
type: Object,
|
||||
default: () => ({ prop: 'created_at', order: 'descending' })
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'update:currentPage',
|
||||
'update:pageSize',
|
||||
'size-change',
|
||||
'current-change',
|
||||
'selection-change',
|
||||
'row-click',
|
||||
'filtered-data'
|
||||
],
|
||||
setup(props, { emit }) {
|
||||
const { data, currentPage, pageSize, tableProps, paginationProps, filters } = toRefs(props);
|
||||
|
||||
const internalCurrentPage = ref(currentPage.value);
|
||||
const internalPageSize = ref(pageSize.value);
|
||||
const sortData = ref({
|
||||
prop: props.defaultSort?.prop || null,
|
||||
order: props.defaultSort?.order || null
|
||||
});
|
||||
|
||||
const showPagination = computed(() => {
|
||||
return props.layout.includes('pagination');
|
||||
});
|
||||
|
||||
const mergedTableProps = computed(() => ({
|
||||
stripe: true,
|
||||
...tableProps.value
|
||||
}));
|
||||
|
||||
const mergedPaginationProps = computed(() => ({
|
||||
layout: 'sizes, prev, pager, next, total',
|
||||
pageSizes: [20, 50, 100, 200],
|
||||
small: true,
|
||||
...paginationProps.value
|
||||
}));
|
||||
|
||||
const applyFilter = function (row, filter) {
|
||||
if (Array.isArray(filter.prop)) {
|
||||
return filter.prop.some((propItem) => applyFilter(row, { prop: propItem, value: filter.value }));
|
||||
}
|
||||
|
||||
const cellValue = row[filter.prop];
|
||||
if (cellValue === undefined || cellValue === null) return false;
|
||||
|
||||
if (Array.isArray(filter.value)) {
|
||||
return filter.value.some((val) =>
|
||||
String(cellValue).toLowerCase().includes(String(val).toLowerCase())
|
||||
);
|
||||
} else {
|
||||
return String(cellValue).toLowerCase().includes(String(filter.value).toLowerCase());
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = computed(() => {
|
||||
let result = [...data.value];
|
||||
|
||||
if (filters.value && Array.isArray(filters.value) && filters.value.length > 0) {
|
||||
filters.value.forEach((filter) => {
|
||||
if (filter.value && (!Array.isArray(filter.value) || filter.value.length > 0)) {
|
||||
result = result.filter((row) => applyFilter(row, filter));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sortData.value.prop && sortData.value.order) {
|
||||
const { prop, order } = sortData.value;
|
||||
result.sort((a, b) => {
|
||||
const aVal = a[prop];
|
||||
const bVal = b[prop];
|
||||
let comparison = 0;
|
||||
|
||||
if (aVal == null && bVal == null) return 0;
|
||||
if (aVal == null) return 1;
|
||||
if (bVal == null) return -1;
|
||||
|
||||
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
||||
comparison = aVal - bVal;
|
||||
} else if (aVal instanceof Date && bVal instanceof Date) {
|
||||
comparison = aVal.getTime() - bVal.getTime();
|
||||
} else {
|
||||
const aStr = String(aVal).toLowerCase();
|
||||
const bStr = String(bVal).toLowerCase();
|
||||
if (aStr > bStr) comparison = 1;
|
||||
else if (aStr < bStr) comparison = -1;
|
||||
}
|
||||
|
||||
return order === 'descending' ? -comparison : comparison;
|
||||
});
|
||||
}
|
||||
|
||||
emit('filtered-data', result);
|
||||
return result;
|
||||
});
|
||||
|
||||
const paginatedData = computed(() => {
|
||||
if (!showPagination.value) {
|
||||
return filteredData.value;
|
||||
}
|
||||
|
||||
const start = (internalCurrentPage.value - 1) * internalPageSize.value;
|
||||
const end = start + internalPageSize.value;
|
||||
return filteredData.value.slice(start, end);
|
||||
});
|
||||
|
||||
const handleSortChange = ({ prop, order }) => {
|
||||
sortData.value = { prop, order };
|
||||
};
|
||||
|
||||
const handleSelectionChange = (selection) => {
|
||||
emit('selection-change', selection);
|
||||
};
|
||||
|
||||
const handleRowClick = (row, column, event) => {
|
||||
emit('row-click', row, column, event);
|
||||
};
|
||||
|
||||
const handleSizeChange = (size) => {
|
||||
internalPageSize.value = size;
|
||||
};
|
||||
|
||||
const handleCurrentChange = (page) => {
|
||||
internalCurrentPage.value = page;
|
||||
};
|
||||
|
||||
watch(currentPage, (newVal) => {
|
||||
internalCurrentPage.value = newVal;
|
||||
});
|
||||
|
||||
watch(pageSize, (newVal) => {
|
||||
internalPageSize.value = newVal;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.defaultSort,
|
||||
(newSort) => {
|
||||
if (newSort) {
|
||||
sortData.value = {
|
||||
prop: newSort.prop,
|
||||
order: newSort.order
|
||||
};
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return {
|
||||
internalCurrentPage,
|
||||
internalPageSize,
|
||||
showPagination,
|
||||
mergedTableProps,
|
||||
mergedPaginationProps,
|
||||
filteredData,
|
||||
paginatedData,
|
||||
handleSortChange,
|
||||
handleSelectionChange,
|
||||
handleRowClick,
|
||||
handleSizeChange,
|
||||
handleCurrentChange
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.data-table-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
<div
|
||||
class="avatar"
|
||||
:class="isFriendActiveOrOffline ? undefined : userStatusClass(friend.ref, friend.pendingOffline)">
|
||||
<img v-lazy="userImage(friend.ref, true)" />
|
||||
<img :src="userImage(friend.ref, true)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span v-if="!hideNicknames && friend.$nickName" class="name" :style="{ color: friend.ref.$userColour }">
|
||||
@@ -17,21 +17,18 @@
|
||||
<span v-if="isFriendActiveOrOffline" class="extra">{{ friend.ref.statusDescription }}</span>
|
||||
<template v-else>
|
||||
<span v-if="friend.pendingOffline" class="extra">
|
||||
<i class="el-icon-warning-outline" /> {{ $t('side_panel.pending_offline') }}
|
||||
<el-icon><WarningFilled /></el-icon> {{ t('side_panel.pending_offline') }}
|
||||
</span>
|
||||
<template v-else-if="isGroupByInstance">
|
||||
<i v-if="isFriendTraveling" class="el-icon el-icon-loading"></i>
|
||||
<el-icon v-if="isFriendTraveling" class="is-loading" style="margin-right: 3px"
|
||||
><Loading
|
||||
/></el-icon>
|
||||
<Timer
|
||||
class="extra"
|
||||
:epoch="epoch"
|
||||
:style="isFriendTraveling ? { display: 'inline-block', overflow: 'unset' } : undefined" />
|
||||
</template>
|
||||
<Location
|
||||
v-else
|
||||
class="extra"
|
||||
:location="friend.ref.location"
|
||||
:traveling="friend.ref.travelingToLocation"
|
||||
:link="false" />
|
||||
<Location v-else class="extra" :location="locationProp" :traveling="travelingProp" :link="false" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,15 +36,15 @@
|
||||
<span>{{ friend.name || friend.id }}</span>
|
||||
<el-button
|
||||
ttype="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
style="margin-left: 5px"
|
||||
@click.stop="$emit('confirm-delete-friend', friend.id)">
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<el-skeleton v-else animated class="skeleton" :throttle="100">
|
||||
<template slot="template">
|
||||
<template #template>
|
||||
<div>
|
||||
<el-skeleton-item variant="circle" />
|
||||
<div>
|
||||
@@ -61,8 +58,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { WarningFilled, Close, Loading } from '@element-plus/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { userImage, userStatusClass } from '../shared/utils';
|
||||
import { useAppearanceSettingsStore, useFriendStore } from '../stores';
|
||||
|
||||
@@ -71,21 +70,27 @@
|
||||
isGroupByInstance: Boolean
|
||||
});
|
||||
|
||||
defineEmits(['click', 'confirm-delete-friend']);
|
||||
|
||||
const { hideNicknames } = storeToRefs(useAppearanceSettingsStore());
|
||||
const { isRefreshFriendsLoading } = storeToRefs(useFriendStore());
|
||||
const { t } = useI18n();
|
||||
|
||||
const isFriendTraveling = computed(() => props.friend.ref?.location === 'traveling');
|
||||
const isFriendActiveOrOffline = computed(() => props.friend.state === 'active' || props.friend.state === 'offline');
|
||||
const epoch = computed(() =>
|
||||
isFriendTraveling.value ? props.friend.ref?.$travelingToTime : props.friend.ref?.$location_at
|
||||
);
|
||||
|
||||
const locationProp = computed(() => props.friend.ref?.location || '');
|
||||
const travelingProp = computed(() => props.friend.ref?.travelingToLocation || '');
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.skeleton {
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
::v-deep .el-skeleton {
|
||||
:deep(.el-skeleton) {
|
||||
height: 100%;
|
||||
> div {
|
||||
height: 100%;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<template v-if="state.canCloseInstance">
|
||||
<el-button
|
||||
:disabled="state.isClosed"
|
||||
size="mini"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="closeInstance(props.location)">
|
||||
{{ t('dialog.user.info.close_instance') }} </el-button
|
||||
@@ -24,17 +24,14 @@
|
||||
>{{ t('dialog.user.info.instance_disabled_content') }} {{ state.disabledContentSettings }}<br
|
||||
/></span>
|
||||
<span v-if="state.userList.length">{{ t('dialog.user.info.instance_users') }}<br /></span>
|
||||
<template v-for="user in state.userList">
|
||||
<span
|
||||
style="cursor: pointer; margin-right: 5px"
|
||||
@click="showUserDialog(user.id)"
|
||||
:key="user.id"
|
||||
>{{ user.displayName }}</span
|
||||
>
|
||||
<template v-for="user in state.userList" :key="user.id">
|
||||
<span style="cursor: pointer; margin-right: 5px" @click="showUserDialog(user.id)">{{
|
||||
user.displayName
|
||||
}}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<i class="el-icon-caret-bottom"></i>
|
||||
<el-icon><CaretBottom /></el-icon>
|
||||
</el-tooltip>
|
||||
<span v-if="state.occupants" style="margin-left: 5px">{{ state.occupants }}/{{ state.capacity }}</span>
|
||||
<span v-if="props.friendcount" style="margin-left: 5px">({{ props.friendcount }})</span>
|
||||
@@ -57,8 +54,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance, reactive, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { CaretBottom } from '@element-plus/icons-vue';
|
||||
import { reactive, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { miscRequest } from '../api';
|
||||
import { formatDateFilter, hasGroupPermission } from '../shared/utils';
|
||||
import { useGroupStore, useInstanceStore, useLocationStore, useUserStore } from '../stores';
|
||||
@@ -94,8 +93,6 @@
|
||||
disabledContentSettings: ''
|
||||
});
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
function parse() {
|
||||
Object.assign(state, {
|
||||
isValidInstance: false,
|
||||
@@ -153,18 +150,19 @@
|
||||
}
|
||||
|
||||
function closeInstance(location) {
|
||||
proxy.$confirm('Continue? Close Instance, nobody will be able to join', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Close Instance, nobody will be able to join', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning',
|
||||
callback: async (action) => {
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async (action) => {
|
||||
if (action !== 'confirm') return;
|
||||
const args = await miscRequest.closeInstance({ location, hardClose: false });
|
||||
if (args.json) {
|
||||
proxy.$message({ message: t('message.instance.closed'), type: 'success' });
|
||||
ElMessage({ message: t('message.instance.closed'), type: 'success' });
|
||||
instanceStore.applyInstance(args.json);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<el-tooltip
|
||||
v-if="!canOpenInstanceInGame()"
|
||||
placement="top"
|
||||
:content="t('dialog.user.info.self_invite_tooltip')"
|
||||
:disabled="hideTooltips">
|
||||
<el-button v-show="isVisible" @click="confirmInvite" size="mini" icon="el-icon-message" circle />
|
||||
</el-tooltip>
|
||||
<el-tooltip v-else placement="top" :content="t('dialog.user.info.open_in_vrchat_tooltip')" :disabled="hideTooltips">
|
||||
<el-button @click="openInstance" size="mini" icon="el-icon-message" circle />
|
||||
</el-tooltip>
|
||||
<div v-if="isVisible" :class="['inline-block']">
|
||||
<el-tooltip
|
||||
v-if="!canOpenInstanceInGame()"
|
||||
placement="top"
|
||||
:content="t('dialog.user.info.self_invite_tooltip')">
|
||||
<el-button v-show="isVisible" @click="confirmInvite" size="small" :icon="Message" circle />
|
||||
</el-tooltip>
|
||||
<el-tooltip v-else placement="top" :content="t('dialog.user.info.open_in_vrchat_tooltip')">
|
||||
<el-button @click="openInstance" size="small" :icon="Message" circle />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Message } from '@element-plus/icons-vue';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { instanceRequest } from '../api';
|
||||
import { checkCanInviteSelf, parseLocation } from '../shared/utils';
|
||||
import { useAppearanceSettingsStore } from '../stores/settings/appearance';
|
||||
import { useLaunchStore } from '../stores/launch';
|
||||
import { useInviteStore } from '../stores/invite';
|
||||
import { useInviteStore, useLaunchStore } from '../stores';
|
||||
|
||||
const props = defineProps({
|
||||
location: String,
|
||||
@@ -28,13 +28,9 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { hideTooltips } = storeToRefs(useAppearanceSettingsStore());
|
||||
|
||||
const { canOpenInstanceInGame } = useInviteStore();
|
||||
const { tryOpenInstanceInVrc } = useLaunchStore();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const isVisible = computed(() => checkCanInviteSelf(props.location));
|
||||
|
||||
function confirmInvite() {
|
||||
@@ -50,7 +46,7 @@
|
||||
shortName: props.shortname
|
||||
})
|
||||
.then((args) => {
|
||||
proxy.$message({ message: 'Self invite sent', type: 'success' });
|
||||
ElMessage({ message: 'Self invite sent', type: 'success' });
|
||||
return args;
|
||||
});
|
||||
}
|
||||
@@ -64,3 +60,9 @@
|
||||
tryOpenInstanceInVrc(L.tag, props.shortname);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<template>
|
||||
<span v-if="lastJoin">
|
||||
<el-tooltip placement="top" style="margin-left: 5px">
|
||||
<span v-if="lastJoin" :class="['inline-block', 'ml-5']">
|
||||
<el-tooltip placement="top" class="ml-5">
|
||||
<template #content>
|
||||
<span>{{ $t('dialog.user.info.last_join') }} <Timer :epoch="lastJoin" /></span>
|
||||
<span>{{ t('dialog.user.info.last_join') }} <Timer :epoch="lastJoin" /></span>
|
||||
</template>
|
||||
<i class="el-icon el-icon-location-outline" style="display: inline-block" />
|
||||
<el-icon style="display: inline-block"><Location /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Location } from '@element-plus/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useInstanceStore } from '../stores';
|
||||
|
||||
const { instanceJoinHistory } = storeToRefs(useInstanceStore());
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
location: String,
|
||||
@@ -30,3 +33,12 @@
|
||||
watch(() => props.location, parse, { immediate: true });
|
||||
watch(() => props.currentlocation, parse);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ml-5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
+14
-10
@@ -1,16 +1,15 @@
|
||||
<template>
|
||||
<el-tooltip
|
||||
v-show="isVisible"
|
||||
placement="top"
|
||||
:content="t('dialog.user.info.launch_invite_tooltip')"
|
||||
:disabled="hideTooltips">
|
||||
<el-button @click="confirm" size="mini" icon="el-icon-switch-button" circle />
|
||||
</el-tooltip>
|
||||
<div v-if="isVisible" class="inline-block">
|
||||
<el-tooltip placement="top" :content="t('dialog.user.info.launch_invite_tooltip')"
|
||||
><el-button @click="confirm" size="small" :icon="SwitchButton" circle />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { SwitchButton } from '@element-plus/icons-vue';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { checkCanInviteSelf } from '../shared/utils';
|
||||
import { useLaunchStore } from '../stores';
|
||||
|
||||
@@ -18,8 +17,7 @@
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
location: String,
|
||||
hideTooltips: Boolean
|
||||
location: String
|
||||
});
|
||||
|
||||
const isVisible = computed(() => {
|
||||
@@ -30,3 +28,9 @@
|
||||
launchStore.showLaunchDialog(props.location);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
+27
-17
@@ -1,28 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<span v-if="!text" style="color: transparent">-</span>
|
||||
<span v-if="!text" class="transparent">-</span>
|
||||
<span v-show="text">
|
||||
<span
|
||||
:class="{ 'x-link': link && location !== 'private' && location !== 'offline' }"
|
||||
@click="handleShowWorldDialog">
|
||||
<i v-if="isTraveling" class="el-icon el-icon-loading" style="display: inline-block"></i>
|
||||
<el-icon :class="['is-loading', 'inline-block']" style="margin-right: 3px" v-if="isTraveling"
|
||||
><Loading
|
||||
/></el-icon>
|
||||
<span>{{ text }}</span>
|
||||
</span>
|
||||
<span v-if="groupName" :class="{ 'x-link': link }" @click="handleShowGroupDialog">({{ groupName }})</span>
|
||||
<span v-if="region" class="flags" :class="region" style="display: inline-block; margin-left: 5px"></span>
|
||||
<i v-if="strict" class="el-icon el-icon-lock" style="display: inline-block; margin-left: 5px"></i>
|
||||
<span v-if="region" :class="['flags', 'inline-block', 'ml-5', region]"></span>
|
||||
<el-icon v-if="strict" :class="['inline-block', 'ml-5']"><Lock /></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watch } from 'vue';
|
||||
import { Loading, Lock } from '@element-plus/icons-vue';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { getGroupName, getWorldName, parseLocation } from '../shared/utils';
|
||||
import { useGroupStore, useInstanceStore, useSearchStore, useWorldStore } from '../stores';
|
||||
|
||||
const { cachedWorlds } = storeToRefs(useWorldStore());
|
||||
const { showWorldDialog } = useWorldStore();
|
||||
const { cachedWorlds, showWorldDialog } = useWorldStore();
|
||||
const { showGroupDialog } = useGroupStore();
|
||||
const { showPreviousInstancesInfoDialog } = useInstanceStore();
|
||||
const { verifyShortName } = useSearchStore();
|
||||
@@ -51,14 +52,9 @@
|
||||
const isTraveling = ref(false);
|
||||
const groupName = ref('');
|
||||
|
||||
watch(
|
||||
() => props.location,
|
||||
() => {
|
||||
parse();
|
||||
}
|
||||
);
|
||||
|
||||
parse();
|
||||
watchEffect(() => {
|
||||
parse();
|
||||
});
|
||||
|
||||
function parse() {
|
||||
isTraveling.value = false;
|
||||
@@ -82,7 +78,7 @@
|
||||
text.value = props.hint;
|
||||
}
|
||||
} else if (L.worldId) {
|
||||
const ref = cachedWorlds.value.get(L.worldId);
|
||||
const ref = cachedWorlds.get(L.worldId);
|
||||
if (typeof ref === 'undefined') {
|
||||
getWorldName(L.worldId).then((worldName) => {
|
||||
if (L.tag === instanceId) {
|
||||
@@ -152,3 +148,17 @@
|
||||
showGroupDialog(L.groupId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ml-5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<template>
|
||||
<span>
|
||||
<span @click="showLaunchDialog" class="x-link">
|
||||
<i v-if="isUnlocked" class="el-icon el-icon-unlock" style="display: inline-block; margin-right: 5px"></i>
|
||||
<el-icon v-if="isUnlocked" style="display: inline-block; margin-right: 5px"><Unlock /></el-icon>
|
||||
<span>#{{ instanceName }} {{ accessTypeName }}</span>
|
||||
</span>
|
||||
<span v-if="groupName" @click="showGroupDialog" class="x-link">({{ groupName }})</span>
|
||||
<span class="flags" :class="region" style="display: inline-block; margin-left: 5px"></span>
|
||||
<i v-if="strict" class="el-icon el-icon-lock" style="display: inline-block; margin-left: 5px"></i>
|
||||
<el-icon v-if="strict" style="display: inline-block; margin-left: 5px"><Lock /></el-icon>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Lock, Unlock } from '@element-plus/icons-vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import { getGroupName, parseLocation } from '../shared/utils';
|
||||
import { useGroupStore, useLaunchStore } from '../stores';
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<div v-else-if="pendingVRCXUpdate || pendingVRCXInstall" class="pending-update">
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-download"
|
||||
size="small"
|
||||
:icon="Download"
|
||||
circle
|
||||
style="font-size: 14px; height: 50px; width: 50px"
|
||||
@click="showVRCXUpdateDialog" />
|
||||
@@ -30,7 +30,7 @@
|
||||
:class="{ notify: notifiedMenus.includes(item.index) }">
|
||||
<i :class="item.icon"></i>
|
||||
<template #title>
|
||||
<span>{{ $t(item.tooltip) }}</span>
|
||||
<span>{{ t(item.tooltip) }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
@@ -38,9 +38,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Download } from '@element-plus/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useUiStore, useVRCXUpdaterStore } from '../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const navItems = [
|
||||
{ index: 'feed', icon: 'ri-rss-line', tooltip: 'nav_tooltip.feed' },
|
||||
{ index: 'gameLog', icon: 'ri-history-line', tooltip: 'nav_tooltip.game_log' },
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
<div class="name" :style="{ width: longLabel ? '300px' : undefined }">
|
||||
{{ label }}
|
||||
<el-tooltip v-if="tooltip" placement="top" class="tooltip" :content="tooltip"
|
||||
><i class="el-icon-info"
|
||||
/></el-tooltip>
|
||||
><el-icon><InfoFilled /></el-icon
|
||||
></el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-switch class="switch" :value="value" @change="change" :disabled="disabled"></el-switch>
|
||||
<el-switch class="switch" :model-value="value" @change="change" :disabled="disabled"></el-switch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
defineProps({
|
||||
label: String,
|
||||
value: Boolean,
|
||||
@@ -30,13 +31,13 @@
|
||||
<style scoped>
|
||||
.simple-switch {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
}
|
||||
.simple-switch > .name {
|
||||
width: 225px;
|
||||
min-width: 225px;
|
||||
word-wrap: break-word;
|
||||
padding-top: 7px;
|
||||
}
|
||||
.simple-switch > .switch {
|
||||
margin-left: 10px;
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<span>
|
||||
<span>{{ text }}</span>
|
||||
<span v-if="groupName">({{ groupName }})</span>
|
||||
<span
|
||||
v-if="region"
|
||||
class="flags"
|
||||
:class="region"
|
||||
style="display:inline-block;margin-bottom:2px;margin-left:5px">
|
||||
</span>
|
||||
<i
|
||||
v-if="strict"
|
||||
class="el-icon el-icon-lock"
|
||||
style="display:inline-block;margin-left:5px">
|
||||
</i>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { parseLocation } from '../shared/utils/location';
|
||||
|
||||
const props = defineProps({
|
||||
location: String,
|
||||
hint: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
grouphint: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const text = ref('');
|
||||
const region = ref('');
|
||||
const strict = ref(false);
|
||||
const groupName = ref('');
|
||||
|
||||
function parse() {
|
||||
text.value = props.location;
|
||||
const L = parseLocation(props.location);
|
||||
|
||||
if (L.isOffline) {
|
||||
text.value = 'Offline';
|
||||
} else if (L.isPrivate) {
|
||||
text.value = 'Private';
|
||||
} else if (L.isTraveling) {
|
||||
text.value = 'Traveling';
|
||||
} else if (typeof props.hint === 'string' && props.hint !== '') {
|
||||
if (L.instanceId) {
|
||||
text.value = `${props.hint} #${L.instanceName} ${L.accessTypeName}`;
|
||||
} else {
|
||||
text.value = props.hint;
|
||||
}
|
||||
} else if (L.worldId) {
|
||||
if (L.instanceId) {
|
||||
text.value = ` #${L.instanceName} ${L.accessTypeName}`;
|
||||
} else {
|
||||
text.value = props.location;
|
||||
}
|
||||
}
|
||||
|
||||
region.value = '';
|
||||
if (
|
||||
props.location !== '' &&
|
||||
L.instanceId &&
|
||||
!L.isOffline &&
|
||||
!L.isPrivate
|
||||
) {
|
||||
region.value = L.region;
|
||||
if (!L.region) {
|
||||
region.value = 'us';
|
||||
}
|
||||
}
|
||||
|
||||
strict.value = L.strict;
|
||||
groupName.value = props.grouphint;
|
||||
}
|
||||
|
||||
watch(() => props.location, parse);
|
||||
|
||||
onMounted(parse);
|
||||
</script>
|
||||
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>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<safe-dialog ref="favoriteDialogRef" :visible.sync="isVisible" :title="t('dialog.favorite.header')" width="300px">
|
||||
<el-dialog :z-index="favoriteDialogIndex" v-model="isVisible" :title="t('dialog.favorite.header')" width="300px">
|
||||
<div v-loading="loading">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.vrchat_favorites') }}</span>
|
||||
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
|
||||
<el-button
|
||||
style="display: block; width: 100%; margin: 10px 0"
|
||||
@click="deleteFavoriteNoConfirm(favoriteDialog.objectId)">
|
||||
<i class="el-icon-check"></i>
|
||||
<el-icon><Check /></el-icon>
|
||||
{{ favoriteDialog.currentGroup.displayName }} ({{ favoriteDialog.currentGroup.count }} /
|
||||
{{ favoriteDialog.currentGroup.capacity }})
|
||||
</el-button>
|
||||
@@ -23,18 +23,16 @@
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'world'" style="margin-top: 20px">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_favorites') }}</span>
|
||||
<template v-for="group in localWorldFavoriteGroups">
|
||||
<template v-for="group in localWorldFavoriteGroups" :key="group">
|
||||
<el-button
|
||||
v-if="hasLocalWorldFavorite(favoriteDialog.objectId, group)"
|
||||
:key="group"
|
||||
style="display: block; width: 100%; margin: 10px 0"
|
||||
@click="removeLocalWorldFavorite(favoriteDialog.objectId, group)">
|
||||
<i class="el-icon-check"></i>
|
||||
<el-icon><Check /></el-icon>
|
||||
{{ group }} ({{ getLocalWorldFavoriteGroupLength(group) }})
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
:key="group"
|
||||
style="display: block; width: 100%; margin: 10px 0"
|
||||
@click="addLocalWorldFavorite(favoriteDialog.objectId, group)">
|
||||
{{ group }} ({{ getLocalWorldFavoriteGroupLength(group) }})
|
||||
@@ -43,18 +41,16 @@
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'avatar'" style="margin-top: 20px">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_avatar_favorites') }}</span>
|
||||
<template v-for="group in localAvatarFavoriteGroups">
|
||||
<template v-for="group in localAvatarFavoriteGroups" :key="group">
|
||||
<el-button
|
||||
v-if="hasLocalAvatarFavorite(favoriteDialog.objectId, group)"
|
||||
:key="group"
|
||||
style="display: block; width: 100%; margin: 10px 0"
|
||||
@click="removeLocalAvatarFavorite(favoriteDialog.objectId, group)">
|
||||
<i class="el-icon-check"></i>
|
||||
<el-icon><Check /></el-icon>
|
||||
{{ group }} ({{ getLocalAvatarFavoriteGroupLength(group) }})
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
:key="group"
|
||||
style="display: block; width: 100%; margin: 10px 0"
|
||||
:disabled="!isLocalUserVrcplusSupporter"
|
||||
@click="addLocalAvatarFavorite(favoriteDialog.objectId, group)">
|
||||
@@ -62,16 +58,18 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Check } from '@element-plus/icons-vue';
|
||||
|
||||
import Noty from 'noty';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { favoriteRequest } from '../../api';
|
||||
import { adjustDialogZ } from '../../shared/utils';
|
||||
import { getNextDialogIndex } from '../../shared/utils';
|
||||
import { useFavoriteStore, useUserStore } from '../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -98,7 +96,7 @@
|
||||
} = favoriteStore;
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
|
||||
const favoriteDialogRef = ref(null);
|
||||
const favoriteDialogIndex = ref(2000);
|
||||
const groups = ref([]);
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -113,11 +111,12 @@
|
||||
|
||||
watch(
|
||||
() => favoriteDialog.value.visible,
|
||||
async (value) => {
|
||||
(value) => {
|
||||
if (value) {
|
||||
initFavoriteDialog();
|
||||
await nextTick();
|
||||
adjustDialogZ(favoriteDialogRef.value.$el);
|
||||
nextTick(() => {
|
||||
favoriteDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,49 +1,90 @@
|
||||
<template>
|
||||
<safe-dialog class="x-dialog" :visible.sync="fullscreenImageDialog.visible" append-to-body width="97vw">
|
||||
<el-dialog class="x-dialog" v-model="fullscreenImageDialog.visible" append-to-body width="97vw">
|
||||
<div>
|
||||
<div style="margin: 0 0 5px 5px">
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-s-order"
|
||||
circle
|
||||
@click="copyImageUrl(fullscreenImageDialog.imageUrl)"></el-button>
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-download"
|
||||
circle
|
||||
style="margin-left: 5px"
|
||||
:disabled="!fullscreenImageDialog.imageUrl.startsWith('http')"
|
||||
@click="
|
||||
downloadAndSaveImage(fullscreenImageDialog.imageUrl, fullscreenImageDialog.fileName)
|
||||
"></el-button>
|
||||
<el-tooltip :content="t('dialog.fullscreen_image.copy_image_to_clipboard')" placement="top">
|
||||
<el-button
|
||||
size="small"
|
||||
:icon="CopyDocument"
|
||||
circle
|
||||
@click="copyImageToClipboard(fullscreenImageDialog.imageUrl)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="t('dialog.fullscreen_image.download_and_save_image')" placement="top">
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
:icon="Download"
|
||||
circle
|
||||
style="margin-left: 5px"
|
||||
:disabled="!fullscreenImageDialog.imageUrl.startsWith('http')"
|
||||
@click="
|
||||
downloadAndSaveImage(fullscreenImageDialog.imageUrl, fullscreenImageDialog.fileName)
|
||||
"></el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<img v-lazy="fullscreenImageDialog.imageUrl" style="width: 100%; height: 85vh; object-fit: contain" />
|
||||
<img
|
||||
:src="fullscreenImageDialog.imageUrl"
|
||||
style="width: 100%; height: 85vh; object-fit: contain"
|
||||
loading="lazy" />
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Download, CopyDocument } from '@element-plus/icons-vue';
|
||||
|
||||
import Noty from 'noty';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { copyToClipboard, escapeTag, extractFileId } from '../../shared/utils';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { escapeTag, extractFileId } from '../../shared/utils';
|
||||
import { useGalleryStore } from '../../stores';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { $message } = proxy;
|
||||
const { t } = useI18n();
|
||||
|
||||
const { fullscreenImageDialog } = storeToRefs(useGalleryStore());
|
||||
|
||||
function copyImageUrl(imageUrl) {
|
||||
copyToClipboard(imageUrl, 'ImageUrl copied to clipboard');
|
||||
async function copyImageToClipboard(url) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
const msg = ElMessage({
|
||||
message: 'Downloading image...',
|
||||
type: 'info'
|
||||
});
|
||||
try {
|
||||
const response = await webApiService.execute({
|
||||
url,
|
||||
method: 'GET'
|
||||
});
|
||||
if (response.status !== 200 || !response.data.startsWith('data:image/png')) {
|
||||
throw new Error(`Error: ${response.data}`);
|
||||
}
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
'image/png': await (await fetch(response.data)).blob()
|
||||
})
|
||||
]);
|
||||
ElMessage({
|
||||
message: 'Image copied to clipboard',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error downloading image:', error);
|
||||
new Noty({
|
||||
type: 'error',
|
||||
text: escapeTag(`Failed to download image. ${url}`)
|
||||
}).show();
|
||||
} finally {
|
||||
msg.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAndSaveImage(url, fileName) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
$message({
|
||||
const msg = ElMessage({
|
||||
message: 'Downloading image...',
|
||||
type: 'info'
|
||||
});
|
||||
@@ -77,6 +118,8 @@
|
||||
type: 'error',
|
||||
text: escapeTag(`Failed to download image. ${url}`)
|
||||
}).show();
|
||||
} finally {
|
||||
msg.close();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="gallerySelectDialog.visible"
|
||||
v-model="gallerySelectDialog.visible"
|
||||
:title="t('dialog.gallery_select.header')"
|
||||
width="100%"
|
||||
append-to-body>
|
||||
@@ -16,16 +16,16 @@
|
||||
style="display: none"
|
||||
@change="onFileChangeGallery" />
|
||||
<el-button-group>
|
||||
<el-button type="default" size="small" icon="el-icon-close" @click="selectImageGallerySelect('', '')">{{
|
||||
<el-button type="default" size="small" :icon="Close" @click="selectImageGallerySelect('', '')">{{
|
||||
t('dialog.gallery_select.none')
|
||||
}}</el-button>
|
||||
<el-button type="default" size="small" icon="el-icon-refresh" @click="refreshGalleryTable">{{
|
||||
<el-button type="default" size="small" :icon="Refresh" @click="refreshGalleryTable">{{
|
||||
t('dialog.gallery_select.refresh')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
icon="el-icon-upload2"
|
||||
:icon="Upload"
|
||||
:disabled="!currentUser.$isVRCPlus"
|
||||
@click="displayGalleryUpload"
|
||||
>{{ t('dialog.gallery_select.upload') }}</el-button
|
||||
@@ -42,24 +42,28 @@
|
||||
v-if="image.versions[image.versions.length - 1].file.url"
|
||||
class="vrcplus-icon"
|
||||
@click="selectImageGallerySelect(image.versions[image.versions.length - 1].file.url, image.id)">
|
||||
<img v-lazy="image.versions[image.versions.length - 1].file.url" class="avatar" /></div
|
||||
<img
|
||||
:src="image.versions[image.versions.length - 1].file.url"
|
||||
class="avatar"
|
||||
loading="lazy" /></div
|
||||
></template>
|
||||
</div>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { Close, Refresh, Upload } from '@element-plus/icons-vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { vrcPlusImageRequest } from '../../../api';
|
||||
import { useGalleryStore, useUserStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { $message } = proxy;
|
||||
const { galleryTable } = storeToRefs(useGalleryStore());
|
||||
const { refreshGalleryTable, handleGalleryImageAdd } = useGalleryStore();
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
@@ -95,7 +99,7 @@
|
||||
}
|
||||
if (files[0].size >= 100000000) {
|
||||
// 100MB
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.too_large'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -103,7 +107,7 @@
|
||||
return;
|
||||
}
|
||||
if (!files[0].type.match(/image.*/)) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.not_image'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -115,7 +119,7 @@
|
||||
const base64Body = btoa(r.result.toString());
|
||||
vrcPlusImageRequest.uploadGalleryImage(base64Body).then((args) => {
|
||||
handleGalleryImageAdd(args);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.gallery.uploaded'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="groupMemberModeration.visible"
|
||||
v-model="groupMemberModeration.visible"
|
||||
:title="t('dialog.group_member_moderation.header')"
|
||||
append-to-body
|
||||
width="90vw">
|
||||
@@ -12,8 +12,8 @@
|
||||
<div style="margin-top: 10px">
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
size="small"
|
||||
:icon="Refresh"
|
||||
:loading="isGroupMembersLoading"
|
||||
circle
|
||||
@click="loadAllGroupMembers" />
|
||||
@@ -34,21 +34,23 @@
|
||||
memberSearch.length ||
|
||||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')
|
||||
)
|
||||
"
|
||||
@click.native.stop>
|
||||
<el-button size="mini">
|
||||
">
|
||||
<el-button size="small" @click.stop>
|
||||
<span
|
||||
>{{ t(memberSortOrder.name) }} <i class="el-icon-arrow-down el-icon--right"></i
|
||||
></span>
|
||||
>{{ t(memberSortOrder.name) }}
|
||||
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
|
||||
</span>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="item in groupDialogSortingOptions"
|
||||
:key="item.name"
|
||||
@click.native="setGroupMemberSortOrder(item)">
|
||||
{{ t(item.name) }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in groupDialogSortingOptions"
|
||||
:key="item.name"
|
||||
@click="setGroupMemberSortOrder(item)">
|
||||
{{ t(item.name) }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<span style="margin-right: 5px">{{ t('dialog.group.members.filter') }}</span>
|
||||
<el-dropdown
|
||||
@@ -61,33 +63,36 @@
|
||||
memberSearch.length ||
|
||||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')
|
||||
)
|
||||
"
|
||||
@click.native.stop>
|
||||
<el-button size="mini">
|
||||
">
|
||||
<el-button size="small" @click.stop>
|
||||
<span
|
||||
>{{ t(memberFilter.name) }} <i class="el-icon-arrow-down el-icon--right"></i
|
||||
>{{ t(memberFilter.name) }}
|
||||
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon
|
||||
></span>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="item in groupDialogFilterOptions"
|
||||
:key="item.name"
|
||||
@click.native="setGroupMemberFilter(item)"
|
||||
v-text="t(item.name)"></el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-for="item in groupMemberModeration.groupRef.roles"
|
||||
:key="item.name"
|
||||
@click.native="setGroupMemberFilter(item)"
|
||||
><span v-if="!item.defaultRole">{{ t(item.name) }}</span></el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in groupDialogFilterOptions"
|
||||
:key="item.name"
|
||||
@click="setGroupMemberFilter(item)"
|
||||
v-text="t(item.name)"></el-dropdown-item>
|
||||
<template v-for="role in groupMemberModeration.groupRef.roles" :key="role.name">
|
||||
<el-dropdown-item
|
||||
v-if="!role.defaultRole"
|
||||
@click="setGroupMemberFilter(role)">
|
||||
{{ t(role.name) }}
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="memberSearch"
|
||||
:disabled="!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')"
|
||||
clearable
|
||||
size="mini"
|
||||
size="small"
|
||||
:placeholder="t('dialog.group.members.search')"
|
||||
style="margin-top: 10px; margin-bottom: 10px"
|
||||
@input="groupMembersSearch"></el-input>
|
||||
@@ -95,13 +100,13 @@
|
||||
<el-button size="small" @click="selectAllGroupMembers">{{
|
||||
t('dialog.group_member_moderation.select_all')
|
||||
}}</el-button>
|
||||
<data-tables
|
||||
<DataTable
|
||||
v-if="groupMemberModerationTable.data.length"
|
||||
v-bind="groupMemberModerationTable"
|
||||
style="margin-top: 10px">
|
||||
<el-table-column width="55" prop="$selected">
|
||||
<template #default="scope">
|
||||
<el-button type="text" size="mini" @click.stop>
|
||||
<el-button type="text" size="small" @click.stop>
|
||||
<el-checkbox
|
||||
v-model="scope.row.$selected"
|
||||
@change="
|
||||
@@ -115,16 +120,19 @@
|
||||
width="70"
|
||||
prop="photo">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" height="500px" trigger="hover">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="userImage(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="userImage(scope.row.user)"
|
||||
class="friends-list-avatar" />
|
||||
<img
|
||||
v-lazy="userImageFull(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
style="height: 500px; cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
||||
:src="userImageFull(scope.row.user)"
|
||||
:class="['friends-list-avatar', 'x-popover-image']"
|
||||
style="cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -145,9 +153,11 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('dialog.group_member_moderation.roles')" prop="roleIds" sortable>
|
||||
<template #default="scope">
|
||||
<template v-for="(roleId, index) in scope.row.roleIds">
|
||||
<template v-for="(role, rIndex) in groupMemberModeration.groupRef.roles">
|
||||
<span v-if="role?.id === roleId" :key="roleId + rIndex"
|
||||
<template v-for="(roleId, index) in scope.row.roleIds" :key="roleId">
|
||||
<template
|
||||
v-for="(role, rIndex) in groupMemberModeration.groupRef.roles"
|
||||
:key="roleId + rIndex">
|
||||
<span v-if="role?.id === roleId"
|
||||
>{{ role.name
|
||||
}}<span v-if="index < scope.row.roleIds.length - 1">, </span></span
|
||||
></template
|
||||
@@ -181,7 +191,7 @@
|
||||
<span v-text="scope.row.visibility"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -191,8 +201,8 @@
|
||||
<div style="margin-top: 10px">
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
size="small"
|
||||
:icon="Refresh"
|
||||
:loading="isGroupMembersLoading"
|
||||
circle
|
||||
@click="getAllGroupBans(groupMemberModeration.id)"></el-button>
|
||||
@@ -203,17 +213,17 @@
|
||||
<el-input
|
||||
v-model="groupBansModerationTable.filters[0].value"
|
||||
clearable
|
||||
size="mini"
|
||||
size="small"
|
||||
:placeholder="t('dialog.group.members.search')"
|
||||
style="margin-top: 10px; margin-bottom: 10px"></el-input>
|
||||
<br />
|
||||
<el-button size="small" @click="selectAllGroupBans">{{
|
||||
t('dialog.group_member_moderation.select_all')
|
||||
}}</el-button>
|
||||
<data-tables v-bind="groupBansModerationTable" style="margin-top: 10px">
|
||||
<DataTable v-bind="groupBansModerationTable" style="margin-top: 10px">
|
||||
<el-table-column width="55" prop="$selected">
|
||||
<template #default="scope">
|
||||
<el-button type="text" size="mini" @click.stop>
|
||||
<el-button type="text" size="small" @click.stop>
|
||||
<el-checkbox
|
||||
v-model="scope.row.$selected"
|
||||
@change="
|
||||
@@ -227,16 +237,19 @@
|
||||
width="70"
|
||||
prop="photo">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" height="500px" trigger="hover">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="userImage(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="userImage(scope.row.user)"
|
||||
class="friends-list-avatar" />
|
||||
<img
|
||||
v-lazy="userImageFull(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
style="height: 500px; cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
||||
:src="userImageFull(scope.row.user)"
|
||||
:class="['friends-list-avatar', 'x-popover-image']"
|
||||
style="cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -257,16 +270,14 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('dialog.group_member_moderation.roles')" prop="roleIds" sortable>
|
||||
<template #default="scope">
|
||||
<template v-for="(roleId, index) in scope.row.roleIds">
|
||||
<template v-for="(roleId, index) in scope.row.roleIds" :key="roleId">
|
||||
<span
|
||||
v-for="(role, rIndex) in groupMemberModeration.groupRef.roles"
|
||||
v-if="role.id === roleId"
|
||||
:key="rIndex + roleId"
|
||||
>{{ role.name }}</span
|
||||
>
|
||||
<span v-if="index < scope.row.roleIds.length - 1" :key="index + roleId"
|
||||
>,
|
||||
</span>
|
||||
<span v-if="index < scope.row.roleIds.length - 1">, </span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -296,7 +307,7 @@
|
||||
<span>{{ formatDateFilter(scope.row.bannedAt, 'long') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -306,29 +317,29 @@
|
||||
<div style="margin-top: 10px">
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
size="small"
|
||||
:icon="Refresh"
|
||||
:loading="isGroupMembersLoading"
|
||||
circle
|
||||
@click="getAllGroupInvitesAndJoinRequests(groupMemberModeration.id)"></el-button>
|
||||
<br />
|
||||
<el-tabs>
|
||||
<el-tab-pane>
|
||||
<span slot="label">
|
||||
<template #label>
|
||||
<span style="font-weight: bold; font-size: 16px">{{
|
||||
t('dialog.group_member_moderation.sent_invites')
|
||||
}}</span>
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
||||
groupInvitesModerationTable.data.length
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
<el-button size="small" @click="selectAllGroupInvites">{{
|
||||
t('dialog.group_member_moderation.select_all')
|
||||
}}</el-button>
|
||||
<data-tables v-bind="groupInvitesModerationTable" style="margin-top: 10px">
|
||||
<DataTable v-bind="groupInvitesModerationTable" style="margin-top: 10px">
|
||||
<el-table-column width="55" prop="$selected">
|
||||
<template #default="scope">
|
||||
<el-button type="text" size="mini" @click.stop>
|
||||
<el-button type="text" size="small" @click.stop>
|
||||
<el-checkbox
|
||||
v-model="scope.row.$selected"
|
||||
@change="
|
||||
@@ -342,16 +353,19 @@
|
||||
width="70"
|
||||
prop="photo">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" height="500px" trigger="hover">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="userImage(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="userImage(scope.row.user)"
|
||||
class="friends-list-avatar" />
|
||||
<img
|
||||
v-lazy="userImageFull(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
style="height: 500px; cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
||||
:src="userImageFull(scope.row.user)"
|
||||
:class="['friends-list-avatar', 'x-popover-image']"
|
||||
style="cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -378,7 +392,7 @@
|
||||
<span @click.stop v-text="scope.row.managerNotes"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
<br />
|
||||
<el-button
|
||||
:disabled="
|
||||
@@ -396,21 +410,21 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane>
|
||||
<span slot="label">
|
||||
<template #label>
|
||||
<span style="font-weight: bold; font-size: 16px">{{
|
||||
t('dialog.group_member_moderation.join_requests')
|
||||
}}</span>
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
||||
groupJoinRequestsModerationTable.data.length
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
<el-button size="small" @click="selectAllGroupJoinRequests">{{
|
||||
t('dialog.group_member_moderation.select_all')
|
||||
}}</el-button>
|
||||
<data-tables v-bind="groupJoinRequestsModerationTable" style="margin-top: 10px">
|
||||
<DataTable v-bind="groupJoinRequestsModerationTable" style="margin-top: 10px">
|
||||
<el-table-column width="55" prop="$selected">
|
||||
<template #default="scope">
|
||||
<el-button type="text" size="mini" @click.stop>
|
||||
<el-button type="text" size="small" @click.stop>
|
||||
<el-checkbox
|
||||
v-model="scope.row.$selected"
|
||||
@change="
|
||||
@@ -424,16 +438,19 @@
|
||||
width="70"
|
||||
prop="photo">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" height="500px" trigger="hover">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="userImage(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="userImage(scope.row.user)"
|
||||
class="friends-list-avatar" />
|
||||
<img
|
||||
v-lazy="userImageFull(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
style="height: 500px; cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
||||
:src="userImageFull(scope.row.user)"
|
||||
:class="['friends-list-avatar', 'x-popover-image']"
|
||||
style="cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -460,7 +477,7 @@
|
||||
<span @click.stop v-text="scope.row.managerNotes"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
<br />
|
||||
<el-button
|
||||
:disabled="
|
||||
@@ -504,21 +521,21 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane>
|
||||
<span slot="label">
|
||||
<template #label>
|
||||
<span style="font-weight: bold; font-size: 16px">{{
|
||||
t('dialog.group_member_moderation.blocked_requests')
|
||||
}}</span>
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
||||
groupBlockedModerationTable.data.length
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
<el-button size="small" @click="selectAllGroupBlocked">{{
|
||||
t('dialog.group_member_moderation.select_all')
|
||||
}}</el-button>
|
||||
<data-tables v-bind="groupBlockedModerationTable" style="margin-top: 10px">
|
||||
<DataTable v-bind="groupBlockedModerationTable" style="margin-top: 10px">
|
||||
<el-table-column width="55" prop="$selected">
|
||||
<template #default="scope">
|
||||
<el-button type="text" size="mini" @click.stop>
|
||||
<el-button type="text" size="small" @click.stop>
|
||||
<el-checkbox
|
||||
v-model="scope.row.$selected"
|
||||
@change="
|
||||
@@ -532,16 +549,19 @@
|
||||
width="70"
|
||||
prop="photo">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" height="500px" trigger="hover">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="userImage(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="userImage(scope.row.user)"
|
||||
class="friends-list-avatar" />
|
||||
<img
|
||||
v-lazy="userImageFull(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
style="height: 500px; cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
||||
:src="userImageFull(scope.row.user)"
|
||||
:class="['friends-list-avatar', 'x-popover-image']"
|
||||
style="cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -568,7 +588,7 @@
|
||||
<span @click.stop v-text="scope.row.managerNotes"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
<br />
|
||||
<el-button
|
||||
:disabled="
|
||||
@@ -594,8 +614,8 @@
|
||||
<div style="margin-top: 10px">
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
size="small"
|
||||
:icon="Refresh"
|
||||
:loading="isGroupMembersLoading"
|
||||
circle
|
||||
@click="getAllGroupLogs(groupMemberModeration.id)"></el-button>
|
||||
@@ -637,7 +657,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<data-tables v-bind="groupLogsModerationTable" style="margin-top: 10px">
|
||||
<DataTable v-bind="groupLogsModerationTable" style="margin-top: 10px">
|
||||
<el-table-column
|
||||
:label="t('dialog.group_member_moderation.created_at')"
|
||||
width="170"
|
||||
@@ -684,7 +704,7 @@
|
||||
v-text="JSON.stringify(scope.row.data)"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
@@ -695,7 +715,7 @@
|
||||
<br />
|
||||
<el-input
|
||||
v-model="selectUserId"
|
||||
size="mini"
|
||||
size="small"
|
||||
style="margin-top: 5px; width: 340px"
|
||||
:placeholder="t('dialog.group_member_moderation.user_id_placeholder')"
|
||||
clearable></el-input>
|
||||
@@ -707,8 +727,8 @@
|
||||
<span class="name">{{ t('dialog.group_member_moderation.selected_users') }}</span>
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
size="small"
|
||||
:icon="Delete"
|
||||
circle
|
||||
style="margin-left: 5px"
|
||||
@click="clearSelectedGroupMembers"></el-button>
|
||||
@@ -725,7 +745,7 @@
|
||||
<template #content>
|
||||
<span>{{ t('dialog.group_member_moderation.user_isnt_in_group') }}</span>
|
||||
</template>
|
||||
<i class="el-icon el-icon-warning" style="display: inline-block" />
|
||||
<el-icon style="margin-left: 3px; display: inline-block"><Warning /></el-icon>
|
||||
</el-tooltip>
|
||||
<span v-text="user.user?.displayName || user.userId" style="font-weight: bold; margin-left: 5px"></span>
|
||||
</el-tag>
|
||||
@@ -739,7 +759,7 @@
|
||||
:rows="2"
|
||||
:autosize="{ minRows: 1, maxRows: 20 }"
|
||||
:placeholder="t('dialog.group_member_moderation.note_placeholder')"
|
||||
size="mini"
|
||||
size="small"
|
||||
resize="none"
|
||||
style="margin-top: 5px"></el-input>
|
||||
<br />
|
||||
@@ -826,7 +846,7 @@
|
||||
>{{ t('dialog.group_member_moderation.unban') }}</el-button
|
||||
>
|
||||
<span v-if="progressCurrent" style="margin-top: 10px">
|
||||
<i class="el-icon-loading" style="margin-left: 5px; margin-right: 5px"></i>
|
||||
<el-icon class="is-loading" style="margin-left: 5px; margin-right: 5px"><Loading /></el-icon>
|
||||
{{ t('dialog.group_member_moderation.progress') }} {{ progressCurrent }}/{{ progressTotal }}
|
||||
</span>
|
||||
<el-button v-if="progressCurrent" style="margin-left: 5px" @click="progressTotal = 0">{{
|
||||
@@ -834,15 +854,18 @@
|
||||
}}</el-button>
|
||||
</div>
|
||||
<group-member-moderation-export-dialog
|
||||
:is-group-logs-export-dialog-visible.sync="isGroupLogsExportDialogVisible"
|
||||
:is-group-logs-export-dialog-visible="isGroupLogsExportDialogVisible"
|
||||
:group-logs-moderation-table="groupLogsModerationTable" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Refresh, Delete, ArrowDown, Warning, Loading } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import { groupRequest, userRequest } from '../../../api';
|
||||
import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants';
|
||||
@@ -857,9 +880,6 @@
|
||||
const { applyGroupMember, handleGroupMember, handleGroupMemberProps } = useGroupStore();
|
||||
const { showFullscreenImageDialog } = useGalleryStore();
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const selectedUsers = reactive({});
|
||||
const selectedUsersArray = ref([]);
|
||||
const isGroupMembersLoading = ref(false);
|
||||
@@ -880,7 +900,7 @@
|
||||
n: 100,
|
||||
offset: 0,
|
||||
groupId: '',
|
||||
sort: '',
|
||||
sort: 'joinedAt:desc',
|
||||
roleId: ''
|
||||
});
|
||||
|
||||
@@ -917,7 +937,7 @@
|
||||
|
||||
const groupInvitesModerationTable = reactive({
|
||||
data: [],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -927,7 +947,7 @@
|
||||
});
|
||||
const groupJoinRequestsModerationTable = reactive({
|
||||
data: [],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -937,7 +957,7 @@
|
||||
});
|
||||
const groupBlockedModerationTable = reactive({
|
||||
data: [],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -948,7 +968,7 @@
|
||||
const groupLogsModerationTable = reactive({
|
||||
data: [],
|
||||
filters: [{ prop: ['description'], value: '' }],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -959,7 +979,7 @@
|
||||
const groupBansModerationTable = reactive({
|
||||
data: [],
|
||||
filters: [{ prop: ['$displayName'], value: '' }],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -969,7 +989,7 @@
|
||||
});
|
||||
const groupMemberModerationTable = reactive({
|
||||
data: [],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -1084,7 +1104,7 @@
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to delete group invites: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1092,7 +1112,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Deleted ${memberCount} group invites`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1136,7 +1156,7 @@
|
||||
}
|
||||
groupBansModerationTable.data = fetchedBans;
|
||||
} catch {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Failed to get group bans',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1167,13 +1187,13 @@
|
||||
await groupRequest.banGroupMember({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to ban group member: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
$message({ message: `Banned ${memberCount} group members`, type: 'success' });
|
||||
ElMessage({ message: `Banned ${memberCount} group members`, type: 'success' });
|
||||
progressCurrent.value = 0;
|
||||
progressTotal.value = 0;
|
||||
getAllGroupBans(D.id);
|
||||
@@ -1196,7 +1216,7 @@
|
||||
await groupRequest.unbanGroupMember({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to unban group member: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1205,7 +1225,7 @@
|
||||
}
|
||||
|
||||
if (allSuccess) {
|
||||
$message({ message: `Unbanned ${memberCount} group members`, type: 'success' });
|
||||
ElMessage({ message: `Unbanned ${memberCount} group members`, type: 'success' });
|
||||
}
|
||||
progressCurrent.value = 0;
|
||||
progressTotal.value = 0;
|
||||
@@ -1230,7 +1250,7 @@
|
||||
await groupRequest.kickGroupMember({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to kick group member: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1238,7 +1258,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({ message: `Kicked ${memberCount} group members`, type: 'success' });
|
||||
ElMessage({ message: `Kicked ${memberCount} group members`, type: 'success' });
|
||||
}
|
||||
progressCurrent.value = 0;
|
||||
progressTotal.value = 0;
|
||||
@@ -1266,7 +1286,7 @@
|
||||
handleGroupMemberProps(args);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to set group member note for ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1274,7 +1294,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({ message: `Saved notes for ${memberCount} group members`, type: 'success' });
|
||||
ElMessage({ message: `Saved notes for ${memberCount} group members`, type: 'success' });
|
||||
}
|
||||
progressCurrent.value = 0;
|
||||
progressTotal.value = 0;
|
||||
@@ -1314,7 +1334,7 @@
|
||||
handleGroupMemberRoleChange(args);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to remove group member roles: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1323,7 +1343,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Roles removed`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1362,7 +1382,7 @@
|
||||
handleGroupMemberRoleChange(args);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to add group member roles: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1371,7 +1391,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Added group member roles`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1466,7 +1486,7 @@
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Failed to get group logs',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1496,7 +1516,7 @@
|
||||
await groupRequest.deleteBlockedGroupRequest({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to delete blocked group requests: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1504,7 +1524,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Deleted ${memberCount} blocked group requests`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1532,7 +1552,7 @@
|
||||
await groupRequest.blockGroupInviteRequest({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to block group join requests: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1540,7 +1560,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Blocked ${memberCount} group join requests`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1569,7 +1589,7 @@
|
||||
await groupRequest.rejectGroupInviteRequest({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to reject group join requests: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1577,7 +1597,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Rejected ${memberCount} group join requests`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1605,7 +1625,7 @@
|
||||
await groupRequest.acceptGroupInviteRequest({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to accept group join requests: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1613,7 +1633,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Accepted ${memberCount} group join requests`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1661,7 +1681,7 @@
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Failed to get group join requests',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1694,7 +1714,7 @@
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Failed to get group join requests',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1731,7 +1751,7 @@
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Failed to get group invites',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1823,7 +1843,7 @@
|
||||
members.value = [];
|
||||
isGroupMembersDone.value = false;
|
||||
loadMoreGroupMembersParams.value = {
|
||||
sort: '',
|
||||
sort: 'joinedAt:desc',
|
||||
roleId: '',
|
||||
n: 100,
|
||||
offset: 0,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="isGroupLogsExportDialogVisible"
|
||||
:model-value="isGroupLogsExportDialogVisible"
|
||||
:title="t('dialog.group_member_moderation.export_logs')"
|
||||
width="650px"
|
||||
append-to-body
|
||||
@@ -10,8 +10,8 @@
|
||||
v-model="checkedGroupLogsExportLogsOptions"
|
||||
style="margin-bottom: 10px"
|
||||
@change="updateGroupLogsExportContent">
|
||||
<template v-for="option in checkGroupsLogsExportLogsOptions">
|
||||
<el-checkbox :key="option.label" :label="option.label">
|
||||
<template v-for="option in checkGroupsLogsExportLogsOptions" :key="option.label">
|
||||
<el-checkbox :label="option.label">
|
||||
{{ t(option.text) }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
@@ -20,18 +20,18 @@
|
||||
<el-input
|
||||
v-model="groupLogsExportContent"
|
||||
type="textarea"
|
||||
size="mini"
|
||||
rows="15"
|
||||
size="small"
|
||||
:rows="15"
|
||||
resize="none"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
@click.native="handleCopyGroupLogsExportContent" />
|
||||
</safe-dialog>
|
||||
@click="handleCopyGroupLogsExportContent" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { copyToClipboard } from '../../../shared/utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
:visible.sync="groupPostEditDialog.visible"
|
||||
<el-dialog
|
||||
v-model="groupPostEditDialog.visible"
|
||||
:title="t('dialog.group_post_edit.header')"
|
||||
width="650px"
|
||||
append-to-body>
|
||||
@@ -8,7 +8,7 @@
|
||||
<h3 v-text="groupPostEditDialog.groupRef.name"></h3>
|
||||
<el-form :model="groupPostEditDialog" label-width="150px">
|
||||
<el-form-item :label="t('dialog.group_post_edit.title')">
|
||||
<el-input v-model="groupPostEditDialog.title" size="mini"></el-input>
|
||||
<el-input v-model="groupPostEditDialog.title" size="small"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.group_post_edit.message')">
|
||||
<el-input
|
||||
@@ -61,29 +61,32 @@
|
||||
<el-form-item :label="t('dialog.group_post_edit.image')">
|
||||
<template v-if="gallerySelectDialog.selectedFileId">
|
||||
<div style="display: inline-block; flex: none; margin-right: 5px">
|
||||
<el-popover placement="right" width="500px" trigger="click">
|
||||
<el-popover placement="right" :width="500" trigger="click">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="gallerySelectDialog.selectedImageUrl"
|
||||
style="
|
||||
flex: none;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="gallerySelectDialog.selectedImageUrl"
|
||||
style="
|
||||
flex: none;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
" />
|
||||
<img
|
||||
v-lazy="gallerySelectDialog.selectedImageUrl"
|
||||
style="height: 500px"
|
||||
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />
|
||||
:src="gallerySelectDialog.selectedImageUrl"
|
||||
:class="['x-link', 'x-popover-image']"
|
||||
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
<el-button size="mini" style="vertical-align: top" @click="clearImageGallerySelect">
|
||||
<el-button size="small" style="vertical-align: top" @click="clearImageGallerySelect">
|
||||
{{ t('dialog.invite_message.clear_selected_image') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button size="mini" style="margin-right: 5px" @click="showGallerySelectDialog">
|
||||
<el-button size="small" style="margin-right: 5px" @click="showGallerySelectDialog">
|
||||
{{ t('dialog.invite_message.select_image') }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -105,12 +108,13 @@
|
||||
:gallery-select-dialog="gallerySelectDialog"
|
||||
:gallery-table="galleryTable"
|
||||
@refresh-gallery-table="refreshGalleryTable" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { groupRequest, vrcPlusIconRequest } from '../../../api';
|
||||
import { useGalleryStore, useGroupStore } from '../../../stores';
|
||||
import GallerySelectDialog from './GallerySelectDialog.vue';
|
||||
@@ -125,7 +129,6 @@
|
||||
|
||||
const emit = defineEmits(['update:dialogData']);
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { showFullscreenImageDialog, handleFilesList } = useGalleryStore();
|
||||
@@ -169,7 +172,7 @@
|
||||
return;
|
||||
}
|
||||
if (!D.title || !D.text) {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Title and text are required',
|
||||
type: 'warning'
|
||||
});
|
||||
@@ -188,8 +191,8 @@
|
||||
params.imageId = gallerySelectDialog.value.selectedFileId;
|
||||
}
|
||||
groupRequest.editGroupPost(params).then((args) => {
|
||||
handleGroupPost();
|
||||
proxy.$message({
|
||||
handleGroupPost(args);
|
||||
ElMessage({
|
||||
message: 'Group post edited',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -200,7 +203,7 @@
|
||||
function createGroupPost() {
|
||||
const D = groupPostEditDialog.value;
|
||||
if (!D.title || !D.text) {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Title and text are required',
|
||||
type: 'warning'
|
||||
});
|
||||
@@ -220,7 +223,7 @@
|
||||
}
|
||||
groupRequest.createGroupPost(params).then((args) => {
|
||||
handleGroupPost();
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Group post created',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="editAndSendInviteDialog.visible"
|
||||
:model-value="editAndSendInviteDialog.visible"
|
||||
:title="t('dialog.edit_send_invite_message.header')"
|
||||
width="400px"
|
||||
append-to-body
|
||||
@@ -13,7 +13,7 @@
|
||||
<el-input
|
||||
v-model="editAndSendInviteDialog.newMessage"
|
||||
type="textarea"
|
||||
size="mini"
|
||||
size="small"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
@@ -21,28 +21,25 @@
|
||||
style="margin-top: 10px"></el-input>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="small" @click="cancelEditAndSendInvite">
|
||||
<el-button @click="cancelEditAndSendInvite">
|
||||
{{ t('dialog.edit_send_invite_message.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="saveEditAndSendInvite">
|
||||
{{ t('dialog.edit_send_invite_message.send') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { instanceRequest, inviteMessagesRequest, notificationRequest } from '../../../api';
|
||||
import { parseLocation } from '../../../shared/utils';
|
||||
import { useGalleryStore, useUserStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const { uploadImage } = storeToRefs(useGalleryStore());
|
||||
const { clearInviteImageUpload } = useGalleryStore();
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
@@ -86,13 +83,13 @@
|
||||
})
|
||||
.then((args) => {
|
||||
if (args.json[slot].message === I.messageSlot.message) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: "VRChat API didn't update message, try again",
|
||||
type: 'error'
|
||||
});
|
||||
throw new Error("VRChat API didn't update message, try again");
|
||||
} else {
|
||||
$message('Invite message updated');
|
||||
ElMessage('Invite message updated');
|
||||
}
|
||||
return args;
|
||||
});
|
||||
@@ -139,7 +136,7 @@
|
||||
} else {
|
||||
J.loading = false;
|
||||
J.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -155,7 +152,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite photo message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -168,7 +165,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -185,7 +182,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Request invite photo message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -198,7 +195,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Request invite message sent',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="inviteDialog.visible"
|
||||
v-model="inviteDialog.visible"
|
||||
:title="t('dialog.invite.header')"
|
||||
width="500px"
|
||||
append-to-body>
|
||||
<div v-if="inviteDialog.visible" v-loading="inviteDialog.loading">
|
||||
<Location :location="inviteDialog.worldId" :link="false" />
|
||||
<br />
|
||||
<el-button size="mini" style="margin-top: 10px" @click="addSelfToInvite">{{
|
||||
<el-button size="small" style="margin-top: 10px" @click="addSelfToInvite">{{
|
||||
t('dialog.invite.add_self')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
size="small"
|
||||
:disabled="inviteDialog.friendsInInstance.length === 0"
|
||||
style="margin-top: 10px"
|
||||
@click="addFriendsInInstanceToInvite"
|
||||
>{{ t('dialog.invite.add_friends_in_instance') }}</el-button
|
||||
>
|
||||
<el-button
|
||||
size="mini"
|
||||
size="small"
|
||||
:disabled="vipFriends.length === 0"
|
||||
style="margin-top: 10px"
|
||||
@click="addFavoriteFriendsToInvite"
|
||||
@@ -34,108 +34,116 @@
|
||||
filterable
|
||||
:disabled="inviteDialog.loading"
|
||||
style="width: 100%; margin-top: 15px">
|
||||
<el-option-group v-if="currentUser" :label="t('side_panel.me')">
|
||||
<el-option
|
||||
class="x-friend-item"
|
||||
:label="currentUser.displayName"
|
||||
:value="currentUser.id"
|
||||
style="height: auto">
|
||||
<div :class="['avatar', userStatusClass(currentUser)]">
|
||||
<img v-lazy="userImage(currentUser)" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name">{{ currentUser.displayName }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
|
||||
<el-option-group
|
||||
v-if="inviteDialog.friendsInInstance.length"
|
||||
:label="t('dialog.invite.friends_in_instance')">
|
||||
<el-option
|
||||
v-for="friend in inviteDialog.friendsInInstance"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<template v-if="currentUser">
|
||||
<el-option-group :label="t('side_panel.me')">
|
||||
<el-option
|
||||
class="x-friend-item"
|
||||
:label="currentUser.displayName"
|
||||
:value="currentUser.id"
|
||||
style="height: auto">
|
||||
<div :class="['avatar', userStatusClass(currentUser)]">
|
||||
<img :src="userImage(currentUser)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
<span class="name">{{ currentUser.displayName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</template>
|
||||
|
||||
<el-option-group v-if="vipFriends.length" :label="t('side_panel.favorite')">
|
||||
<el-option
|
||||
v-for="friend in vipFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<template v-if="inviteDialog.friendsInInstance.length">
|
||||
<el-option-group :label="t('dialog.invite.friends_in_instance')">
|
||||
<el-option
|
||||
v-for="friend in inviteDialog.friendsInInstance"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</template>
|
||||
|
||||
<el-option-group v-if="onlineFriends.length" :label="t('side_panel.online')">
|
||||
<el-option
|
||||
v-for="friend in onlineFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<template v-if="vipFriends.length">
|
||||
<el-option-group :label="t('side_panel.favorite')">
|
||||
<el-option
|
||||
v-for="friend in vipFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</template>
|
||||
|
||||
<el-option-group v-if="activeFriends.length" :label="t('side_panel.active')">
|
||||
<el-option
|
||||
v-for="friend in activeFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar"><img v-lazy="userImage(friend.ref)" /></div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<template v-if="onlineFriends.length">
|
||||
<el-option-group :label="t('side_panel.online')">
|
||||
<el-option
|
||||
v-for="friend in onlineFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</template>
|
||||
|
||||
<template v-if="activeFriends.length">
|
||||
<el-option-group :label="t('side_panel.active')">
|
||||
<el-option
|
||||
v-for="friend in activeFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar"><img :src="userImage(friend.ref)" loading="lazy" /></div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</template>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
@@ -155,17 +163,19 @@
|
||||
>
|
||||
</template>
|
||||
<SendInviteDialog
|
||||
:send-invite-dialog-visible.sync="sendInviteDialogVisible"
|
||||
:send-invite-dialog-visible="sendInviteDialogVisible"
|
||||
:send-invite-dialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { instanceRequest, notificationRequest } from '../../../api';
|
||||
import { parseLocation, userImage, userStatusClass } from '../../../shared/utils';
|
||||
import { useFriendStore, useGalleryStore, useInviteStore, useUserStore } from '../../../stores';
|
||||
@@ -177,10 +187,6 @@
|
||||
const { clearInviteImageUpload } = useGalleryStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
const $confirm = instance.proxy.$confirm;
|
||||
|
||||
const props = defineProps({
|
||||
inviteDialog: {
|
||||
type: Object,
|
||||
@@ -238,11 +244,12 @@
|
||||
}
|
||||
|
||||
function sendInvite() {
|
||||
$confirm('Continue? Invite', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Invite', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
const D = props.inviteDialog;
|
||||
if (action !== 'confirm' || D.loading === true) {
|
||||
return;
|
||||
@@ -275,14 +282,14 @@
|
||||
} else {
|
||||
D.loading = false;
|
||||
D.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite sent',
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
};
|
||||
inviteLoop();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="visible"
|
||||
:model-value="visible"
|
||||
:title="t('dialog.invite_message.header')"
|
||||
width="400px"
|
||||
append-to-body
|
||||
@@ -18,22 +18,19 @@
|
||||
{{ t('dialog.invite_message.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { instanceRequest, notificationRequest } from '../../../api';
|
||||
import { parseLocation } from '../../../shared/utils';
|
||||
import { useGalleryStore, useUserStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const { uploadImage } = storeToRefs(useGalleryStore());
|
||||
const { clearInviteImageUpload } = useGalleryStore();
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
@@ -54,10 +51,10 @@
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:visible', 'closeInviteDialog']);
|
||||
const emit = defineEmits(['update:model-value', 'closeInviteDialog']);
|
||||
|
||||
function cancelInviteConfirm() {
|
||||
emit('update:visible', false);
|
||||
emit('update:model-value', false);
|
||||
}
|
||||
|
||||
function sendInviteConfirm() {
|
||||
@@ -106,7 +103,7 @@
|
||||
} else {
|
||||
J.loading = false;
|
||||
J.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -122,7 +119,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite photo message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -135,7 +132,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -152,7 +149,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Request invite photo message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -165,7 +162,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Request invite message sent',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="sendInviteDialogVisible"
|
||||
:model-value="sendInviteDialogVisible"
|
||||
:title="t('dialog.invite_message.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@@ -9,46 +9,46 @@
|
||||
<template v-if="currentUser.$isVRCPlus">
|
||||
<!-- <template v-if="gallerySelectDialog.selectedFileId">-->
|
||||
<!-- <div style="display: inline-block; flex: none; margin-right: 5px">-->
|
||||
<!-- <el-popover placement="right" width="500px" trigger="click">-->
|
||||
<!-- <el-popover placement="right" :width="500px" trigger="click">-->
|
||||
<!-- <template #reference>-->
|
||||
<!-- <img-->
|
||||
<!-- class="x-link"-->
|
||||
<!-- v-lazy="gallerySelectDialog.selectedImageUrl"-->
|
||||
<!-- :src="gallerySelectDialog.selectedImageUrl"-->
|
||||
<!-- style="flex: none; width: 60px; height: 60px; border-radius: 4px; object-fit: cover" />-->
|
||||
<!-- </template>-->
|
||||
<!-- <img-->
|
||||
<!-- class="x-link"-->
|
||||
<!-- v-lazy="gallerySelectDialog.selectedImageUrl"-->
|
||||
<!-- :src="gallerySelectDialog.selectedImageUrl"-->
|
||||
<!-- style="height: 500px"-->
|
||||
<!-- @click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />-->
|
||||
<!-- </el-popover>-->
|
||||
<!-- </div>-->
|
||||
<!-- <el-button size="mini" @click="clearImageGallerySelect" style="vertical-align: top">-->
|
||||
<!-- <el-button size="small" @click="clearImageGallerySelect" style="vertical-align: top">-->
|
||||
<!-- {{ t('dialog.invite_message.clear_selected_image') }}-->
|
||||
<!-- </el-button>-->
|
||||
<!-- </template>-->
|
||||
<!-- <template v-else>-->
|
||||
<!-- <el-button size="mini" @click="showGallerySelectDialog" style="margin-right: 5px">-->
|
||||
<!-- <el-button size="small" @click="showGallerySelectDialog" style="margin-right: 5px">-->
|
||||
<!-- {{ t('dialog.invite_message.select_image') }}-->
|
||||
<!-- </el-button>-->
|
||||
<!-- </template>-->
|
||||
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||
</template>
|
||||
|
||||
<data-tables
|
||||
<DataTable
|
||||
v-bind="inviteMessageTable"
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
@row-click="showSendInviteConfirmDialog">
|
||||
<el-table-column
|
||||
:label="t('table.profile.invite_messages.slot')"
|
||||
prop="slot"
|
||||
sortable="custom"
|
||||
:sortable="true"
|
||||
width="70"></el-table-column>
|
||||
<el-table-column :label="t('table.profile.invite_messages.message')" prop="message"></el-table-column>
|
||||
<el-table-column
|
||||
:label="t('table.profile.invite_messages.cool_down')"
|
||||
prop="updatedAt"
|
||||
sortable="custom"
|
||||
:sortable="true"
|
||||
width="110"
|
||||
align="right">
|
||||
<template #default="scope">
|
||||
@@ -59,38 +59,40 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:icon="Edit"
|
||||
size="small"
|
||||
@click.stop="showEditAndSendInviteDialog(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="small" @click="cancelSendInvite">
|
||||
<el-button @click="cancelSendInvite">
|
||||
{{ t('dialog.invite_message.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="small" @click="refreshInviteMessageTableData('message')">
|
||||
<el-button @click="refreshInviteMessageTableData('message')">
|
||||
{{ t('dialog.invite_message.refresh') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<SendInviteConfirmDialog
|
||||
:visible.sync="isSendInviteConfirmDialogVisible"
|
||||
v-model="isSendInviteConfirmDialogVisible"
|
||||
:send-invite-dialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
<EditAndSendInviteDialog
|
||||
:edit-and-send-invite-dialog.sync="editAndSendInviteDialog"
|
||||
:edit-and-send-invite-dialog="editAndSendInviteDialog"
|
||||
:send-invite-dialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Edit } from '@element-plus/icons-vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useGalleryStore, useInviteStore, useUserStore } from '../../../stores';
|
||||
import EditAndSendInviteDialog from './EditAndSendInviteDialog.vue';
|
||||
import SendInviteConfirmDialog from './SendInviteConfirmDialog.vue';
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="inviteGroupDialogRef"
|
||||
:visible.sync="inviteGroupDialog.visible"
|
||||
:title="$t('dialog.invite_to_group.header')"
|
||||
<el-dialog
|
||||
:z-index="inviteGroupDialogIndex"
|
||||
v-model="inviteGroupDialog.visible"
|
||||
:title="t('dialog.invite_to_group.header')"
|
||||
width="450px"
|
||||
append-to-body>
|
||||
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
|
||||
<span>{{ $t('dialog.invite_to_group.description') }}</span>
|
||||
<span>{{ t('dialog.invite_to_group.description') }}</span>
|
||||
<br />
|
||||
<el-select
|
||||
v-model="inviteGroupDialog.groupId"
|
||||
clearable
|
||||
:placeholder="$t('dialog.invite_to_group.choose_group_placeholder')"
|
||||
:placeholder="t('dialog.invite_to_group.choose_group_placeholder')"
|
||||
filterable
|
||||
:disabled="inviteGroupDialog.loading"
|
||||
style="margin-top: 15px; width: 100%">
|
||||
<el-option-group
|
||||
:label="$t('dialog.invite_to_group.groups_with_invite_permission')"
|
||||
:label="t('dialog.invite_to_group.groups_with_invite_permission')"
|
||||
style="width: 410px">
|
||||
<el-option
|
||||
v-for="group in groupsWithInvitePermission"
|
||||
@@ -26,7 +26,7 @@
|
||||
style="height: auto"
|
||||
class="x-friend-item">
|
||||
<div class="avatar">
|
||||
<img v-lazy="group.iconUrl" />
|
||||
<img :src="group.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="group.name"></span>
|
||||
@@ -38,11 +38,11 @@
|
||||
v-model="inviteGroupDialog.userIds"
|
||||
multiple
|
||||
clearable
|
||||
:placeholder="$t('dialog.invite_to_group.choose_friends_placeholder')"
|
||||
:placeholder="t('dialog.invite_to_group.choose_friends_placeholder')"
|
||||
filterable
|
||||
:disabled="inviteGroupDialog.loading"
|
||||
style="width: 100%; margin-top: 15px">
|
||||
<el-option-group v-if="inviteGroupDialog.userId" :label="$t('dialog.invite_to_group.selected_users')">
|
||||
<el-option-group v-if="inviteGroupDialog.userId" :label="t('dialog.invite_to_group.selected_users')">
|
||||
<el-option
|
||||
:key="inviteGroupDialog.userObject.id"
|
||||
:label="inviteGroupDialog.userObject.displayName"
|
||||
@@ -50,7 +50,7 @@
|
||||
class="x-friend-item">
|
||||
<template v-if="inviteGroupDialog.userObject.id">
|
||||
<div class="avatar" :class="userStatusClass(inviteGroupDialog.userObject)">
|
||||
<img v-lazy="userImage(inviteGroupDialog.userObject)" />
|
||||
<img :src="userImage(inviteGroupDialog.userObject)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -62,7 +62,7 @@
|
||||
<span v-else v-text="inviteGroupDialog.userId"></span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<el-option-group v-if="vipFriends.length" :label="$t('side_panel.favorite')">
|
||||
<el-option-group v-if="vipFriends.length" :label="t('side_panel.favorite')">
|
||||
<el-option
|
||||
v-for="friend in vipFriends"
|
||||
:key="friend.id"
|
||||
@@ -72,7 +72,7 @@
|
||||
class="x-friend-item">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar" :class="userStatusClass(friend.ref)">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -84,7 +84,7 @@
|
||||
<span v-else v-text="friend.id"></span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<el-option-group v-if="onlineFriends.length" :label="$t('side_panel.online')">
|
||||
<el-option-group v-if="onlineFriends.length" :label="t('side_panel.online')">
|
||||
<el-option
|
||||
v-for="friend in onlineFriends"
|
||||
:key="friend.id"
|
||||
@@ -94,7 +94,7 @@
|
||||
class="x-friend-item">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar" :class="userStatusClass(friend.ref)">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -106,7 +106,7 @@
|
||||
<span v-else v-text="friend.id"></span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<el-option-group v-if="activeFriends.length" :label="$t('side_panel.active')">
|
||||
<el-option-group v-if="activeFriends.length" :label="t('side_panel.active')">
|
||||
<el-option
|
||||
v-for="friend in activeFriends"
|
||||
:key="friend.id"
|
||||
@@ -116,7 +116,7 @@
|
||||
class="x-friend-item">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -128,7 +128,7 @@
|
||||
<span v-else v-text="friend.id"></span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<el-option-group v-if="offlineFriends.length" :label="$t('side_panel.offline')">
|
||||
<el-option-group v-if="offlineFriends.length" :label="t('side_panel.offline')">
|
||||
<el-option
|
||||
v-for="friend in offlineFriends"
|
||||
:key="friend.id"
|
||||
@@ -138,7 +138,7 @@
|
||||
class="x-friend-item">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -158,24 +158,25 @@
|
||||
size="small"
|
||||
:disabled="inviteGroupDialog.loading || !inviteGroupDialog.userIds.length || !inviteGroupDialog.groupId"
|
||||
@click="sendGroupInvite">
|
||||
{{ $t('dialog.invite_to_group.invite') }}
|
||||
{{ t('dialog.invite_to_group.invite') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, getCurrentInstance, nextTick, computed } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { ref, watch, nextTick, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { groupRequest, userRequest } from '../../api';
|
||||
import { adjustDialogZ, hasGroupPermission, userImage, userStatusClass } from '../../shared/utils';
|
||||
import { getNextDialogIndex, hasGroupPermission, userImage, userStatusClass } from '../../shared/utils';
|
||||
import { useFriendStore, useGroupStore } from '../../stores';
|
||||
|
||||
const { vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore());
|
||||
const { currentUserGroups, inviteGroupDialog } = storeToRefs(useGroupStore());
|
||||
const { applyGroup } = useGroupStore();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { t } = useI18n();
|
||||
|
||||
watch(
|
||||
() => {
|
||||
@@ -188,7 +189,7 @@
|
||||
}
|
||||
);
|
||||
|
||||
const inviteGroupDialogRef = ref(null);
|
||||
const inviteGroupDialogIndex = ref(2000);
|
||||
|
||||
const groupsWithInvitePermission = computed(() => {
|
||||
return Array.from(currentUserGroups.value.values()).filter((group) =>
|
||||
@@ -197,7 +198,9 @@
|
||||
});
|
||||
|
||||
function initDialog() {
|
||||
nextTick(() => adjustDialogZ(inviteGroupDialogRef.value.$el));
|
||||
nextTick(() => {
|
||||
inviteGroupDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = inviteGroupDialog.value;
|
||||
if (D.groupId) {
|
||||
groupRequest
|
||||
@@ -236,7 +239,7 @@
|
||||
}
|
||||
// not allowed to invite
|
||||
inviteGroupDialog.value.groupId = '';
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: 'You are not allowed to invite to this group'
|
||||
});
|
||||
@@ -247,11 +250,12 @@
|
||||
});
|
||||
}
|
||||
function sendGroupInvite() {
|
||||
proxy.$confirm('Continue? Invite User(s) To Group', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Invite User(s) To Group', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
const D = inviteGroupDialog.value;
|
||||
if (action !== 'confirm' || D.loading === true) {
|
||||
return;
|
||||
@@ -274,7 +278,7 @@
|
||||
});
|
||||
};
|
||||
inviteLoop();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
<template>
|
||||
<safe-dialog ref="launchDialogRef" :visible.sync="isVisible" :title="t('dialog.launch.header')" width="450px">
|
||||
<el-dialog :z-index="launchDialogIndex" v-model="isVisible" :title="t('dialog.launch.header')" width="450px">
|
||||
<el-form :model="launchDialog" label-width="100px">
|
||||
<el-form-item :label="t('dialog.launch.url')">
|
||||
<el-input
|
||||
v-model="launchDialog.url"
|
||||
size="mini"
|
||||
style="width: 260px"
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
|
||||
size="small"
|
||||
style="width: 230px"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-s-order"
|
||||
size="small"
|
||||
:icon="CopyDocument"
|
||||
style="margin-left: 5px"
|
||||
circle
|
||||
@click="copyInstanceMessage(launchDialog.url)" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="launchDialog.shortUrl">
|
||||
<template slot="label">
|
||||
<template #label>
|
||||
<span>{{ t('dialog.launch.short_url') }}</span>
|
||||
<el-tooltip placement="top" style="margin-left: 5px" :content="t('dialog.launch.short_url_notice')">
|
||||
<i class="el-icon-warning" />
|
||||
<el-tooltip placement="top" :content="t('dialog.launch.short_url_notice')">
|
||||
<el-icon style="display: inline-block; margin-left: 5px"><Warning /></el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-input
|
||||
v-model="launchDialog.shortUrl"
|
||||
size="mini"
|
||||
style="width: 260px"
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
|
||||
size="small"
|
||||
style="width: 230px"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-s-order"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
:icon="CopyDocument"
|
||||
style="display: inline-block; margin-left: 5px"
|
||||
circle
|
||||
@click="copyInstanceMessage(launchDialog.shortUrl)" />
|
||||
</el-tooltip>
|
||||
@@ -40,23 +40,26 @@
|
||||
<el-form-item :label="t('dialog.launch.location')">
|
||||
<el-input
|
||||
v-model="launchDialog.location"
|
||||
size="mini"
|
||||
style="width: 260px"
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
|
||||
size="small"
|
||||
style="width: 230px"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-s-order"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
:icon="CopyDocument"
|
||||
style="display: inline-block; margin-left: 5px"
|
||||
circle
|
||||
@click="copyInstanceMessage(launchDialog.location)" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-checkbox v-model="launchDialog.desktop" style="float: left; margin-top: 5px" @change="saveLaunchDialog">
|
||||
<el-checkbox
|
||||
v-model="launchDialog.desktop"
|
||||
style="display: inline-flex; align-items: center; margin-top: 5px"
|
||||
@change="saveLaunchDialog">
|
||||
{{ t('dialog.launch.start_as_desktop') }}
|
||||
</el-checkbox>
|
||||
<template slot="footer">
|
||||
<template #footer>
|
||||
<el-button size="small" @click="showPreviousInstancesInfoDialog(launchDialog.location)">
|
||||
{{ t('dialog.launch.info') }}
|
||||
</el-button>
|
||||
@@ -68,7 +71,6 @@
|
||||
</el-button>
|
||||
<template v-if="canOpenInstanceInGame()">
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
:disabled="!launchDialog.secureOrShortName"
|
||||
@click="handleLaunchGame(launchDialog.location, launchDialog.shortName, launchDialog.desktop)">
|
||||
@@ -84,7 +86,6 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
:disabled="!launchDialog.secureOrShortName"
|
||||
@click="selfInvite(launchDialog.location, launchDialog.shortName)">
|
||||
@@ -100,18 +101,26 @@
|
||||
</template>
|
||||
</template>
|
||||
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, nextTick, watch, getCurrentInstance } from 'vue';
|
||||
import { CopyDocument, Warning } from '@element-plus/icons-vue';
|
||||
|
||||
import { ref, computed, nextTick, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { instanceRequest, worldRequest } from '../../api';
|
||||
import configRepository from '../../service/config';
|
||||
import { adjustDialogZ, checkCanInvite, getLaunchURL, isRealInstance, parseLocation } from '../../shared/utils';
|
||||
import {
|
||||
useAppearanceSettingsStore,
|
||||
getNextDialogIndex,
|
||||
checkCanInvite,
|
||||
getLaunchURL,
|
||||
isRealInstance,
|
||||
parseLocation
|
||||
} from '../../shared/utils';
|
||||
import {
|
||||
useFriendStore,
|
||||
useInviteStore,
|
||||
useInstanceStore,
|
||||
@@ -121,19 +130,18 @@
|
||||
} from '../../stores';
|
||||
import InviteDialog from './InviteDialog/InviteDialog.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { friends } = storeToRefs(useFriendStore());
|
||||
const { hideTooltips } = storeToRefs(useAppearanceSettingsStore());
|
||||
const { lastLocation } = storeToRefs(useLocationStore());
|
||||
const { launchGame, tryOpenInstanceInVrc } = useLaunchStore();
|
||||
const { launchDialogData } = storeToRefs(useLaunchStore());
|
||||
|
||||
const { showPreviousInstancesInfoDialog } = useInstanceStore();
|
||||
const { canOpenInstanceInGame } = useInviteStore();
|
||||
const { isGameRunning } = storeToRefs(useGameStore());
|
||||
|
||||
const launchDialogRef = ref(null);
|
||||
const launchDialogIndex = ref(2000);
|
||||
|
||||
const launchDialog = ref({
|
||||
loading: false,
|
||||
@@ -207,17 +215,18 @@
|
||||
}
|
||||
function handleLaunchGame(location, shortName, desktop) {
|
||||
if (isGameRunning.value) {
|
||||
proxy.$confirm(t('dialog.launch.game_running_warning'), t('dialog.launch.header'), {
|
||||
ElMessageBox.confirm(t('dialog.launch.game_running_warning'), t('dialog.launch.header'), {
|
||||
confirmButtonText: t('dialog.launch.confirm_yes'),
|
||||
cancelButtonText: t('dialog.launch.confirm_no'),
|
||||
type: 'warning',
|
||||
callback: (action) => {
|
||||
type: 'warning'
|
||||
})
|
||||
.then((action) => {
|
||||
if (action === 'confirm') {
|
||||
launchGame(location, shortName, desktop);
|
||||
isVisible.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
return;
|
||||
}
|
||||
launchGame(location, shortName, desktop);
|
||||
@@ -239,7 +248,7 @@
|
||||
shortName
|
||||
})
|
||||
.then((args) => {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Self invite sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -257,7 +266,9 @@
|
||||
if (!isRealInstance(tag)) {
|
||||
return;
|
||||
}
|
||||
nextTick(() => adjustDialogZ(launchDialogRef.value.$el));
|
||||
nextTick(() => {
|
||||
launchDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = launchDialog.value;
|
||||
D.tag = tag;
|
||||
D.secureOrShortName = shortName;
|
||||
@@ -300,12 +311,12 @@
|
||||
async function copyInstanceMessage(input) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(input);
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Instance copied to clipboard',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Instance copied failed',
|
||||
type: 'error'
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="moderateGroupDialogRef"
|
||||
:visible.sync="moderateGroupDialog.visible"
|
||||
:title="$t('dialog.moderate_group.header')"
|
||||
<el-dialog
|
||||
:z-index="moderateGroupDialogIndex"
|
||||
v-model="moderateGroupDialog.visible"
|
||||
:title="t('dialog.moderate_group.header')"
|
||||
width="450px"
|
||||
append-to-body>
|
||||
<div v-if="moderateGroupDialog.visible">
|
||||
<div class="x-friend-item" style="cursor: default">
|
||||
<div class="avatar">
|
||||
<img v-lazy="userImage(moderateGroupDialog.userObject)" />
|
||||
<img :src="userImage(moderateGroupDialog.userObject)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -22,12 +22,12 @@
|
||||
<el-select
|
||||
v-model="moderateGroupDialog.groupId"
|
||||
clearable
|
||||
:placeholder="$t('dialog.moderate_group.choose_group_placeholder')"
|
||||
:placeholder="t('dialog.moderate_group.choose_group_placeholder')"
|
||||
filterable
|
||||
style="margin-top: 15px; width: 100%">
|
||||
<el-option-group
|
||||
v-if="currentUserGroups.size"
|
||||
:label="$t('dialog.moderate_group.groups_with_moderation_permission')">
|
||||
:label="t('dialog.moderate_group.groups_with_moderation_permission')">
|
||||
<el-option
|
||||
v-for="group in groupsWithModerationPermission"
|
||||
:key="group.id"
|
||||
@@ -36,7 +36,7 @@
|
||||
style="height: auto"
|
||||
class="x-friend-item">
|
||||
<div class="avatar">
|
||||
<img v-lazy="group.iconUrl" />
|
||||
<img :src="group.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="group.name"></span>
|
||||
@@ -54,29 +54,23 @@
|
||||
showGroupMemberModerationDialog(moderateGroupDialog.groupId, moderateGroupDialog.userId);
|
||||
moderateGroupDialog.visible = false;
|
||||
">
|
||||
{{ $t('dialog.moderate_group.moderation_tools') }}
|
||||
{{ t('dialog.moderate_group.moderation_tools') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, getCurrentInstance, nextTick, computed } from 'vue';
|
||||
import { ref, watch, nextTick, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { groupRequest, userRequest } from '../../api';
|
||||
import {
|
||||
adjustDialogZ,
|
||||
hasGroupPermission,
|
||||
hasGroupModerationPermission,
|
||||
userImage,
|
||||
userStatusClass
|
||||
} from '../../shared/utils';
|
||||
import { getNextDialogIndex, hasGroupModerationPermission, userImage } from '../../shared/utils';
|
||||
import { useGroupStore } from '../../stores';
|
||||
|
||||
const { currentUserGroups, moderateGroupDialog } = storeToRefs(useGroupStore());
|
||||
const { applyGroup, showGroupMemberModerationDialog } = useGroupStore();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { showGroupMemberModerationDialog } = useGroupStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const groupsWithModerationPermission = computed(() => {
|
||||
return Array.from(currentUserGroups.value.values()).filter((group) => hasGroupModerationPermission(group));
|
||||
@@ -93,10 +87,12 @@
|
||||
}
|
||||
);
|
||||
|
||||
const moderateGroupDialogRef = ref(null);
|
||||
const moderateGroupDialogIndex = ref(2000);
|
||||
|
||||
function initDialog() {
|
||||
nextTick(() => adjustDialogZ(moderateGroupDialogRef.value.$el));
|
||||
nextTick(() => {
|
||||
moderateGroupDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = moderateGroupDialog.value;
|
||||
if (D.groupId) {
|
||||
groupRequest
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="newInstanceDialogRef"
|
||||
:visible.sync="newInstanceDialog.visible"
|
||||
<el-dialog
|
||||
:z-index="newInstanceDialogIndex"
|
||||
v-model="newInstanceDialog.visible"
|
||||
:title="t('dialog.new_instance.header')"
|
||||
width="650px"
|
||||
append-to-body>
|
||||
<el-tabs v-model="newInstanceDialog.selectedTab" type="card" @tab-click="newInstanceTabClick">
|
||||
<el-tab-pane :label="t('dialog.new_instance.normal')">
|
||||
<el-tab-pane name="Normal" :label="t('dialog.new_instance.normal')">
|
||||
<el-form :model="newInstanceDialog" label-width="150px">
|
||||
<el-form-item :label="t('dialog.new_instance.access_type')">
|
||||
<el-radio-group v-model="newInstanceDialog.accessType" size="mini" @change="buildInstance">
|
||||
<el-radio-group v-model="newInstanceDialog.accessType" size="small" @change="buildInstance">
|
||||
<el-radio-button label="public">{{
|
||||
t('dialog.new_instance.access_type_public')
|
||||
}}</el-radio-button>
|
||||
@@ -33,7 +33,10 @@
|
||||
<el-form-item
|
||||
v-if="newInstanceDialog.accessType === 'group'"
|
||||
:label="t('dialog.new_instance.group_access_type')">
|
||||
<el-radio-group v-model="newInstanceDialog.groupAccessType" size="mini" @change="buildInstance">
|
||||
<el-radio-group
|
||||
v-model="newInstanceDialog.groupAccessType"
|
||||
size="small"
|
||||
@change="buildInstance">
|
||||
<el-radio-button
|
||||
label="members"
|
||||
:disabled="
|
||||
@@ -59,7 +62,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.region')">
|
||||
<el-radio-group v-model="newInstanceDialog.region" size="mini" @change="buildInstance">
|
||||
<el-radio-group v-model="newInstanceDialog.region" size="small" @change="buildInstance">
|
||||
<el-radio-button label="US West">{{ t('dialog.new_instance.region_usw') }}</el-radio-button>
|
||||
<el-radio-button label="US East">{{ t('dialog.new_instance.region_use') }}</el-radio-button>
|
||||
<el-radio-button label="Europe">{{ t('dialog.new_instance.region_eu') }}</el-radio-button>
|
||||
@@ -84,8 +87,16 @@
|
||||
<el-form-item :label="t('dialog.new_instance.world_id')">
|
||||
<el-input
|
||||
v-model="newInstanceDialog.worldId"
|
||||
size="mini"
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"
|
||||
size="small"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()"
|
||||
@change="buildInstance"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.display_name')">
|
||||
<el-input
|
||||
:disabled="!isLocalUserVrcplusSupporter"
|
||||
v-model="newInstanceDialog.displayName"
|
||||
size="small"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()"
|
||||
@change="buildInstance"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
@@ -114,7 +125,7 @@
|
||||
hasGroupPermission(group, 'group-instance-open-create'))
|
||||
">
|
||||
<div class="avatar">
|
||||
<img v-lazy="group.iconUrl" />
|
||||
<img :src="group.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="group.name"></span>
|
||||
@@ -155,22 +166,22 @@
|
||||
<el-form-item :label="t('dialog.new_instance.location')">
|
||||
<el-input
|
||||
v-model="newInstanceDialog.location"
|
||||
size="mini"
|
||||
size="small"
|
||||
readonly
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"></el-input>
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.url')">
|
||||
<el-input v-model="newInstanceDialog.url" size="mini" readonly></el-input>
|
||||
<el-input v-model="newInstanceDialog.url" size="small" readonly></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('dialog.new_instance.legacy')">
|
||||
<el-tab-pane name="Legacy" :label="t('dialog.new_instance.legacy')">
|
||||
<el-form :model="newInstanceDialog" label-width="150px">
|
||||
<el-form-item :label="t('dialog.new_instance.access_type')">
|
||||
<el-radio-group
|
||||
v-model="newInstanceDialog.accessType"
|
||||
size="mini"
|
||||
size="small"
|
||||
@change="buildLegacyInstance">
|
||||
<el-radio-button label="public">{{
|
||||
t('dialog.new_instance.access_type_public')
|
||||
@@ -197,7 +208,7 @@
|
||||
:label="t('dialog.new_instance.group_access_type')">
|
||||
<el-radio-group
|
||||
v-model="newInstanceDialog.groupAccessType"
|
||||
size="mini"
|
||||
size="small"
|
||||
@change="buildLegacyInstance">
|
||||
<el-radio-button label="members">{{
|
||||
t('dialog.new_instance.group_access_type_members')
|
||||
@@ -211,7 +222,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.region')">
|
||||
<el-radio-group v-model="newInstanceDialog.region" size="mini" @change="buildLegacyInstance">
|
||||
<el-radio-group v-model="newInstanceDialog.region" size="small" @change="buildLegacyInstance">
|
||||
<el-radio-button label="US West">{{ t('dialog.new_instance.region_usw') }}</el-radio-button>
|
||||
<el-radio-button label="US East">{{ t('dialog.new_instance.region_use') }}</el-radio-button>
|
||||
<el-radio-button label="Europe">{{ t('dialog.new_instance.region_eu') }}</el-radio-button>
|
||||
@@ -226,15 +237,15 @@
|
||||
<el-form-item :label="t('dialog.new_instance.world_id')">
|
||||
<el-input
|
||||
v-model="newInstanceDialog.worldId"
|
||||
size="mini"
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"
|
||||
size="small"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()"
|
||||
@change="buildLegacyInstance"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.instance_id')">
|
||||
<el-input
|
||||
v-model="newInstanceDialog.instanceName"
|
||||
:placeholder="t('dialog.new_instance.instance_id_placeholder')"
|
||||
size="mini"
|
||||
size="small"
|
||||
@change="buildLegacyInstance"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
@@ -254,7 +265,7 @@
|
||||
:value="currentUser.id"
|
||||
style="height: auto">
|
||||
<div class="avatar" :class="userStatusClass(currentUser)">
|
||||
<img v-lazy="userImage(currentUser)" />
|
||||
<img :src="userImage(currentUser)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="currentUser.displayName"></span>
|
||||
@@ -271,7 +282,7 @@
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar" :class="userStatusClass(friend.ref)">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -293,7 +304,7 @@
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar" :class="userStatusClass(friend.ref)">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -315,7 +326,7 @@
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -337,7 +348,7 @@
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -371,7 +382,7 @@
|
||||
style="height: auto; width: 478px">
|
||||
<template v-if="group">
|
||||
<div class="avatar">
|
||||
<img v-lazy="group.iconUrl" />
|
||||
<img :src="group.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="group.name"></span></div
|
||||
@@ -383,17 +394,17 @@
|
||||
<el-form-item :label="t('dialog.new_instance.location')">
|
||||
<el-input
|
||||
v-model="newInstanceDialog.location"
|
||||
size="mini"
|
||||
size="small"
|
||||
readonly
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"></el-input>
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.url')">
|
||||
<el-input v-model="newInstanceDialog.url" size="mini" readonly></el-input>
|
||||
<el-input v-model="newInstanceDialog.url" size="small" readonly></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template v-if="newInstanceDialog.selectedTab === '0'" #footer>
|
||||
<template v-if="newInstanceDialog.selectedTab === 'Normal'" #footer>
|
||||
<template v-if="newInstanceDialog.instanceCreated">
|
||||
<el-button size="small" @click="copyInstanceUrl(newInstanceDialog.location)">{{
|
||||
t('dialog.new_instance.copy_url')
|
||||
@@ -412,13 +423,11 @@
|
||||
>
|
||||
<template v-if="canOpenInstanceInGame()">
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
|
||||
>{{ t('dialog.new_instance.launch') }}</el-button
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleAttachGame(newInstanceDialog.location, newInstanceDialog.shortName)">
|
||||
{{ t('dialog.new_instance.open_ingame') }}
|
||||
@@ -439,7 +448,7 @@
|
||||
}}</el-button>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="newInstanceDialog.selectedTab === '1'" #footer>
|
||||
<template v-else-if="newInstanceDialog.selectedTab === 'Legacy'" #footer>
|
||||
<el-button size="small" @click="copyInstanceUrl(newInstanceDialog.location)">{{
|
||||
t('dialog.new_instance.copy_url')
|
||||
}}</el-button>
|
||||
@@ -457,7 +466,6 @@
|
||||
>
|
||||
<template v-if="canOpenInstanceInGame()">
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
|
||||
>{{ t('dialog.new_instance.launch') }}</el-button
|
||||
@@ -479,17 +487,18 @@
|
||||
</template>
|
||||
</template>
|
||||
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, nextTick, getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ref, watch, nextTick, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { groupRequest, instanceRequest, worldRequest } from '../../api';
|
||||
import configRepository from '../../service/config';
|
||||
import {
|
||||
adjustDialogZ,
|
||||
getNextDialogIndex,
|
||||
copyToClipboard,
|
||||
getLaunchURL,
|
||||
hasGroupPermission,
|
||||
@@ -518,11 +527,9 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const { friends, vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore());
|
||||
const { currentUserGroups, cachedGroups } = storeToRefs(useGroupStore());
|
||||
const { handleGroupPermissions } = useGroupStore();
|
||||
const { currentUserGroups } = storeToRefs(useGroupStore());
|
||||
const { cachedGroups, handleGroupPermissions } = useGroupStore();
|
||||
const { lastLocation } = storeToRefs(useLocationStore());
|
||||
const { showLaunchDialog } = useLaunchStore();
|
||||
const { createNewInstance } = useInstanceStore();
|
||||
@@ -530,12 +537,12 @@
|
||||
const { tryOpenInstanceInVrc } = useLaunchStore();
|
||||
const { canOpenInstanceInGame } = useInviteStore();
|
||||
|
||||
const newInstanceDialogRef = ref(null);
|
||||
const newInstanceDialogIndex = ref(2000);
|
||||
|
||||
const newInstanceDialog = ref({
|
||||
visible: false,
|
||||
// loading: false,
|
||||
selectedTab: '0',
|
||||
selectedTab: 'Normal',
|
||||
instanceCreated: false,
|
||||
queueEnabled: false,
|
||||
worldId: '',
|
||||
@@ -551,6 +558,7 @@
|
||||
strict: false,
|
||||
location: '',
|
||||
shortName: '',
|
||||
displayName: '',
|
||||
url: '',
|
||||
secureOrShortName: '',
|
||||
lastSelectedGroupId: '',
|
||||
@@ -575,6 +583,8 @@
|
||||
}
|
||||
);
|
||||
|
||||
const isLocalUserVrcplusSupporter = computed(() => currentUser.value.$isVRCPlus);
|
||||
|
||||
initializeNewInstanceDialog();
|
||||
|
||||
function closeInviteDialog() {
|
||||
@@ -617,7 +627,9 @@
|
||||
if (!isRealInstance(tag)) {
|
||||
return;
|
||||
}
|
||||
nextTick(() => adjustDialogZ(newInstanceDialogRef.value.$el));
|
||||
nextTick(() => {
|
||||
newInstanceDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = newInstanceDialog.value;
|
||||
const L = parseLocation(tag);
|
||||
if (D.worldId === L.worldId) {
|
||||
@@ -634,6 +646,9 @@
|
||||
D.strict = false;
|
||||
D.shortName = '';
|
||||
D.secureOrShortName = '';
|
||||
if (!isLocalUserVrcplusSupporter.value) {
|
||||
D.displayName = '';
|
||||
}
|
||||
const args = await groupRequest.getGroupPermissions({ userId: currentUser.value.id });
|
||||
handleGroupPermissions(args);
|
||||
buildInstance();
|
||||
@@ -673,10 +688,23 @@
|
||||
configRepository
|
||||
.getBool('instanceDialogAgeGate', false)
|
||||
.then((value) => (newInstanceDialog.value.ageGate = value));
|
||||
|
||||
configRepository
|
||||
.getString('instanceDialogDisplayName', '')
|
||||
.then((value) => (newInstanceDialog.value.displayName = value));
|
||||
}
|
||||
function saveNewInstanceDialog() {
|
||||
const { accessType, region, instanceName, userId, groupId, groupAccessType, queueEnabled, ageGate } =
|
||||
newInstanceDialog.value;
|
||||
const {
|
||||
accessType,
|
||||
region,
|
||||
instanceName,
|
||||
userId,
|
||||
groupId,
|
||||
groupAccessType,
|
||||
queueEnabled,
|
||||
ageGate,
|
||||
displayName
|
||||
} = newInstanceDialog.value;
|
||||
|
||||
configRepository.setString('instanceDialogAccessType', accessType);
|
||||
configRepository.setString('instanceRegion', region);
|
||||
@@ -686,9 +714,10 @@
|
||||
configRepository.setString('instanceDialogGroupAccessType', groupAccessType);
|
||||
configRepository.setBool('instanceDialogQueueEnabled', queueEnabled);
|
||||
configRepository.setBool('instanceDialogAgeGate', ageGate);
|
||||
configRepository.setString('instanceDialogDisplayName', displayName);
|
||||
}
|
||||
function newInstanceTabClick(tab) {
|
||||
if (tab === '1') {
|
||||
function newInstanceTabClick(obj) {
|
||||
if (obj.props.name === 'Normal') {
|
||||
buildInstance();
|
||||
} else {
|
||||
buildLegacyInstance();
|
||||
@@ -720,7 +749,7 @@
|
||||
worldId: L.worldId
|
||||
})
|
||||
.then((args) => {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Self invite sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -749,7 +778,7 @@
|
||||
}
|
||||
if (D.groupId && D.groupId !== D.lastSelectedGroupId) {
|
||||
D.roleIds = [];
|
||||
const ref = cachedGroups.value.get(D.groupId);
|
||||
const ref = cachedGroups.get(D.groupId);
|
||||
if (typeof ref !== 'undefined') {
|
||||
D.groupRef = ref;
|
||||
D.selectedGroupRoles = ref.roles;
|
||||
@@ -824,7 +853,7 @@
|
||||
}
|
||||
if (D.groupId && D.groupId !== D.lastSelectedGroupId) {
|
||||
D.roleIds = [];
|
||||
const ref = cachedGroups.value.get(D.groupId);
|
||||
const ref = cachedGroups.get(D.groupId);
|
||||
if (typeof ref !== 'undefined') {
|
||||
D.groupRef = ref;
|
||||
D.selectedGroupRoles = ref.roles;
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
class="x-dialog"
|
||||
:visible="previousImagesDialogVisible"
|
||||
:title="t('dialog.previous_images.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@close="closeDialog">
|
||||
<div>
|
||||
<div v-for="image in previousImagesTable" :key="image.version" style="display: inline-block">
|
||||
<el-popover
|
||||
class="x-change-image-item"
|
||||
placement="right"
|
||||
width="500px"
|
||||
trigger="click"
|
||||
v-if="image.file">
|
||||
<img slot="reference" v-lazy="image.file.url" class="x-link" />
|
||||
<img
|
||||
v-lazy="image.file.url"
|
||||
class="x-link"
|
||||
style="width: 500px; height: 375px"
|
||||
@click="showFullscreenImageDialog(image.file.url)" />
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useGalleryStore } from '../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { previousImagesDialogVisible, previousImagesTable } = storeToRefs(useGalleryStore());
|
||||
const { showFullscreenImageDialog } = useGalleryStore();
|
||||
|
||||
function closeDialog() {
|
||||
previousImagesDialogVisible.value = false;
|
||||
}
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="previousInstancesGroupDialogRef"
|
||||
:visible.sync="isVisible"
|
||||
<el-dialog
|
||||
:z-index="previousInstancesGroupDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
append-to-body>
|
||||
@@ -13,7 +13,7 @@
|
||||
style="width: 150px" />
|
||||
</div>
|
||||
|
||||
<data-tables v-loading="loading" v-bind="previousInstancesGroupDialogTable" style="margin-top: 10px">
|
||||
<DataTable v-loading="loading" v-bind="previousInstancesGroupDialogTable" style="margin-top: 10px">
|
||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||
@@ -39,48 +39,50 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-s-data"
|
||||
size="mini"
|
||||
:icon="DataLine"
|
||||
size="small"
|
||||
@click="showPreviousInstancesInfoDialog(scope.row.location)" />
|
||||
<el-button
|
||||
v-if="shiftHeld"
|
||||
style="color: #f56c6c"
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
@click="deleteGameLogGroupInstance(scope.row)" />
|
||||
<el-button
|
||||
v-else
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
@click="deleteGameLogGroupInstancePrompt(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</safe-dialog>
|
||||
</DataTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { DataLine, Close } from '@element-plus/icons-vue';
|
||||
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { ref, reactive, computed, watch, nextTick, getCurrentInstance } from 'vue';
|
||||
import {
|
||||
parseLocation,
|
||||
compareByCreatedAt,
|
||||
timeToText,
|
||||
removeFromArray,
|
||||
adjustDialogZ,
|
||||
getNextDialogIndex,
|
||||
formatDateFilter
|
||||
} from '../../../shared/utils';
|
||||
import { database } from '../../../service/database';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useInstanceStore, useUiStore } from '../../../stores';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { showPreviousInstancesInfoDialog } = useInstanceStore();
|
||||
const { shiftHeld } = useUiStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const previousInstancesGroupDialogRef = ref(null);
|
||||
const previousInstancesGroupDialogIndex = ref(2000);
|
||||
const loading = ref(false);
|
||||
|
||||
const previousInstancesGroupDialogTable = reactive({
|
||||
@@ -88,7 +90,7 @@
|
||||
filters: [{ prop: 'groupName', value: '' }],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: { prop: 'created_at', order: 'descending' }
|
||||
},
|
||||
pageSize: 10,
|
||||
@@ -115,7 +117,7 @@
|
||||
() => {
|
||||
if (props.previousInstancesGroupDialog.visible) {
|
||||
nextTick(() => {
|
||||
adjustDialogZ(previousInstancesGroupDialogRef.value.$el);
|
||||
previousInstancesGroupDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
refreshPreviousInstancesGroupTable();
|
||||
}
|
||||
@@ -144,15 +146,16 @@
|
||||
}
|
||||
|
||||
function deleteGameLogGroupInstancePrompt(row) {
|
||||
proxy.$confirm('Continue? Delete GameLog Instance', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Delete GameLog Instance', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
if (action === 'confirm') {
|
||||
deleteGameLogGroupInstance(row);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="dialogRef"
|
||||
:visible="previousInstancesInfoDialogVisible"
|
||||
:title="$t('dialog.previous_instances.info')"
|
||||
<el-dialog
|
||||
:z-index="previousInstancesInfoDialogIndex"
|
||||
:model-value="previousInstancesInfoDialogVisible"
|
||||
:title="t('dialog.previous_instances.info')"
|
||||
width="800px"
|
||||
:fullscreen="fullscreen"
|
||||
destroy-on-close
|
||||
@@ -11,12 +11,12 @@
|
||||
<Location :location="location.tag" style="font-size: 14px" />
|
||||
<el-input
|
||||
v-model="dataTable.filters[0].value"
|
||||
:placeholder="$t('dialog.previous_instances.search_placeholder')"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
style="width: 150px"
|
||||
clearable></el-input>
|
||||
</div>
|
||||
<data-tables v-loading="loading" v-bind="dataTable" style="margin-top: 10px">
|
||||
<el-table-column :label="$t('table.previous_instances.date')" prop="created_at" sortable width="110">
|
||||
<DataTable v-loading="loading" v-bind="dataTable" style="margin-top: 10px">
|
||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="110">
|
||||
<template #default="scope">
|
||||
<el-tooltip placement="left">
|
||||
<template #content>
|
||||
@@ -26,7 +26,7 @@
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.gameLog.icon')" prop="isFriend" width="70" align="center">
|
||||
<el-table-column :label="t('table.gameLog.icon')" prop="isFriend" width="70" align="center">
|
||||
<template #default="scope">
|
||||
<template v-if="gameLogIsFriend(scope.row)">
|
||||
<el-tooltip v-if="gameLogIsFavorite(scope.row)" placement="top" content="Favorite">
|
||||
@@ -36,33 +36,35 @@
|
||||
<span>💚</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<span v-else></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.display_name')" prop="displayName" sortable>
|
||||
<el-table-column :label="t('table.previous_instances.display_name')" prop="displayName" sortable>
|
||||
<template #default="scope">
|
||||
<span class="x-link" @click="lookupUser(scope.row)">{{ scope.row.displayName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.time')" prop="time" width="100" sortable>
|
||||
<el-table-column :label="t('table.previous_instances.time')" prop="time" width="100" sortable>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.timer }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.count')" prop="count" width="100" sortable>
|
||||
<el-table-column :label="t('table.previous_instances.count')" prop="count" width="100" sortable>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.count }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</safe-dialog>
|
||||
</DataTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { database } from '../../../service/database';
|
||||
import {
|
||||
adjustDialogZ,
|
||||
getNextDialogIndex,
|
||||
compareByCreatedAt,
|
||||
parseLocation,
|
||||
timeToText,
|
||||
@@ -74,8 +76,9 @@
|
||||
const { previousInstancesInfoDialogVisible, previousInstancesInfoDialogInstanceId } =
|
||||
storeToRefs(useInstanceStore());
|
||||
const { gameLogIsFriend, gameLogIsFavorite } = useGameLogStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const dialogRef = ref(null);
|
||||
const previousInstancesInfoDialogIndex = ref(2000);
|
||||
|
||||
const loading = ref(false);
|
||||
const location = ref({
|
||||
@@ -111,7 +114,7 @@
|
||||
],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: {
|
||||
prop: 'created_at',
|
||||
order: 'descending'
|
||||
@@ -139,7 +142,7 @@
|
||||
);
|
||||
|
||||
function init() {
|
||||
adjustDialogZ(dialogRef.value.$el);
|
||||
previousInstancesInfoDialogIndex.value = getNextDialogIndex();
|
||||
loading.value = true;
|
||||
location.value = parseLocation(previousInstancesInfoDialogInstanceId.value);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="previousInstancesWorldDialogRef"
|
||||
:visible.sync="isVisible"
|
||||
<el-dialog
|
||||
:z-index="previousInstancesWorldDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
append-to-body>
|
||||
@@ -12,7 +12,7 @@
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
style="display: block; width: 150px"></el-input>
|
||||
</div>
|
||||
<data-tables v-loading="loading" v-bind="previousInstancesWorldDialogTable" style="margin-top: 10px">
|
||||
<DataTable v-loading="loading" v-bind="previousInstancesWorldDialogTable" style="margin-top: 10px">
|
||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||
@@ -43,35 +43,41 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-s-data"
|
||||
size="mini"
|
||||
:icon="DataLine"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="showPreviousInstancesInfoDialog(scope.row.location)"></el-button>
|
||||
<el-button
|
||||
v-if="shiftHeld"
|
||||
style="color: #f56c6c"
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="deleteGameLogWorldInstance(scope.row)"></el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="deleteGameLogWorldInstancePrompt(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</safe-dialog>
|
||||
</DataTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { DataLine, Close } from '@element-plus/icons-vue';
|
||||
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, getCurrentInstance, nextTick, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { database } from '../../../service/database';
|
||||
import {
|
||||
adjustDialogZ,
|
||||
getNextDialogIndex,
|
||||
compareByCreatedAt,
|
||||
parseLocation,
|
||||
removeFromArray,
|
||||
@@ -80,7 +86,6 @@
|
||||
} from '../../../shared/utils';
|
||||
import { useInstanceStore, useUiStore, useUserStore } from '../../../stores';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
@@ -100,7 +105,7 @@
|
||||
filters: [{ prop: 'groupName', value: '' }],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: { prop: 'created_at', order: 'descending' }
|
||||
},
|
||||
pageSize: 10,
|
||||
@@ -111,7 +116,7 @@
|
||||
}
|
||||
});
|
||||
const loading = ref(false);
|
||||
const previousInstancesWorldDialogRef = ref(null);
|
||||
const previousInstancesWorldDialogIndex = ref(2000);
|
||||
|
||||
const isVisible = computed({
|
||||
get: () => props.previousInstancesWorldDialog.visible,
|
||||
@@ -145,16 +150,17 @@
|
||||
}
|
||||
|
||||
function deleteGameLogWorldInstancePrompt(row) {
|
||||
proxy.$confirm('Continue? Delete GameLog Instance', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Delete GameLog Instance', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
if (action === 'confirm') {
|
||||
deleteGameLogWorldInstance(row);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -162,10 +168,16 @@
|
||||
() => {
|
||||
if (props.previousInstancesWorldDialog.visible) {
|
||||
nextTick(() => {
|
||||
adjustDialogZ(previousInstancesWorldDialogRef.value.$el);
|
||||
previousInstancesWorldDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
refreshPreviousInstancesWorldTable();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.button-pd-0 {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
ref="elDialogRef"
|
||||
:visible="props.visible"
|
||||
v-bind="attrs"
|
||||
:close-on-click-modal="false"
|
||||
@open="handleOpen"
|
||||
@close="handleClose"
|
||||
:top="marginTop">
|
||||
<slot></slot>
|
||||
|
||||
<template v-if="slots.title" #title>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
|
||||
<template v-if="slots.footer" #footer>
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onBeforeUnmount, nextTick, useAttrs, useSlots } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:visible', 'open', 'close']);
|
||||
|
||||
const attrs = useAttrs();
|
||||
const slots = useSlots();
|
||||
|
||||
const elDialogRef = ref(null);
|
||||
const wrapperElement = ref(null);
|
||||
const mouseDownOnWrapper = ref(false);
|
||||
const styleObserver = ref(null);
|
||||
const resizeObserver = ref(null);
|
||||
const marginTop = ref('5px');
|
||||
let handleResize = null;
|
||||
|
||||
const handleOpen = () => {
|
||||
emit('open');
|
||||
|
||||
nextTick(() => {
|
||||
addWrapperListeners();
|
||||
removeTitleAttribute();
|
||||
adjustDialogMarginTop();
|
||||
});
|
||||
};
|
||||
|
||||
const removeTitleAttribute = () => {
|
||||
const wrapper = elDialogRef.value?.$el;
|
||||
if (wrapper && wrapper.nodeType === Node.ELEMENT_NODE) {
|
||||
wrapper.removeAttribute('title');
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
removeWrapperListeners();
|
||||
|
||||
if (styleObserver.value) {
|
||||
styleObserver.value.disconnect();
|
||||
styleObserver.value = null;
|
||||
}
|
||||
|
||||
if (resizeObserver.value) {
|
||||
resizeObserver.value.disconnect();
|
||||
resizeObserver.value = null;
|
||||
}
|
||||
|
||||
if (handleResize) {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
handleResize = null;
|
||||
}
|
||||
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
const handleWrapperMouseDown = (event) => {
|
||||
if (event.target === wrapperElement.value) {
|
||||
mouseDownOnWrapper.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleWrapperMouseUp = (event) => {
|
||||
if (event.target === wrapperElement.value && mouseDownOnWrapper.value) {
|
||||
handleClose();
|
||||
}
|
||||
mouseDownOnWrapper.value = false;
|
||||
};
|
||||
|
||||
const addWrapperListeners = () => {
|
||||
const wrapper = elDialogRef.value?.$el;
|
||||
|
||||
if (
|
||||
wrapper &&
|
||||
wrapper.nodeType === Node.ELEMENT_NODE &&
|
||||
wrapper.classList &&
|
||||
wrapper.classList.contains('el-dialog__wrapper')
|
||||
) {
|
||||
wrapperElement.value = wrapper;
|
||||
wrapperElement.value.addEventListener('mousedown', handleWrapperMouseDown);
|
||||
wrapperElement.value.addEventListener('mouseup', handleWrapperMouseUp);
|
||||
} else {
|
||||
wrapperElement.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const removeWrapperListeners = () => {
|
||||
if (wrapperElement.value) {
|
||||
wrapperElement.value.removeEventListener('mousedown', handleWrapperMouseDown);
|
||||
wrapperElement.value.removeEventListener('mouseup', handleWrapperMouseUp);
|
||||
wrapperElement.value = null;
|
||||
}
|
||||
mouseDownOnWrapper.value = false;
|
||||
};
|
||||
|
||||
const adjustDialogMarginTop = () => {
|
||||
const wrapper = elDialogRef.value?.$el;
|
||||
if (!wrapper) return;
|
||||
|
||||
const dialog = wrapper.querySelector('.el-dialog');
|
||||
if (!dialog) return;
|
||||
|
||||
const applyStyle = () => {
|
||||
const dialogHeight = dialog.offsetHeight;
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
const topOffset = Math.max(0, (viewportHeight - dialogHeight) * 0.2);
|
||||
marginTop.value = `${topOffset}px`;
|
||||
};
|
||||
|
||||
applyStyle();
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
removeWrapperListeners();
|
||||
|
||||
if (styleObserver.value) {
|
||||
styleObserver.value.disconnect();
|
||||
styleObserver.value = null;
|
||||
}
|
||||
|
||||
if (resizeObserver.value) {
|
||||
resizeObserver.value.disconnect();
|
||||
resizeObserver.value = null;
|
||||
}
|
||||
|
||||
if (handleResize) {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
handleResize = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--<template>-->
|
||||
<!-- <safe-dialog-->
|
||||
<!-- <el-dialog-->
|
||||
<!-- class="x-dialog"-->
|
||||
<!-- :visible="sendBoopDialog.visible"-->
|
||||
<!-- :model-value="sendBoopDialog.visible"-->
|
||||
<!-- :title="t('dialog.boop_dialog.header')"-->
|
||||
<!-- width="450px"-->
|
||||
<!-- @close="closeDialog">-->
|
||||
@@ -20,7 +20,7 @@
|
||||
<!-- style="height: auto">-->
|
||||
<!-- <template v-if="friend.ref">-->
|
||||
<!-- <div class="avatar" :class="userStatusClass(friend.ref)">-->
|
||||
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="detail">-->
|
||||
<!-- <span-->
|
||||
@@ -42,7 +42,7 @@
|
||||
<!-- style="height: auto">-->
|
||||
<!-- <template v-if="friend.ref">-->
|
||||
<!-- <div class="avatar" :class="userStatusClass(friend.ref)">-->
|
||||
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="detail">-->
|
||||
<!-- <span-->
|
||||
@@ -64,7 +64,7 @@
|
||||
<!-- style="height: auto">-->
|
||||
<!-- <template v-if="friend.ref">-->
|
||||
<!-- <div class="avatar">-->
|
||||
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="detail">-->
|
||||
<!-- <span-->
|
||||
@@ -86,7 +86,7 @@
|
||||
<!-- style="height: auto">-->
|
||||
<!-- <template v-if="friend.ref">-->
|
||||
<!-- <div class="avatar">-->
|
||||
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="detail">-->
|
||||
<!-- <span-->
|
||||
@@ -135,7 +135,7 @@
|
||||
<!-- </template>-->
|
||||
<!-- <template v-else>-->
|
||||
<!-- <img-->
|
||||
<!-- v-lazy="image.versions[image.versions.length - 1].file.url"-->
|
||||
<!-- :src="image.versions[image.versions.length - 1].file.url"-->
|
||||
<!-- class="avatar"-->
|
||||
<!-- style="width: 200px; height: 200px" />-->
|
||||
<!-- </template>-->
|
||||
@@ -162,7 +162,7 @@
|
||||
<!-- t('dialog.boop_dialog.send')-->
|
||||
<!-- }}</el-button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </safe-dialog>-->
|
||||
<!-- </el-dialog>-->
|
||||
<!--</template>-->
|
||||
|
||||
<!--<script setup>-->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="bioDialog.visible"
|
||||
v-model="bioDialog.visible"
|
||||
:title="t('dialog.bio.header')"
|
||||
width="600px"
|
||||
append-to-body>
|
||||
@@ -9,7 +9,7 @@
|
||||
<el-input
|
||||
v-model="bioDialog.bio"
|
||||
type="textarea"
|
||||
size="mini"
|
||||
size="small"
|
||||
maxlength="512"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 5, maxRows: 20 }"
|
||||
@@ -23,16 +23,13 @@
|
||||
v-model="bioDialog.bioLinks[index]"
|
||||
size="small"
|
||||
style="margin-top: 5px">
|
||||
<img
|
||||
slot="prepend"
|
||||
:src="getFaviconUrl(link)"
|
||||
style="width: 16px; height: 16px; vertical-align: middle" />
|
||||
<el-button slot="append" icon="el-icon-delete" @click="bioDialog.bioLinks.splice(index, 1)" />
|
||||
<img :src="getFaviconUrl(link)" style="width: 16px; height: 16px; vertical-align: middle" />
|
||||
<el-button :icon="Delete" @click="bioDialog.bioLinks.splice(index, 1)" />
|
||||
</el-input>
|
||||
|
||||
<el-button
|
||||
:disabled="bioDialog.bioLinks.length >= 3"
|
||||
size="mini"
|
||||
size="small"
|
||||
style="margin-top: 5px"
|
||||
@click="bioDialog.bioLinks.push('')">
|
||||
{{ t('dialog.bio.add_link') }}
|
||||
@@ -44,18 +41,19 @@
|
||||
{{ t('dialog.bio.update') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { userRequest } from '../../../api';
|
||||
import { getFaviconUrl } from '../../../shared/utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { $message } = getCurrentInstance().proxy;
|
||||
|
||||
const props = defineProps({
|
||||
bioDialog: {
|
||||
type: Object,
|
||||
@@ -79,7 +77,7 @@
|
||||
})
|
||||
.then((args) => {
|
||||
D.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Bio updated',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="languageDialog.visible"
|
||||
v-model="languageDialog.visible"
|
||||
:title="t('dialog.language.header')"
|
||||
width="400px"
|
||||
append-to-body>
|
||||
@@ -22,7 +22,6 @@
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-select
|
||||
value=""
|
||||
:disabled="languageDialog.loading || (currentUser.$languages && currentUser.$languages.length === 3)"
|
||||
:placeholder="t('dialog.language.select_language')"
|
||||
style="margin-top: 14px"
|
||||
@@ -40,12 +39,12 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { userRequest } from '../../../api';
|
||||
import { languageClass } from '../../../shared/utils';
|
||||
import { useUserStore } from '../../../stores';
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="previousInstancesUserDialogRef"
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.previous_instances.header')"
|
||||
<el-dialog
|
||||
:z-index="previousInstancesUserDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
append-to-body>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 14px" v-text="previousInstancesUserDialog.userRef.displayName"></span>
|
||||
<el-input
|
||||
v-model="previousInstancesUserDialogTable.filters[0].value"
|
||||
:placeholder="$t('dialog.previous_instances.search_placeholder')"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
style="display: block; width: 150px"></el-input>
|
||||
</div>
|
||||
<data-tables v-loading="loading" v-bind="previousInstancesUserDialogTable" style="margin-top: 10px">
|
||||
<el-table-column :label="$t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||
<DataTable v-loading="loading" v-bind="previousInstancesUserDialogTable" style="margin-top: 10px">
|
||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.world')" prop="name" sortable>
|
||||
<el-table-column :label="t('table.previous_instances.world')" prop="name" sortable>
|
||||
<template #default="scope">
|
||||
<Location
|
||||
:location="scope.row.location"
|
||||
@@ -26,53 +26,61 @@
|
||||
:grouphint="scope.row.groupName" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.instance_creator')" prop="location" width="170">
|
||||
<el-table-column :label="t('table.previous_instances.instance_creator')" prop="location" width="170">
|
||||
<template #default="scope">
|
||||
<DisplayName :userid="scope.row.$location.userId" :location="scope.row.$location.tag" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.time')" prop="time" width="100" sortable>
|
||||
<el-table-column :label="t('table.previous_instances.time')" prop="time" width="100" sortable>
|
||||
<template #default="scope">
|
||||
<span v-text="scope.row.timer"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.action')" width="90" align="right">
|
||||
<el-table-column :label="t('table.previous_instances.action')" width="90" align="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-switch-button"
|
||||
size="mini"
|
||||
:icon="SwitchButton"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="showLaunchDialog(scope.row.location)"></el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-s-data"
|
||||
size="mini"
|
||||
:icon="DataLine"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="showPreviousInstancesInfoDialog(scope.row.location)"></el-button>
|
||||
<el-button
|
||||
v-if="shiftHeld"
|
||||
style="color: #f56c6c"
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="deleteGameLogUserInstance(scope.row)"></el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="deleteGameLogUserInstancePrompt(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</safe-dialog>
|
||||
</DataTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { SwitchButton, DataLine, Close } from '@element-plus/icons-vue';
|
||||
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, getCurrentInstance, nextTick, reactive, ref, watch } from 'vue';
|
||||
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { database } from '../../../service/database';
|
||||
import {
|
||||
adjustDialogZ,
|
||||
getNextDialogIndex,
|
||||
compareByCreatedAt,
|
||||
parseLocation,
|
||||
removeFromArray,
|
||||
@@ -93,22 +101,20 @@
|
||||
previousInstancesTable: {
|
||||
data: [],
|
||||
filters: [{ prop: 'displayName', value: '' }],
|
||||
tableProps: { stripe: true, size: 'mini', height: '400px' }
|
||||
tableProps: { stripe: true, size: 'small', height: '400px' }
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:previous-instances-user-dialog']);
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const loading = ref(false);
|
||||
const previousInstancesUserDialogTable = reactive({
|
||||
data: [],
|
||||
filters: [{ prop: 'worldName', value: '' }],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: { prop: 'created_at', order: 'descending' }
|
||||
},
|
||||
pageSize: 10,
|
||||
@@ -122,8 +128,9 @@
|
||||
const { showLaunchDialog } = useLaunchStore();
|
||||
const { showPreviousInstancesInfoDialog } = useInstanceStore();
|
||||
const { shiftHeld } = storeToRefs(useUiStore());
|
||||
const { t } = useI18n();
|
||||
|
||||
const previousInstancesUserDialogRef = ref(null);
|
||||
const previousInstancesUserDialogIndex = ref(2000);
|
||||
|
||||
const isVisible = computed({
|
||||
get: () => props.previousInstancesUserDialog.visible,
|
||||
@@ -154,7 +161,7 @@
|
||||
() => {
|
||||
if (props.previousInstancesUserDialog.visible) {
|
||||
nextTick(() => {
|
||||
adjustDialogZ(previousInstancesUserDialogRef.value.$el);
|
||||
previousInstancesUserDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
refreshPreviousInstancesUserTable();
|
||||
}
|
||||
@@ -172,13 +179,20 @@
|
||||
}
|
||||
|
||||
function deleteGameLogUserInstancePrompt(row) {
|
||||
proxy.$confirm('Continue? Delete User From GameLog Instance', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Delete User From GameLog Instance', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
if (action === 'confirm') deleteGameLogUserInstance(row);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.button-pd-0 {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="pronounsDialog.visible"
|
||||
v-model="pronounsDialog.visible"
|
||||
:title="t('dialog.pronouns.header')"
|
||||
width="600px"
|
||||
append-to-body>
|
||||
@@ -9,7 +9,7 @@
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="pronounsDialog.pronouns"
|
||||
size="mini"
|
||||
size="small"
|
||||
maxlength="32"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
@@ -21,18 +21,15 @@
|
||||
{{ t('dialog.pronouns.update') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { userRequest } from '../../../api';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { $message } = proxy;
|
||||
|
||||
const props = defineProps({
|
||||
pronounsDialog: {
|
||||
type: Object,
|
||||
@@ -55,7 +52,7 @@
|
||||
})
|
||||
.then((args) => {
|
||||
D.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Pronouns updated',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="sendInviteRequestDialogVisible"
|
||||
:model-value="sendInviteRequestDialogVisible"
|
||||
@update:model-value="$emit('update:sendInviteRequestDialogVisible', $event)"
|
||||
:title="t('dialog.invite_request_message.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@@ -10,20 +11,20 @@
|
||||
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||
</template>
|
||||
|
||||
<data-tables
|
||||
<DataTable
|
||||
v-bind="inviteRequestMessageTable"
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
@row-click="showSendInviteConfirmDialog">
|
||||
<el-table-column
|
||||
:label="t('table.profile.invite_messages.slot')"
|
||||
prop="slot"
|
||||
sortable="custom"
|
||||
:sortable="true"
|
||||
width="70"></el-table-column>
|
||||
<el-table-column :label="t('table.profile.invite_messages.message')" prop="message"></el-table-column>
|
||||
<el-table-column
|
||||
:label="t('table.profile.invite_messages.cool_down')"
|
||||
prop="updatedAt"
|
||||
sortable="custom"
|
||||
:sortable="true"
|
||||
width="110"
|
||||
align="right">
|
||||
<template #default="scope">
|
||||
@@ -34,38 +35,38 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:icon="Edit"
|
||||
size="small"
|
||||
@click.stop="showEditAndSendInviteDialog(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="small" @click="cancelSendInviteRequest">{{
|
||||
t('dialog.invite_request_message.cancel')
|
||||
}}</el-button>
|
||||
<el-button type="small" @click="refreshInviteMessageTableData('request')">{{
|
||||
<el-button @click="cancelSendInviteRequest">{{ t('dialog.invite_request_message.cancel') }}</el-button>
|
||||
<el-button @click="refreshInviteMessageTableData('request')">{{
|
||||
t('dialog.invite_request_message.refresh')
|
||||
}}</el-button>
|
||||
</template>
|
||||
<SendInviteConfirmDialog
|
||||
:visible.sync="isSendInviteConfirmDialogVisible"
|
||||
v-model="isSendInviteConfirmDialogVisible"
|
||||
:send-invite-dialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
<EditAndSendInviteDialog
|
||||
:edit-and-send-invite-dialog.sync="editAndSendInviteDialog"
|
||||
:edit-and-send-invite-dialog="editAndSendInviteDialog"
|
||||
:send-invite-dialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Edit } from '@element-plus/icons-vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useGalleryStore, useInviteStore, useUserStore } from '../../../stores';
|
||||
import EditAndSendInviteDialog from '../InviteDialog/EditAndSendInviteDialog.vue';
|
||||
import SendInviteConfirmDialog from '../InviteDialog/SendInviteConfirmDialog.vue';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="socialStatusDialog.visible"
|
||||
v-model="socialStatusDialog.visible"
|
||||
:title="t('dialog.social_status.header')"
|
||||
append-to-body
|
||||
width="400px">
|
||||
@@ -11,13 +11,13 @@
|
||||
<template #title>
|
||||
<span style="font-size: 16px">{{ t('dialog.social_status.history') }}</span>
|
||||
</template>
|
||||
<data-tables
|
||||
<DataTable
|
||||
v-bind="socialStatusHistoryTable"
|
||||
style="cursor: pointer"
|
||||
@row-click="setSocialStatusFromHistory">
|
||||
<el-table-column :label="t('table.social_status.no')" prop="no" width="50"></el-table-column>
|
||||
<el-table-column :label="t('table.social_status.status')" prop="status"></el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
@@ -53,19 +53,18 @@
|
||||
{{ t('dialog.social_status.update') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { userRequest } from '../../../api';
|
||||
import { useUserStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { $message } = getCurrentInstance().proxy;
|
||||
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
|
||||
const props = defineProps({
|
||||
@@ -103,7 +102,7 @@
|
||||
})
|
||||
.then((args) => {
|
||||
D.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Status updated',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="VRCXUpdateDialogRef"
|
||||
<el-dialog
|
||||
:z-index="VRCXUpdateDialogIndex"
|
||||
class="x-dialog"
|
||||
:visible.sync="VRCXUpdateDialog.visible"
|
||||
v-model="VRCXUpdateDialog.visible"
|
||||
:title="t('dialog.vrcx_updater.header')"
|
||||
append-to-body
|
||||
width="400px">
|
||||
@@ -59,15 +59,15 @@
|
||||
{{ t('dialog.vrcx_updater.install') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { branches } from '../../shared/constants';
|
||||
import { adjustDialogZ } from '../../shared/utils';
|
||||
import { getNextDialogIndex } from '../../shared/utils';
|
||||
import { useVRCXUpdaterStore } from '../../stores';
|
||||
|
||||
const VRCXUpdaterStore = useVRCXUpdaterStore();
|
||||
@@ -85,14 +85,14 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const VRCXUpdateDialogRef = ref(null);
|
||||
const VRCXUpdateDialogIndex = ref(2000);
|
||||
|
||||
watch(
|
||||
() => VRCXUpdateDialog,
|
||||
(newVal) => {
|
||||
if (newVal.value.visible) {
|
||||
nextTick(() => {
|
||||
adjustDialogZ(VRCXUpdateDialogRef.value.$el);
|
||||
VRCXUpdateDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="changeWorldImageDialogVisible"
|
||||
:model-value="changeWorldImageDialogVisible"
|
||||
:title="t('dialog.change_content_image.world')"
|
||||
width="850px"
|
||||
append-to-body
|
||||
@@ -16,106 +16,60 @@
|
||||
<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="uploadWorldImage">{{
|
||||
t('dialog.change_content_image.upload')
|
||||
}}</el-button>
|
||||
<!-- el-button(type="default" size="small" @click="deleteWorldImage" icon="el-icon-delete") Delete Latest Image-->
|
||||
<el-button type="default" size="small" :icon="Upload" @click="uploadWorldImage">
|
||||
{{ 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="setWorldImage(image)">
|
||||
<img v-lazy="image.file.url" class="image" />
|
||||
</div>
|
||||
<div class="x-change-image-item">
|
||||
<img :src="previousImageUrl" 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 { useGalleryStore, useWorldStore } from '../../../stores';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { worldRequest } from '../../../api';
|
||||
import { useWorldStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const { worldDialog } = storeToRefs(useWorldStore());
|
||||
const { previousImagesTable } = storeToRefs(useGalleryStore());
|
||||
const { applyWorld } = useWorldStore();
|
||||
|
||||
const props = defineProps({
|
||||
changeWorldImageDialogVisible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
required: true
|
||||
},
|
||||
previousImagesFileId: {
|
||||
previousImageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:changeWorldImageDialogVisible', 'refresh']);
|
||||
|
||||
const changeWorldImageDialogLoading = ref(false);
|
||||
const worldImage = ref({
|
||||
base64File: '',
|
||||
fileMd5: '',
|
||||
base64SignatureFile: '',
|
||||
signatureMd5: '',
|
||||
fileId: '',
|
||||
avatarId: '',
|
||||
worldId: ''
|
||||
});
|
||||
|
||||
function uploadWorldImage() {
|
||||
document.getElementById('WorldImageUploadButton').click();
|
||||
}
|
||||
const emit = defineEmits(['update:changeWorldImageDialogVisible', 'update:previousImageUrl']);
|
||||
|
||||
function closeDialog() {
|
||||
emit('update:changeWorldImageDialogVisible', false);
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
emit('refresh', 'Change');
|
||||
}
|
||||
|
||||
async function resizeImageToFitLimits(file) {
|
||||
const response = await AppApi.ResizeImageToFitLimits(file);
|
||||
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 onFileChangeWorldImage(e) {
|
||||
const clearFile = function () {
|
||||
const fileInput = /** @type {HTMLInputElement} */ (document.querySelector('#WorldImageUploadButton'));
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
const fileInput = /** @type{HTMLInputElement} */ (document.querySelector('#WorldImageUploadButton'));
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
@@ -125,9 +79,11 @@
|
||||
clearFile();
|
||||
return;
|
||||
}
|
||||
|
||||
// validate file
|
||||
if (files[0].size >= 100000000) {
|
||||
// 100MB
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.too_large'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -135,223 +91,57 @@
|
||||
return;
|
||||
}
|
||||
if (!files[0].type.match(/image.*/)) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.not_image'),
|
||||
type: 'error'
|
||||
});
|
||||
clearFile();
|
||||
return;
|
||||
}
|
||||
changeWorldImageDialogLoading.value = true;
|
||||
|
||||
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 worldId = worldDialog.value.id;
|
||||
const { imageUrl } = worldDialog.value.ref;
|
||||
const fileId = extractFileId(imageUrl);
|
||||
if (!fileId) {
|
||||
$message({
|
||||
message: t('message.world.image_invalid'),
|
||||
type: 'error'
|
||||
});
|
||||
clearFile();
|
||||
return;
|
||||
}
|
||||
worldImage.value = {
|
||||
base64File,
|
||||
fileMd5,
|
||||
base64SignatureFile,
|
||||
signatureMd5,
|
||||
fileId,
|
||||
worldId,
|
||||
...worldImage.value
|
||||
};
|
||||
const params = {
|
||||
fileMd5,
|
||||
fileSizeInBytes,
|
||||
signatureMd5,
|
||||
signatureSizeInBytes
|
||||
};
|
||||
|
||||
// Upload chaining
|
||||
await initiateUpload(params, fileId);
|
||||
await initiateUpload(base64File);
|
||||
} catch (error) {
|
||||
console.error('World image upload process failed:', error);
|
||||
} finally {
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
clearFile();
|
||||
}
|
||||
};
|
||||
|
||||
changeWorldImageDialogLoading.value = true;
|
||||
r.readAsBinaryString(files[0]);
|
||||
}
|
||||
|
||||
// ------------ Upload Process Start ------------
|
||||
|
||||
async function initiateUpload(params, fileId) {
|
||||
const res = await imageRequest.uploadWorldImage(params, fileId);
|
||||
return worldImageInit(res);
|
||||
}
|
||||
|
||||
async function worldImageInit(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.uploadWorldImageFileStart(params);
|
||||
return worldImageFileStart(res);
|
||||
}
|
||||
|
||||
async function worldImageFileStart(args) {
|
||||
const { url } = args.json;
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
url,
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
return uploadWorldImageFileAWS(params);
|
||||
}
|
||||
|
||||
async function uploadWorldImageFileAWS(params) {
|
||||
const json = await webApiService.execute({
|
||||
url: params.url,
|
||||
uploadFilePUT: true,
|
||||
fileData: worldImage.value.base64File,
|
||||
fileMIME: 'image/png',
|
||||
headers: {
|
||||
'Content-MD5': worldImage.value.fileMd5
|
||||
}
|
||||
});
|
||||
|
||||
if (json.status !== 200) {
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
$throw(json.status, 'World image upload failed', params.url);
|
||||
}
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return worldImageFileAWS(args);
|
||||
}
|
||||
|
||||
async function worldImageFileAWS(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadWorldImageFileFinish(params);
|
||||
return worldImageFileFinish(res);
|
||||
}
|
||||
|
||||
async function worldImageFileFinish(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadWorldImageSigStart(params);
|
||||
return worldImageSigStart(res);
|
||||
}
|
||||
|
||||
async function worldImageSigStart(args) {
|
||||
const { url } = args.json;
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
url,
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
return uploadWorldImageSigAWS(params);
|
||||
}
|
||||
|
||||
async function uploadWorldImageSigAWS(params) {
|
||||
const json = await webApiService.execute({
|
||||
url: params.url,
|
||||
uploadFilePUT: true,
|
||||
fileData: worldImage.value.base64SignatureFile,
|
||||
fileMIME: 'application/x-rsync-signature',
|
||||
headers: {
|
||||
'Content-MD5': worldImage.value.signatureMd5
|
||||
}
|
||||
});
|
||||
|
||||
if (json.status !== 200) {
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
$throw(json.status, 'World image upload failed', params.url);
|
||||
}
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return worldImageSigAWS(args);
|
||||
}
|
||||
|
||||
async function worldImageSigAWS(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadWorldImageSigFinish(params);
|
||||
return worldImageSigFinish(res);
|
||||
}
|
||||
async function worldImageSigFinish(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const parmas = {
|
||||
id: worldImage.value.worldId,
|
||||
imageUrl: `${AppGlobal.endpointDomain}/file/${fileId}/${fileVersion}/file`
|
||||
};
|
||||
const res = await imageRequest.setWorldImage(parmas);
|
||||
return worldImageSet(res);
|
||||
}
|
||||
|
||||
function worldImageSet(args) {
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
if (args.json.imageUrl === args.params.imageUrl) {
|
||||
$message({
|
||||
message: t('message.world.image_changed'),
|
||||
type: 'success'
|
||||
});
|
||||
refresh();
|
||||
} else {
|
||||
$throw(0, 'World image change failed', args.params.imageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------ Upload Process End ------------
|
||||
|
||||
function setWorldImage(image) {
|
||||
changeWorldImageDialogLoading.value = true;
|
||||
const parmas = {
|
||||
async function initiateUpload(base64File) {
|
||||
const args = await worldRequest.uploadWorldImage(base64File);
|
||||
const fileUrl = args.json.versions[args.json.versions.length - 1].file.url;
|
||||
const worldArgs = await worldRequest.saveWorld({
|
||||
id: worldDialog.value.id,
|
||||
imageUrl: `${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file`
|
||||
};
|
||||
imageRequest
|
||||
.setWorldImage(parmas)
|
||||
.then((args) => worldImageSet(args))
|
||||
.finally(() => {
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
closeDialog();
|
||||
});
|
||||
imageUrl: fileUrl
|
||||
});
|
||||
const ref = applyWorld(worldArgs.json);
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
emit('update:previousImageUrl', ref.imageUrl);
|
||||
ElMessage({
|
||||
message: t('message.world.image_changed'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
// closeDialog();
|
||||
}
|
||||
|
||||
function compareCurrentImage(image) {
|
||||
if (
|
||||
`${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file` ===
|
||||
worldDialog.value.ref.imageUrl
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
function uploadWorldImage() {
|
||||
document.getElementById('WorldImageUploadButton').click();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.img-size {
|
||||
width: 500px;
|
||||
height: 375px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
:visible.sync="isVisible"
|
||||
<el-dialog
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.set_world_tags.header')"
|
||||
width="400px"
|
||||
destroy-on-close
|
||||
@@ -20,7 +20,7 @@
|
||||
<el-input
|
||||
v-model="setWorldTagsDialog.authorTags"
|
||||
type="textarea"
|
||||
size="mini"
|
||||
size="small"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
placeholder=""
|
||||
@@ -81,12 +81,13 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { worldRequest } from '../../../api';
|
||||
import { useWorldStore } from '../../../stores';
|
||||
|
||||
@@ -115,8 +116,6 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const setWorldTagsDialog = ref({
|
||||
authorTags: '',
|
||||
contentTags: '',
|
||||
@@ -296,7 +295,7 @@
|
||||
tags
|
||||
})
|
||||
.then((args) => {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Tags updated',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
:visible.sync="isVisible"
|
||||
<el-dialog
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.allowed_video_player_domains.header')"
|
||||
width="600px"
|
||||
destroy-on-close
|
||||
@@ -12,9 +12,9 @@
|
||||
v-model="urlList[index]"
|
||||
size="small"
|
||||
style="margin-top: 5px">
|
||||
<el-button slot="append" icon="el-icon-delete" @click="urlList.splice(index, 1)"></el-button>
|
||||
<el-button :icon="Delete" @click="urlList.splice(index, 1)"></el-button>
|
||||
</el-input>
|
||||
<el-button size="mini" style="margin-top: 5px" @click="urlList.push('')">
|
||||
<el-button size="small" style="margin-top: 5px" @click="urlList.push('')">
|
||||
{{ t('dialog.allowed_video_player_domains.add_domain') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -27,12 +27,16 @@
|
||||
{{ t('dialog.allowed_video_player_domains.save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { worldRequest } from '../../../api';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -44,8 +48,6 @@
|
||||
|
||||
const emit = defineEmits(['update:worldAllowedDomainsDialog']);
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const urlList = ref([]);
|
||||
@@ -79,7 +81,7 @@
|
||||
urlList: urlList.value
|
||||
})
|
||||
.then((args) => {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Allowed Video Player Domains updated',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,18 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, user-scalable=no"
|
||||
/>
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache" />
|
||||
|
||||
<title>VRCX</title>
|
||||
|
||||
<!-- <link rel="stylesheet" href="app.css" /> -->
|
||||
|
||||
<link rel="preconnect" href="https://api.vrchat.cloud" />
|
||||
<link rel="preconnect" href="https://files.vrchat.cloud" />
|
||||
<link rel="preconnect" href="https://d348imysud55la.cloudfront.net" />
|
||||
@@ -15,5 +20,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"language": "Čeština (CZ)",
|
||||
"language": "Čeština (cs)",
|
||||
"translator": "-",
|
||||
"nav_tooltip": {
|
||||
"feed": "Zdroj",
|
||||
@@ -617,7 +617,7 @@
|
||||
"launch_commands": {
|
||||
"header": "Launch Commands (Deep Links)",
|
||||
"docs": "Launch Command Docs",
|
||||
"show_confirmation_on_switch_avatar_enable": "Show confirmation diaglog before switching avatars",
|
||||
"show_confirmation_on_switch_avatar_enable": "Show confirmation dialog before switching avatars",
|
||||
"show_confirmation_on_switch_avatar_tooltip": "When disabled VRCX will not come to the front when switching avatars and ask for confirmation",
|
||||
"website_userscript": "VRC Website Userscript"
|
||||
},
|
||||
@@ -653,6 +653,7 @@
|
||||
"avatar_name_cache": "Avatar Name cache:",
|
||||
"instance_cache": "Instance cache:",
|
||||
"clear_cache": "Clear Cache",
|
||||
"refresh_cache": "Refresh Cache",
|
||||
"auto_clear_cache": "Auto Clear Cache",
|
||||
"show_console": "Show Console"
|
||||
},
|
||||
@@ -766,7 +767,6 @@
|
||||
"show_avatar_author": "Show Avatar Author",
|
||||
"show_fallback_avatar": "Show Fallback Avatar Details",
|
||||
"show_previous_instances": "Show Previous Instances",
|
||||
"show_previous_images": "Show Previous Images",
|
||||
"moderation_block": "Block",
|
||||
"moderation_unblock": "Unblock",
|
||||
"moderation_mute": "Mute",
|
||||
@@ -915,7 +915,6 @@
|
||||
"make_home": "Make Home",
|
||||
"reset_home": "Reset Home",
|
||||
"show_previous_instances": "Show Previous Instances",
|
||||
"show_previous_images": "Show Previous Images",
|
||||
"rename": "Rename",
|
||||
"change_description": "Change Description",
|
||||
"change_capacity": "Change Capacity",
|
||||
@@ -1002,7 +1001,6 @@
|
||||
"select_fallback": "Select Fallback Avatar",
|
||||
"block": "Block Avatar",
|
||||
"unblock": "Unblock Avatar",
|
||||
"show_previous_images": "Show Previous Images",
|
||||
"make_public": "Make Public",
|
||||
"make_private": "Make Private",
|
||||
"rename": "Rename",
|
||||
@@ -1118,7 +1116,6 @@
|
||||
"load_more": "Load more...",
|
||||
"sort_by": "Sort By:",
|
||||
"sorting": {
|
||||
"user_id": "User ID (Ascending)",
|
||||
"joined_at_asc": "Joined At (Ascending)",
|
||||
"joined_at_desc": "Joined At (Descending)"
|
||||
},
|
||||
@@ -1227,7 +1224,8 @@
|
||||
"group": "Group",
|
||||
"legacy": "Legacy",
|
||||
"roles": "Roles",
|
||||
"open_ingame": "Open in-game"
|
||||
"open_ingame": "Open in-game",
|
||||
"display_name": "Display Name (VRC+)"
|
||||
},
|
||||
"launch_options": {
|
||||
"header": "VRChat Launch Options",
|
||||
@@ -1518,12 +1516,8 @@
|
||||
"avatar": "Change Avatar Image",
|
||||
"world": "Change World Image",
|
||||
"description": "Recommended image size: 1200x900px (4:3)",
|
||||
"refresh": "Refresh",
|
||||
"upload": "Upload Image"
|
||||
},
|
||||
"previous_images": {
|
||||
"header": "Previous Images"
|
||||
},
|
||||
"previous_instances": {
|
||||
"header": "Previous Instances",
|
||||
"info": "Previous Instance Info",
|
||||
@@ -1663,6 +1657,10 @@
|
||||
"choose_group_placeholder": "Choose Group",
|
||||
"groups_with_moderation_permission": "Groups with Moderation Permission",
|
||||
"moderation_tools": "Moderation Tools"
|
||||
},
|
||||
"fullscreen_image": {
|
||||
"download_and_save_image": "Download and save image",
|
||||
"copy_image_to_clipboard": "Copy image to clipboard"
|
||||
}
|
||||
},
|
||||
"confirm": {
|
||||
|
||||
@@ -1,73 +1,44 @@
|
||||
import en from './en/en.json' assert { type: 'JSON' };
|
||||
import elements_en from 'element-ui/lib/locale/lang/en';
|
||||
|
||||
import es from './es/en.json' assert { type: 'JSON' };
|
||||
import elements_es from 'element-ui/lib/locale/lang/es';
|
||||
|
||||
import fr from './fr/en.json' assert { type: 'JSON' };
|
||||
import elements_fr from 'element-ui/lib/locale/lang/fr';
|
||||
|
||||
// import hu from './hu/en.json' assert { type: 'JSON' };
|
||||
// import elements_hu from 'element-ui/lib/locale/lang/hu';
|
||||
import hu from './hu/en.json' assert { type: 'JSON' };
|
||||
|
||||
import ja from './ja/en.json' assert { type: 'JSON' };
|
||||
import elements_ja from 'element-ui/lib/locale/lang/ja';
|
||||
|
||||
import ko from './ko/en.json' assert { type: 'JSON' };
|
||||
import elements_ko from 'element-ui/lib/locale/lang/ko';
|
||||
|
||||
import pl from './pl/en.json' assert { type: 'JSON' };
|
||||
import elements_pl from 'element-ui/lib/locale/lang/pl';
|
||||
|
||||
import pt from './pt/en.json' assert { type: 'JSON' };
|
||||
import elements_pt from 'element-ui/lib/locale/lang/pt';
|
||||
|
||||
import cz from './cz/en.json' assert { type: 'JSON' };
|
||||
import elements_cz from 'element-ui/lib/locale/lang/cs-CZ';
|
||||
import cs from './cs/en.json' assert { type: 'JSON' };
|
||||
|
||||
import ru_RU from './ru/en.json' assert { type: 'JSON' };
|
||||
import elements_ru from 'element-ui/lib/locale/lang/ru-RU';
|
||||
import ru from './ru/en.json' assert { type: 'JSON' };
|
||||
|
||||
import vi from './vi/en.json' assert { type: 'JSON' };
|
||||
import elements_vi from 'element-ui/lib/locale/lang/vi';
|
||||
|
||||
import zh_CN from './zh-CN/en.json' assert { type: 'JSON' };
|
||||
import elements_zh_CN from 'element-ui/lib/locale/lang/zh-CN';
|
||||
|
||||
import zh_TW from './zh-TW/en.json' assert { type: 'JSON' };
|
||||
import elements_zh_TW from 'element-ui/lib/locale/lang/zh-TW';
|
||||
|
||||
import th from './th/en.json' assert { type: 'JSON' };
|
||||
import elements_th from 'element-ui/lib/locale/lang/th';
|
||||
|
||||
const localized_en = { ...en, ...elements_en };
|
||||
const localized_es = { ...es, ...elements_es };
|
||||
const localized_fr = { ...fr, ...elements_fr };
|
||||
// const localized_hu = { ...hu, ...elements_hu };
|
||||
const localized_ja = { ...ja, ...elements_ja };
|
||||
const localized_ko = { ...ko, ...elements_ko };
|
||||
const localized_pl = { ...pl, ...elements_pl };
|
||||
const localized_pt = { ...pt, ...elements_pt };
|
||||
const localized_cz = { ...cz, ...elements_cz };
|
||||
const localized_ru = { ...ru_RU, ...elements_ru };
|
||||
const localized_vi = { ...vi, ...elements_vi };
|
||||
const localized_zh_CN = { ...zh_CN, ...elements_zh_CN };
|
||||
const localized_zh_TW = { ...zh_TW, ...elements_zh_TW };
|
||||
const localized_th = { ...th, ...elements_th };
|
||||
|
||||
export {
|
||||
localized_en as en,
|
||||
localized_es as es,
|
||||
localized_fr as fr,
|
||||
// localized_hu as hu,
|
||||
localized_ja as ja_JP,
|
||||
localized_ko as ko,
|
||||
localized_pl as pl,
|
||||
localized_pt as pt,
|
||||
localized_cz as cz,
|
||||
localized_ru as ru_RU,
|
||||
localized_vi as vi,
|
||||
localized_zh_CN as zh_CN,
|
||||
localized_zh_TW as zh_TW,
|
||||
localized_th as th
|
||||
en,
|
||||
es,
|
||||
fr,
|
||||
hu,
|
||||
ja,
|
||||
ko,
|
||||
pl,
|
||||
pt,
|
||||
cs,
|
||||
ru,
|
||||
vi,
|
||||
zh_CN,
|
||||
zh_TW,
|
||||
th
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"language": "简体中文 (zh_CN)",
|
||||
"language": "简体中文 (zh-CN)",
|
||||
"translator": "flower_elf,Yingxue,Map1en",
|
||||
"nav_tooltip": {
|
||||
"feed": "好友动态",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"language": "繁體中文 (zh_TW)",
|
||||
"language": "繁體中文 (zh-TW)",
|
||||
"translator": "Kamiya,XoF_eLtTiL",
|
||||
"nav_tooltip": {
|
||||
"feed": "動態",
|
||||
|
||||
+15
-14
@@ -1,7 +1,5 @@
|
||||
import Vue from 'vue';
|
||||
import AvatarInfo from '../components/AvatarInfo.vue';
|
||||
import CountdownTimer from '../components/CountdownTimer.vue';
|
||||
import SafeDialog from '../components/dialogs/SafeDialog.vue';
|
||||
import DisplayName from '../components/DisplayName.vue';
|
||||
import InstanceInfo from '../components/InstanceInfo.vue';
|
||||
import InviteYourself from '../components/InviteYourself.vue';
|
||||
@@ -11,16 +9,19 @@ import Location from '../components/Location.vue';
|
||||
import LocationWorld from '../components/LocationWorld.vue';
|
||||
import SimpleSwitch from '../components/SimpleSwitch.vue';
|
||||
import Timer from '../components/Timer.vue';
|
||||
import DataTable from '../components/DataTable.vue';
|
||||
|
||||
Vue.component('SafeDialog', SafeDialog);
|
||||
Vue.component('SimpleSwitch', SimpleSwitch);
|
||||
Vue.component('Location', Location);
|
||||
Vue.component('Timer', Timer);
|
||||
Vue.component('InstanceInfo', InstanceInfo);
|
||||
Vue.component('LastJoin', LastJoin);
|
||||
Vue.component('CountdownTimer', CountdownTimer);
|
||||
Vue.component('AvatarInfo', AvatarInfo);
|
||||
Vue.component('DisplayName', DisplayName);
|
||||
Vue.component('InviteYourself', InviteYourself);
|
||||
Vue.component('Launch', Launch);
|
||||
Vue.component('LocationWorld', LocationWorld);
|
||||
export default function registerComponents(app) {
|
||||
app.component('SimpleSwitch', SimpleSwitch);
|
||||
app.component('Location', Location);
|
||||
app.component('Timer', Timer);
|
||||
app.component('InstanceInfo', InstanceInfo);
|
||||
app.component('LastJoin', LastJoin);
|
||||
app.component('CountdownTimer', CountdownTimer);
|
||||
app.component('AvatarInfo', AvatarInfo);
|
||||
app.component('DisplayName', DisplayName);
|
||||
app.component('InviteYourself', InviteYourself);
|
||||
app.component('Launch', Launch);
|
||||
app.component('LocationWorld', LocationWorld);
|
||||
app.component('DataTable', DataTable);
|
||||
}
|
||||
|
||||
+16
-26
@@ -1,30 +1,20 @@
|
||||
import ElementUI from 'element-ui';
|
||||
import Vue from 'vue';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import { createI18n } from 'vue-i18n-bridge';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import * as localizedStrings from '../localization/localizedStrings';
|
||||
|
||||
// i18n: execution order matters here
|
||||
Vue.use(VueI18n, { bridge: true });
|
||||
const i18n = createI18n(
|
||||
{
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages: localizedStrings,
|
||||
legacy: false,
|
||||
globalInjection: true,
|
||||
missingWarn: false,
|
||||
warnHtmlMessage: false,
|
||||
fallbackWarn: false
|
||||
},
|
||||
VueI18n
|
||||
);
|
||||
|
||||
Vue.use(i18n);
|
||||
Vue.use(ElementUI, {
|
||||
i18n: (key, value) => i18n.global.t(key, value)
|
||||
const i18n = createI18n({
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages: Object.fromEntries(
|
||||
Object.entries(localizedStrings).map(([key, value]) => [
|
||||
key.replaceAll('_', '-'),
|
||||
value
|
||||
])
|
||||
),
|
||||
legacy: false,
|
||||
globalInjection: false,
|
||||
missingWarn: false,
|
||||
warnHtmlMessage: false,
|
||||
fallbackWarn: false
|
||||
});
|
||||
|
||||
const t = i18n.global.t;
|
||||
|
||||
export { i18n, t };
|
||||
export { i18n };
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import './ipc';
|
||||
import './dayjs';
|
||||
import './components';
|
||||
|
||||
export { t, i18n } from './i18n';
|
||||
@@ -1,6 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
import { reactive } from 'vue';
|
||||
|
||||
const AppGlobal = Vue.observable({
|
||||
const AppDebug = reactive({
|
||||
debug: false,
|
||||
debugWebSocket: false,
|
||||
debugUserDiff: false,
|
||||
@@ -16,6 +16,6 @@ const AppGlobal = Vue.observable({
|
||||
websocketDomainVrchat: 'wss://pipeline.vrchat.cloud'
|
||||
});
|
||||
|
||||
window.__APP_GLOBALS__ = AppGlobal;
|
||||
window.$debug = AppDebug;
|
||||
|
||||
export { AppGlobal };
|
||||
export { AppDebug };
|
||||
|
||||
+30
-14
@@ -1,6 +1,6 @@
|
||||
import Noty from 'noty';
|
||||
import { $app } from '../app.js';
|
||||
import { t } from '../plugin';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import { i18n } from '../plugin/i18n';
|
||||
import { statusCodes } from '../shared/constants/api.js';
|
||||
import { escapeTag } from '../shared/utils';
|
||||
import {
|
||||
@@ -10,13 +10,15 @@ import {
|
||||
useUpdateLoopStore,
|
||||
useUserStore
|
||||
} from '../stores';
|
||||
import { AppGlobal } from './appConfig.js';
|
||||
import { AppDebug } from './appConfig.js';
|
||||
import webApiService from './webapi.js';
|
||||
import { watchState } from './watchState';
|
||||
|
||||
const pendingGetRequests = new Map();
|
||||
export let failedGetRequests = new Map();
|
||||
|
||||
const t = i18n.global.t;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {string} endpoint
|
||||
@@ -38,7 +40,7 @@ export function request(endpoint, options) {
|
||||
}
|
||||
let req;
|
||||
const init = {
|
||||
url: `${AppGlobal.endpointDomain}/${endpoint}`,
|
||||
url: `${AppDebug.endpointDomain}/${endpoint}`,
|
||||
method: 'GET',
|
||||
...options
|
||||
};
|
||||
@@ -50,7 +52,7 @@ export function request(endpoint, options) {
|
||||
if (lastRun >= Date.now() - 900000) {
|
||||
// 15mins
|
||||
$throw(
|
||||
0,
|
||||
-1,
|
||||
t('api.error.message.403_404_bailing_request'),
|
||||
endpoint
|
||||
);
|
||||
@@ -102,14 +104,14 @@ export function request(endpoint, options) {
|
||||
throw `API request blocked while logged out: ${endpoint}`;
|
||||
}
|
||||
if (!response.data) {
|
||||
if (AppGlobal.debugWebRequests) {
|
||||
if (AppDebug.debugWebRequests) {
|
||||
console.log(init, 'no data', response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
try {
|
||||
response.data = JSON.parse(response.data);
|
||||
if (AppGlobal.debugWebRequests) {
|
||||
if (AppDebug.debugWebRequests) {
|
||||
console.log(init, 'parsed data', response.data);
|
||||
}
|
||||
return response;
|
||||
@@ -179,7 +181,7 @@ export function request(endpoint, options) {
|
||||
}
|
||||
}
|
||||
if (status === 403 && endpoint === 'config') {
|
||||
$app.$alert(
|
||||
ElMessageBox.alert(
|
||||
t('api.error.message.vpn_in_use'),
|
||||
`403 ${t('api.error.message.login_error')}`
|
||||
);
|
||||
@@ -191,7 +193,7 @@ export function request(endpoint, options) {
|
||||
status === 404 &&
|
||||
endpoint.startsWith('avatars/')
|
||||
) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: t('message.api_handler.avatar_private_or_deleted'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -276,15 +278,29 @@ export function $throw(code, error, endpoint) {
|
||||
);
|
||||
}
|
||||
const text = message.map((s) => escapeTag(s)).join('<br>');
|
||||
if (text.length) {
|
||||
if (AppGlobal.errorNoty) {
|
||||
AppGlobal.errorNoty.close();
|
||||
let ignoreError = false;
|
||||
if (
|
||||
(code === 404 || code === -1) &&
|
||||
endpoint.split('/').length === 2 &&
|
||||
(endpoint.startsWith('users/') ||
|
||||
endpoint.startsWith('worlds/') ||
|
||||
endpoint.startsWith('avatars/') ||
|
||||
endpoint.startsWith('file/'))
|
||||
) {
|
||||
ignoreError = true;
|
||||
}
|
||||
if (endpoint.startsWith('analysis/')) {
|
||||
ignoreError = true;
|
||||
}
|
||||
if (text.length && !ignoreError) {
|
||||
if (AppDebug.errorNoty) {
|
||||
AppDebug.errorNoty.close();
|
||||
}
|
||||
AppGlobal.errorNoty = new Noty({
|
||||
AppDebug.errorNoty = new Noty({
|
||||
type: 'error',
|
||||
text
|
||||
});
|
||||
AppGlobal.errorNoty.show();
|
||||
AppDebug.errorNoty.show();
|
||||
}
|
||||
const e = new Error(text);
|
||||
e.status = code;
|
||||
|
||||
+11
-11
@@ -13,7 +13,7 @@ import {
|
||||
useUiStore,
|
||||
useUserStore
|
||||
} from '../stores';
|
||||
import { AppGlobal } from './appConfig';
|
||||
import { AppDebug } from './appConfig';
|
||||
import { request } from './request';
|
||||
import { watchState } from './watchState';
|
||||
|
||||
@@ -49,9 +49,9 @@ function connectWebSocket(token) {
|
||||
if (webSocket !== null) {
|
||||
return;
|
||||
}
|
||||
const socket = new WebSocket(`${AppGlobal.websocketDomain}/?auth=${token}`);
|
||||
const socket = new WebSocket(`${AppDebug.websocketDomain}/?auth=${token}`);
|
||||
socket.onopen = () => {
|
||||
if (AppGlobal.debugWebSocket) {
|
||||
if (AppDebug.debugWebSocket) {
|
||||
console.log('WebSocket connected');
|
||||
}
|
||||
};
|
||||
@@ -64,7 +64,7 @@ function connectWebSocket(token) {
|
||||
} catch (err) {
|
||||
console.error('Error closing WebSocket:', err);
|
||||
}
|
||||
if (AppGlobal.debugWebSocket) {
|
||||
if (AppDebug.debugWebSocket) {
|
||||
console.log('WebSocket closed');
|
||||
}
|
||||
workerTimers.setTimeout(() => {
|
||||
@@ -78,10 +78,10 @@ function connectWebSocket(token) {
|
||||
}, 5000);
|
||||
};
|
||||
socket.onerror = () => {
|
||||
if (AppGlobal.errorNoty) {
|
||||
AppGlobal.errorNoty.close();
|
||||
if (AppDebug.errorNoty) {
|
||||
AppDebug.errorNoty.close();
|
||||
}
|
||||
AppGlobal.errorNoty = new Noty({
|
||||
AppDebug.errorNoty = new Noty({
|
||||
type: 'error',
|
||||
text: 'WebSocket Error'
|
||||
}).show();
|
||||
@@ -109,7 +109,7 @@ function connectWebSocket(token) {
|
||||
handlePipeline({
|
||||
json
|
||||
});
|
||||
if (AppGlobal.debugWebSocket && json.content) {
|
||||
if (AppDebug.debugWebSocket && json.content) {
|
||||
let displayName = '';
|
||||
const user = userStore.cachedUsers.get(json.content.userId);
|
||||
if (user) {
|
||||
@@ -167,10 +167,10 @@ function handlePipeline(args) {
|
||||
const { type, content, err } = args.json;
|
||||
if (typeof err !== 'undefined') {
|
||||
console.error('PIPELINE: error', args);
|
||||
if (AppGlobal.errorNoty) {
|
||||
AppGlobal.errorNoty.close();
|
||||
if (AppDebug.errorNoty) {
|
||||
AppDebug.errorNoty.close();
|
||||
}
|
||||
AppGlobal.errorNoty = new Noty({
|
||||
AppDebug.errorNoty = new Noty({
|
||||
type: 'error',
|
||||
text: escapeTag(`WebSocket Error: ${err}`)
|
||||
}).show();
|
||||
|
||||
@@ -153,7 +153,7 @@ function feedFiltersOptions() {
|
||||
name: 'Video Play',
|
||||
options: getOptions(['Off', 'On']),
|
||||
tooltip: 'Requires VRCX YouTube API option enabled',
|
||||
tooltipIcon: 'el-icon-warning'
|
||||
tooltipWarning: true
|
||||
},
|
||||
{
|
||||
key: 'Event',
|
||||
|
||||
@@ -6,10 +6,6 @@ const groupDialogSortingOptions = {
|
||||
joinedAtAsc: {
|
||||
name: 'dialog.group.members.sorting.joined_at_asc',
|
||||
value: 'joinedAt:asc'
|
||||
},
|
||||
userId: {
|
||||
name: 'dialog.group.members.sorting.user_id',
|
||||
value: ''
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
export * from './emoji';
|
||||
export * from './feedFilters';
|
||||
export * from './language';
|
||||
export * from './ossLicenses';
|
||||
export * from './photon';
|
||||
export * from './settings';
|
||||
export * from './group';
|
||||
export * from './user';
|
||||
|
||||
@@ -104,30 +104,6 @@ const openSourceSoftwareLicenses = [
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.`
|
||||
},
|
||||
{
|
||||
name: 'librsync.net',
|
||||
licenseText: `The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Brad Dodson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.`
|
||||
},
|
||||
{
|
||||
name: 'Newtonsoft.Json',
|
||||
licenseText: `The MIT License (MIT)
|
||||
@@ -140,6 +116,7 @@ const openSourceSoftwareLicenses = [
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.`
|
||||
},
|
||||
|
||||
{
|
||||
name: 'normalize',
|
||||
licenseText: `The MIT License (MIT)
|
||||
@@ -276,52 +253,39 @@ const openSourceSoftwareLicenses = [
|
||||
THE SOFTWARE.`
|
||||
},
|
||||
{
|
||||
name: 'vue-data-tables',
|
||||
licenseText: `The MIT License (MIT)
|
||||
name: 'NLog',
|
||||
licenseText: `BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2018 Leon Zhang
|
||||
Copyright (c) 2004-2024 Jaroslaw Kowalski <jaak@jkowalski.net>, Kim Christensen, Julian Verdurmen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
All rights reserved.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.`
|
||||
},
|
||||
{
|
||||
name: 'vue-lazyload',
|
||||
licenseText: `The MIT License (MIT)
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
Copyright (c) 2016 Awe
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
* Neither the name of Jaroslaw Kowalski nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.`
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.`
|
||||
},
|
||||
{
|
||||
name: 'Encode Sans Font (from Dark Vanilla)',
|
||||
@@ -386,6 +350,54 @@ const openSourceSoftwareLicenses = [
|
||||
ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
||||
DEALINGS IN THE FONT SOFTWARE.`
|
||||
},
|
||||
{
|
||||
name: 'SixLabors ImageSharp',
|
||||
licenseText: `Apache License 2.0
|
||||
|
||||
Six Labors Split License
|
||||
Version 1.0, June 2022
|
||||
Copyright (c) Six Labors
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source
|
||||
code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including
|
||||
but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" (or "Works") shall mean any Six Labors software made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work.
|
||||
|
||||
"Direct Package Dependency" shall mean any Work in Source or Object form that is installed directly by You.
|
||||
|
||||
"Transitive Package Dependency" shall mean any Work in Object form that is installed indirectly by a third party
|
||||
dependency unrelated to Six Labors.
|
||||
|
||||
2. License
|
||||
|
||||
Works in Source or Object form are split licensed and may be licensed under the Apache License, Version 2.0 or a
|
||||
Six Labors Commercial Use License.
|
||||
|
||||
Licenses are granted based upon You meeting the qualified criteria as stated. Once granted,
|
||||
You must reference the granted license only in all documentation.
|
||||
|
||||
Works in Source or Object form are licensed to You under the Apache License, Version 2.0 if.
|
||||
|
||||
- You are consuming the Work in for use in software licensed under an Open Source or Source Available license.
|
||||
- You are consuming the Work as a Transitive Package Dependency.
|
||||
- You are consuming the Work as a Direct Package Dependency in the capacity of a For-profit company/individual with
|
||||
less than 1M USD annual gross revenue.
|
||||
- You are consuming the Work as a Direct Package Dependency in the capacity of a Non-profit organization
|
||||
or Registered Charity.
|
||||
|
||||
For all other scenarios, Works in Source or Object form are licensed to You under the Six Labors Commercial License
|
||||
which may be purchased by visiting https://sixlabors.com/pricing/.`
|
||||
},
|
||||
{
|
||||
name: 'Apache ECharts',
|
||||
licenseText: `Apache License 2.0
|
||||
@@ -418,6 +430,41 @@ const openSourceSoftwareLicenses = [
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.`
|
||||
},
|
||||
{
|
||||
name: 'Electron',
|
||||
licenseText: `MIT License
|
||||
|
||||
Copyright (c) Electron contributors
|
||||
Copyright (c) 2013-2020 GitHub Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.`
|
||||
},
|
||||
{
|
||||
name: 'Remix Icon',
|
||||
licenseText: `Apache License 2.0
|
||||
|
||||
Copyright 2017-2025 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (https://www.apache.org/).`
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import dark from '../../assets/scss/themes/theme.dark.scss?url';
|
||||
import darkblue from '../../assets/scss/themes/theme.darkblue.scss?url';
|
||||
import amoled from '../../assets/scss/themes/theme.amoled.scss?url';
|
||||
import darkvanillaold from '../../assets/scss/themes/theme.darkvanillaold.scss?url';
|
||||
import darkvanilla from '../../assets/scss/themes/theme.darkvanilla.scss?url';
|
||||
import pink from '../../assets/scss/themes/theme.pink.scss?url';
|
||||
import material3 from '../../assets/scss/themes/theme.material3.scss?url';
|
||||
|
||||
export const THEME_CONFIG = {
|
||||
system: {
|
||||
cssFile: '',
|
||||
requiresDarkBase: false,
|
||||
isDark: 'system',
|
||||
name: 'System'
|
||||
},
|
||||
light: {
|
||||
cssFile: '',
|
||||
requiresDarkBase: false,
|
||||
isDark: false,
|
||||
name: 'Light'
|
||||
},
|
||||
dark: { cssFile: '', requiresDarkBase: true, isDark: true, name: 'Dark' },
|
||||
dark: { cssFile: dark, isDark: true, name: 'Dark' },
|
||||
darkblue: {
|
||||
cssFile: 'theme.darkblue.css',
|
||||
requiresDarkBase: true,
|
||||
cssFile: darkblue,
|
||||
isDark: true,
|
||||
name: 'Dark Blue'
|
||||
},
|
||||
amoled: {
|
||||
cssFile: 'theme.amoled.css',
|
||||
requiresDarkBase: true,
|
||||
cssFile: amoled,
|
||||
isDark: true,
|
||||
name: 'Amoled'
|
||||
},
|
||||
darkvanillaold: {
|
||||
cssFile: 'theme.darkvanillaold.css',
|
||||
requiresDarkBase: true,
|
||||
cssFile: darkvanillaold,
|
||||
isDark: true,
|
||||
name: 'Dark Vanilla Old'
|
||||
},
|
||||
darkvanilla: {
|
||||
cssFile: 'theme.darkvanilla.css',
|
||||
requiresDarkBase: true,
|
||||
cssFile: darkvanilla,
|
||||
isDark: true,
|
||||
name: 'Dark Vanilla'
|
||||
},
|
||||
pink: {
|
||||
cssFile: 'theme.pink.css',
|
||||
requiresDarkBase: true,
|
||||
cssFile: pink,
|
||||
isDark: true,
|
||||
name: 'Pink'
|
||||
},
|
||||
material3: {
|
||||
cssFile: 'theme.material3.css',
|
||||
requiresDarkBase: true,
|
||||
cssFile: material3,
|
||||
isDark: true,
|
||||
name: 'Material 3'
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ function timeToText(sec, isNeedSeconds = false) {
|
||||
if (n < 0) {
|
||||
n = -n;
|
||||
}
|
||||
if (n >= 31536000) {
|
||||
arr.push(`${Math.floor(n / 31536000)}y`);
|
||||
n %= 31536000;
|
||||
}
|
||||
if (n >= 86400) {
|
||||
arr.push(`${Math.floor(n / 86400)}d`);
|
||||
n %= 86400;
|
||||
|
||||
+42
-63
@@ -23,13 +23,24 @@ function changeAppDarkStyle(isDark) {
|
||||
}
|
||||
|
||||
function changeAppThemeStyle(themeMode) {
|
||||
if (themeMode === 'system') {
|
||||
themeMode = systemIsDarkMode() ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
const themeConfig = THEME_CONFIG[themeMode];
|
||||
if (!themeConfig) return;
|
||||
if (!themeConfig) {
|
||||
console.error('Invalid theme mode:', themeMode);
|
||||
return;
|
||||
}
|
||||
|
||||
let filePathPrefix = 'file://vrcx/';
|
||||
if (LINUX) {
|
||||
filePathPrefix = './';
|
||||
}
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
filePathPrefix = 'http://localhost:9000/';
|
||||
console.log('Using development file path prefix:', filePathPrefix);
|
||||
}
|
||||
|
||||
let $appThemeStyle = document.getElementById('app-theme-style');
|
||||
if (!$appThemeStyle) {
|
||||
@@ -38,63 +49,31 @@ function changeAppThemeStyle(themeMode) {
|
||||
$appThemeStyle.rel = 'stylesheet';
|
||||
document.head.appendChild($appThemeStyle);
|
||||
}
|
||||
$appThemeStyle.href = themeConfig.cssFile
|
||||
? `${filePathPrefix}${themeConfig.cssFile}`
|
||||
: '';
|
||||
$appThemeStyle.href = themeConfig.cssFile ? themeConfig.cssFile : '';
|
||||
|
||||
let $appThemeDarkStyle = document.getElementById('app-theme-dark-style');
|
||||
const darkThemeCssPath = `${filePathPrefix}theme.dark.css`;
|
||||
const shouldApplyDarkBase =
|
||||
themeConfig.requiresDarkBase ||
|
||||
(themeMode === 'system' && systemIsDarkMode());
|
||||
|
||||
if (shouldApplyDarkBase) {
|
||||
if (!$appThemeDarkStyle) {
|
||||
$appThemeDarkStyle = document.createElement('link');
|
||||
$appThemeDarkStyle.setAttribute('id', 'app-theme-dark-style');
|
||||
$appThemeDarkStyle.rel = 'stylesheet';
|
||||
$appThemeDarkStyle.href = darkThemeCssPath;
|
||||
document.head.insertBefore($appThemeDarkStyle, $appThemeStyle);
|
||||
} else if ($appThemeDarkStyle.href !== darkThemeCssPath) {
|
||||
$appThemeDarkStyle.href = darkThemeCssPath;
|
||||
}
|
||||
if (themeConfig.isDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
$appThemeDarkStyle && $appThemeDarkStyle.remove();
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
changeAppDarkStyle(themeConfig.isDark);
|
||||
|
||||
let isDarkForExternalApp = themeConfig.isDark;
|
||||
if (isDarkForExternalApp === 'system') {
|
||||
isDarkForExternalApp = systemIsDarkMode();
|
||||
}
|
||||
changeAppDarkStyle(isDarkForExternalApp);
|
||||
}
|
||||
|
||||
/**
|
||||
* CJK character in Japanese, Korean, Chinese are different
|
||||
* so change font-family order when users change language to display CJK character correctly
|
||||
* @param {string} lang
|
||||
*/
|
||||
function changeCJKFontsOrder(lang) {
|
||||
const otherFonts = window
|
||||
.getComputedStyle(document.body)
|
||||
.fontFamily.split(',')
|
||||
.filter((item) => !item.includes('Noto Sans'))
|
||||
.join(', ');
|
||||
const notoSans = 'Noto Sans';
|
||||
|
||||
const fontFamilies = {
|
||||
ja_JP: ['JP', 'KR', 'TC', 'SC'],
|
||||
ko: ['KR', 'JP', 'TC', 'SC'],
|
||||
zh_TW: ['TC', 'JP', 'KR', 'SC'],
|
||||
zh_CN: ['SC', 'JP', 'KR', 'TC']
|
||||
};
|
||||
|
||||
if (fontFamilies[lang]) {
|
||||
const CJKFamily = fontFamilies[lang]
|
||||
.map((item) => `${notoSans} ${item}`)
|
||||
.join(', ');
|
||||
document.body.style.fontFamily = `${CJKFamily}, ${otherFonts}`;
|
||||
}
|
||||
// let $appThemeDarkStyle = document.getElementById('app-theme-dark-style');
|
||||
// const darkThemeCssPath = `${filePathPrefix}theme.dark.css`;
|
||||
// const shouldApplyDarkBase = themeConfig.isDark;
|
||||
// if (shouldApplyDarkBase) {
|
||||
// if (!$appThemeDarkStyle) {
|
||||
// $appThemeDarkStyle = document.createElement('link');
|
||||
// $appThemeDarkStyle.setAttribute('id', 'app-theme-dark-style');
|
||||
// $appThemeDarkStyle.rel = 'stylesheet';
|
||||
// $appThemeDarkStyle.href = darkThemeCssPath;
|
||||
// document.head.insertBefore($appThemeDarkStyle, $appThemeStyle);
|
||||
// } else if ($appThemeDarkStyle.href !== darkThemeCssPath) {
|
||||
// $appThemeDarkStyle.href = darkThemeCssPath;
|
||||
// }
|
||||
// } else {
|
||||
// $appThemeDarkStyle && $appThemeDarkStyle.remove();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,28 +203,28 @@ function HSVtoRGB(h, s, v) {
|
||||
return `#${decColor.toString(16).substr(1)}`;
|
||||
}
|
||||
|
||||
function adjustDialogZ(el) {
|
||||
let z = 0;
|
||||
document.querySelectorAll('.v-modal,.el-dialog__wrapper').forEach((v) => {
|
||||
function getNextDialogIndex() {
|
||||
let z = 2000;
|
||||
document.querySelectorAll('.el-overlay,.el-modal-dialog').forEach((v) => {
|
||||
if (v.style.display === 'none') {
|
||||
return;
|
||||
}
|
||||
const _z = Number(v.style.zIndex) || 0;
|
||||
if (_z && _z > z && v !== el) {
|
||||
if (_z > z) {
|
||||
z = _z;
|
||||
}
|
||||
});
|
||||
if (z) {
|
||||
el.style.zIndex = z + 1;
|
||||
}
|
||||
return z + 1;
|
||||
}
|
||||
|
||||
export {
|
||||
systemIsDarkMode,
|
||||
changeAppDarkStyle,
|
||||
changeAppThemeStyle,
|
||||
changeCJKFontsOrder,
|
||||
updateTrustColorClasses,
|
||||
refreshCustomCss,
|
||||
refreshCustomScript,
|
||||
HueToHex,
|
||||
HSVtoRGB,
|
||||
adjustDialogZ
|
||||
getNextDialogIndex
|
||||
};
|
||||
|
||||
+27
-29
@@ -1,7 +1,7 @@
|
||||
import Noty from 'noty';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import { miscRequest } from '../../api';
|
||||
import { $app } from '../../app';
|
||||
import {
|
||||
useAvatarStore,
|
||||
useInstanceStore,
|
||||
@@ -156,14 +156,14 @@ function copyToClipboard(text, message = 'Copied successfully!') {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: message,
|
||||
type: 'success'
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Copy failed:', err);
|
||||
$app.$message.error('Copy failed!');
|
||||
ElMessage.error('Copy failed!');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -387,19 +387,20 @@ function openExternalLink(link) {
|
||||
return;
|
||||
}
|
||||
|
||||
$app.$confirm(`${link}`, 'Open External Link', {
|
||||
ElMessageBox.confirm(`${link}`, 'Open External Link', {
|
||||
distinguishCancelAndClose: true,
|
||||
confirmButtonText: 'Open',
|
||||
cancelButtonText: 'Copy',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
if (action === 'confirm') {
|
||||
AppApi.OpenLink(link);
|
||||
} else if (action === 'cancel') {
|
||||
copyLink(link);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -407,7 +408,7 @@ function openExternalLink(link) {
|
||||
* @param {string} text
|
||||
*/
|
||||
function copyLink(text) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: 'Link copied to clipboard',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -485,26 +486,23 @@ async function getBundleDateSize(ref) {
|
||||
fileSize
|
||||
};
|
||||
|
||||
if (unityPackage.variant === 'standard') {
|
||||
if (avatarDialog.value.id === ref.id) {
|
||||
// update avatar dialog
|
||||
avatarDialog.value.bundleSizes[platform] =
|
||||
bundleSizes[platform];
|
||||
avatarDialog.value.lastUpdated = createdAt;
|
||||
avatarDialog.value.fileAnalysis = buildTreeData(bundleJson);
|
||||
}
|
||||
// update world dialog
|
||||
if (worldDialog.value.id === ref.id) {
|
||||
worldDialog.value.bundleSizes[platform] = bundleSizes[platform];
|
||||
worldDialog.value.lastUpdated = createdAt;
|
||||
worldDialog.value.fileAnalysis = buildTreeData(bundleJson);
|
||||
}
|
||||
// update player list
|
||||
if (currentInstanceLocation.value.worldId === ref.id) {
|
||||
currentInstanceWorld.value.bundleSizes[platform] =
|
||||
bundleSizes[platform];
|
||||
currentInstanceWorld.value.lastUpdated = createdAt;
|
||||
}
|
||||
if (avatarDialog.value.id === ref.id) {
|
||||
// update avatar dialog
|
||||
avatarDialog.value.bundleSizes[platform] = bundleSizes[platform];
|
||||
avatarDialog.value.lastUpdated = createdAt;
|
||||
avatarDialog.value.fileAnalysis = buildTreeData(bundleJson);
|
||||
}
|
||||
// update world dialog
|
||||
if (worldDialog.value.id === ref.id) {
|
||||
worldDialog.value.bundleSizes[platform] = bundleSizes[platform];
|
||||
worldDialog.value.lastUpdated = createdAt;
|
||||
worldDialog.value.fileAnalysis = buildTreeData(bundleJson);
|
||||
}
|
||||
// update player list
|
||||
if (currentInstanceLocation.value.worldId === ref.id) {
|
||||
currentInstanceWorld.value.bundleSizes[platform] =
|
||||
bundleSizes[platform];
|
||||
currentInstanceWorld.value.lastUpdated = createdAt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
|
||||
<title>VRCXVR</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+97
-87
@@ -1,12 +1,12 @@
|
||||
import Noty from 'noty';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import { authRequest } from '../api';
|
||||
import { $app } from '../app';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import configRepository from '../service/config';
|
||||
import { database } from '../service/database';
|
||||
import { AppGlobal } from '../service/appConfig';
|
||||
import { AppDebug } from '../service/appConfig';
|
||||
import { request } from '../service/request';
|
||||
import security from '../service/security';
|
||||
import webApiService from '../service/webapi';
|
||||
@@ -59,7 +59,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
password: '',
|
||||
rePassword: '',
|
||||
beforeClose(done) {
|
||||
$app._data.enablePrimaryPassword = false;
|
||||
// $app._data.enablePrimaryPassword = false;
|
||||
done();
|
||||
}
|
||||
},
|
||||
@@ -211,8 +211,8 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
state.loginForm.lastUserLoggedIn
|
||||
];
|
||||
if (user?.loginParmas?.endpoint) {
|
||||
AppGlobal.endpointDomain = user.loginParmas.endpoint;
|
||||
AppGlobal.websocketDomain = user.loginParmas.websocket;
|
||||
AppDebug.endpointDomain = user.loginParmas.endpoint;
|
||||
AppDebug.websocketDomain = user.loginParmas.websocket;
|
||||
}
|
||||
// login at startup
|
||||
state.loginForm.loading = true;
|
||||
@@ -283,7 +283,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
if (advancedSettingsStore.enablePrimaryPassword) {
|
||||
state.enablePrimaryPasswordDialog.visible = true;
|
||||
} else {
|
||||
$app.$prompt(
|
||||
ElMessageBox.prompt(
|
||||
t('prompt.primary_password.description'),
|
||||
t('prompt.primary_password.header'),
|
||||
{
|
||||
@@ -415,7 +415,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
if (!advancedSettingsStore.enablePrimaryPassword) {
|
||||
resolve(args.password);
|
||||
}
|
||||
$app.$prompt(
|
||||
ElMessageBox.prompt(
|
||||
t('prompt.primary_password.description'),
|
||||
t('prompt.primary_password.header'),
|
||||
{
|
||||
@@ -443,11 +443,12 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
}
|
||||
|
||||
function logout() {
|
||||
$app.$confirm('Continue? Logout', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Logout', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
if (action === 'confirm') {
|
||||
const existingStyle = document.getElementById(
|
||||
'login-container-style'
|
||||
@@ -457,8 +458,8 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
}
|
||||
handleLogoutEvent();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
async function relogin(user) {
|
||||
@@ -468,11 +469,11 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
}
|
||||
state.loginForm.lastUserLoggedIn = user.user.id; // for resend email 2fa
|
||||
if (loginParmas.endpoint) {
|
||||
AppGlobal.endpointDomain = loginParmas.endpoint;
|
||||
AppGlobal.websocketDomain = loginParmas.websocket;
|
||||
AppDebug.endpointDomain = loginParmas.endpoint;
|
||||
AppDebug.websocketDomain = loginParmas.websocket;
|
||||
} else {
|
||||
AppGlobal.endpointDomain = AppGlobal.endpointDomainVrchat;
|
||||
AppGlobal.websocketDomain = AppGlobal.websocketDomainVrchat;
|
||||
AppDebug.endpointDomain = AppDebug.endpointDomainVrchat;
|
||||
AppDebug.websocketDomain = AppDebug.websocketDomainVrchat;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
state.loginForm.loading = true;
|
||||
@@ -501,7 +502,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
});
|
||||
})
|
||||
.catch((_) => {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: 'Incorrect primary password',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -559,11 +560,11 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
if (!state.loginForm.loading) {
|
||||
state.loginForm.loading = true;
|
||||
if (state.loginForm.endpoint) {
|
||||
AppGlobal.endpointDomain = state.loginForm.endpoint;
|
||||
AppGlobal.websocketDomain = state.loginForm.websocket;
|
||||
AppDebug.endpointDomain = state.loginForm.endpoint;
|
||||
AppDebug.websocketDomain = state.loginForm.websocket;
|
||||
} else {
|
||||
AppGlobal.endpointDomain = AppGlobal.endpointDomainVrchat;
|
||||
AppGlobal.websocketDomain = AppGlobal.websocketDomainVrchat;
|
||||
AppDebug.endpointDomain = AppDebug.endpointDomainVrchat;
|
||||
AppDebug.websocketDomain = AppDebug.websocketDomainVrchat;
|
||||
}
|
||||
authRequest
|
||||
.getConfig()
|
||||
@@ -576,7 +577,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
state.loginForm.saveCredentials &&
|
||||
advancedSettingsStore.enablePrimaryPassword
|
||||
) {
|
||||
$app.$prompt(
|
||||
ElMessageBox.prompt(
|
||||
t('prompt.primary_password.description'),
|
||||
t('prompt.primary_password.header'),
|
||||
{
|
||||
@@ -649,36 +650,40 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
}
|
||||
AppApi.FlashWindow();
|
||||
state.twoFactorAuthDialogVisible = true;
|
||||
$app.$prompt(t('prompt.totp.description'), t('prompt.totp.header'), {
|
||||
distinguishCancelAndClose: true,
|
||||
cancelButtonText: t('prompt.totp.use_otp'),
|
||||
confirmButtonText: t('prompt.totp.verify'),
|
||||
inputPlaceholder: t('prompt.totp.input_placeholder'),
|
||||
inputPattern: /^[0-9]{6}$/,
|
||||
inputErrorMessage: t('prompt.totp.input_error'),
|
||||
callback: (action, instance) => {
|
||||
if (action === 'confirm') {
|
||||
authRequest
|
||||
.verifyTOTP({
|
||||
code: instance.inputValue.trim()
|
||||
})
|
||||
.catch((err) => {
|
||||
clearCookiesTryLogin();
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
userStore.getCurrentUser();
|
||||
return args;
|
||||
});
|
||||
} else if (action === 'cancel') {
|
||||
promptOTP();
|
||||
ElMessageBox.prompt(
|
||||
t('prompt.totp.description'),
|
||||
t('prompt.totp.header'),
|
||||
{
|
||||
distinguishCancelAndClose: true,
|
||||
cancelButtonText: t('prompt.totp.use_otp'),
|
||||
confirmButtonText: t('prompt.totp.verify'),
|
||||
inputPlaceholder: t('prompt.totp.input_placeholder'),
|
||||
inputPattern: /^[0-9]{6}$/,
|
||||
inputErrorMessage: t('prompt.totp.input_error'),
|
||||
callback: (action, instance) => {
|
||||
if (action === 'confirm') {
|
||||
authRequest
|
||||
.verifyTOTP({
|
||||
code: instance.inputValue.trim()
|
||||
})
|
||||
.catch((err) => {
|
||||
clearCookiesTryLogin();
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
userStore.getCurrentUser();
|
||||
return args;
|
||||
});
|
||||
} else if (action === 'cancel') {
|
||||
promptOTP();
|
||||
}
|
||||
},
|
||||
beforeClose: (action, instance, done) => {
|
||||
state.twoFactorAuthDialogVisible = false;
|
||||
done();
|
||||
}
|
||||
},
|
||||
beforeClose: (action, instance, done) => {
|
||||
state.twoFactorAuthDialogVisible = false;
|
||||
done();
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
function promptOTP() {
|
||||
@@ -686,36 +691,40 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
return;
|
||||
}
|
||||
state.twoFactorAuthDialogVisible = true;
|
||||
$app.$prompt(t('prompt.otp.description'), t('prompt.otp.header'), {
|
||||
distinguishCancelAndClose: true,
|
||||
cancelButtonText: t('prompt.otp.use_totp'),
|
||||
confirmButtonText: t('prompt.otp.verify'),
|
||||
inputPlaceholder: t('prompt.otp.input_placeholder'),
|
||||
inputPattern: /^[a-z0-9]{4}-[a-z0-9]{4}$/,
|
||||
inputErrorMessage: t('prompt.otp.input_error'),
|
||||
callback: (action, instance) => {
|
||||
if (action === 'confirm') {
|
||||
authRequest
|
||||
.verifyOTP({
|
||||
code: instance.inputValue.trim()
|
||||
})
|
||||
.catch((err) => {
|
||||
clearCookiesTryLogin();
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
userStore.getCurrentUser();
|
||||
return args;
|
||||
});
|
||||
} else if (action === 'cancel') {
|
||||
promptTOTP();
|
||||
ElMessageBox.prompt(
|
||||
t('prompt.otp.description'),
|
||||
t('prompt.otp.header'),
|
||||
{
|
||||
distinguishCancelAndClose: true,
|
||||
cancelButtonText: t('prompt.otp.use_totp'),
|
||||
confirmButtonText: t('prompt.otp.verify'),
|
||||
inputPlaceholder: t('prompt.otp.input_placeholder'),
|
||||
inputPattern: /^[a-z0-9]{4}-[a-z0-9]{4}$/,
|
||||
inputErrorMessage: t('prompt.otp.input_error'),
|
||||
callback: (action, instance) => {
|
||||
if (action === 'confirm') {
|
||||
authRequest
|
||||
.verifyOTP({
|
||||
code: instance.inputValue.trim()
|
||||
})
|
||||
.catch((err) => {
|
||||
clearCookiesTryLogin();
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
userStore.getCurrentUser();
|
||||
return args;
|
||||
});
|
||||
} else if (action === 'cancel') {
|
||||
promptTOTP();
|
||||
}
|
||||
},
|
||||
beforeClose: (action, instance, done) => {
|
||||
state.twoFactorAuthDialogVisible = false;
|
||||
done();
|
||||
}
|
||||
},
|
||||
beforeClose: (action, instance, done) => {
|
||||
state.twoFactorAuthDialogVisible = false;
|
||||
done();
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
function promptEmailOTP() {
|
||||
@@ -724,7 +733,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
}
|
||||
AppApi.FlashWindow();
|
||||
state.twoFactorAuthDialogVisible = true;
|
||||
$app.$prompt(
|
||||
ElMessageBox.prompt(
|
||||
t('prompt.email_otp.description'),
|
||||
t('prompt.email_otp.header'),
|
||||
{
|
||||
@@ -840,20 +849,20 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
state.autoLoginAttempts.add(new Date().getTime());
|
||||
relogin(user)
|
||||
.then(() => {
|
||||
if (AppGlobal.errorNoty) {
|
||||
AppGlobal.errorNoty.close();
|
||||
if (AppDebug.errorNoty) {
|
||||
AppDebug.errorNoty.close();
|
||||
}
|
||||
AppGlobal.errorNoty = new Noty({
|
||||
AppDebug.errorNoty = new Noty({
|
||||
type: 'success',
|
||||
text: 'Automatically logged in.'
|
||||
}).show();
|
||||
console.log('Automatically logged in.');
|
||||
})
|
||||
.catch((err) => {
|
||||
if (AppGlobal.errorNoty) {
|
||||
AppGlobal.errorNoty.close();
|
||||
if (AppDebug.errorNoty) {
|
||||
AppDebug.errorNoty.close();
|
||||
}
|
||||
AppGlobal.errorNoty = new Noty({
|
||||
AppDebug.errorNoty = new Noty({
|
||||
type: 'error',
|
||||
text: 'Failed to login automatically.'
|
||||
}).show();
|
||||
@@ -861,7 +870,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
})
|
||||
.finally(() => {
|
||||
if (!navigator.onLine) {
|
||||
AppGlobal.errorNoty = new Noty({
|
||||
AppDebug.errorNoty = new Noty({
|
||||
type: 'error',
|
||||
text: `You're offline.`
|
||||
}).show();
|
||||
@@ -879,6 +888,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
|
||||
return {
|
||||
state,
|
||||
|
||||
loginForm,
|
||||
enablePrimaryPasswordDialog,
|
||||
saveCredentials,
|
||||
|
||||
+51
-74
@@ -1,10 +1,9 @@
|
||||
import Noty from 'noty';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { avatarRequest, imageRequest } from '../api';
|
||||
import { $app } from '../app';
|
||||
import { computed, reactive, watch, nextTick } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { avatarRequest, miscRequest } from '../api';
|
||||
import { database } from '../service/database';
|
||||
import { AppGlobal } from '../service/appConfig';
|
||||
import { AppDebug } from '../service/appConfig';
|
||||
import webApiService from '../service/webapi';
|
||||
import { watchState } from '../service/watchState';
|
||||
import {
|
||||
@@ -53,15 +52,16 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
cacheSize: '',
|
||||
cacheLocked: false,
|
||||
cachePath: '',
|
||||
fileAnalysis: {}
|
||||
fileAnalysis: []
|
||||
},
|
||||
cachedAvatarModerations: new Map(),
|
||||
avatarHistory: new Set(),
|
||||
avatarHistoryArray: [],
|
||||
cachedAvatars: new Map(),
|
||||
cachedAvatarNames: new Map()
|
||||
avatarHistoryArray: []
|
||||
});
|
||||
|
||||
let cachedAvatarModerations = new Map();
|
||||
let cachedAvatars = new Map();
|
||||
let cachedAvatarNames = new Map();
|
||||
|
||||
const avatarDialog = computed({
|
||||
get: () => state.avatarDialog,
|
||||
set: (value) => {
|
||||
@@ -76,34 +76,13 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const cachedAvatarModerations = computed({
|
||||
get: () => state.cachedAvatarModerations,
|
||||
set: (value) => {
|
||||
state.cachedAvatarModerations = value;
|
||||
}
|
||||
});
|
||||
|
||||
const cachedAvatars = computed({
|
||||
get: () => state.cachedAvatars,
|
||||
set: (value) => {
|
||||
state.cachedAvatars = value;
|
||||
}
|
||||
});
|
||||
|
||||
const cachedAvatarNames = computed({
|
||||
get: () => state.cachedAvatarNames,
|
||||
set: (value) => {
|
||||
state.cachedAvatarNames = value;
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => watchState.isLoggedIn,
|
||||
(isLoggedIn) => {
|
||||
state.avatarDialog.visible = false;
|
||||
state.cachedAvatars.clear();
|
||||
state.cachedAvatarNames.clear();
|
||||
state.cachedAvatarModerations.clear();
|
||||
cachedAvatars.clear();
|
||||
cachedAvatarNames.clear();
|
||||
cachedAvatarModerations.clear();
|
||||
state.avatarHistory.clear();
|
||||
state.avatarHistoryArray = [];
|
||||
if (isLoggedIn) {
|
||||
@@ -120,7 +99,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
function applyAvatar(json) {
|
||||
json.name = replaceBioSymbols(json.name);
|
||||
json.description = replaceBioSymbols(json.description);
|
||||
let ref = state.cachedAvatars.get(json.id);
|
||||
let ref = cachedAvatars.get(json.id);
|
||||
if (typeof ref === 'undefined') {
|
||||
ref = {
|
||||
acknowledgements: '',
|
||||
@@ -152,7 +131,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
version: 0,
|
||||
...json
|
||||
};
|
||||
state.cachedAvatars.set(ref.id, ref);
|
||||
cachedAvatars.set(ref.id, ref);
|
||||
} else {
|
||||
const { unityPackages } = ref;
|
||||
Object.assign(ref, json);
|
||||
@@ -212,7 +191,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
D.cacheSize = '';
|
||||
D.cacheLocked = false;
|
||||
D.cachePath = '';
|
||||
D.fileAnalysis = {};
|
||||
D.fileAnalysis = [];
|
||||
D.isQuestFallback = false;
|
||||
D.isPC = false;
|
||||
D.isQuest = false;
|
||||
@@ -228,8 +207,8 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
favoriteStore.cachedFavoritesByObjectId.has(avatarId) ||
|
||||
(userStore.currentUser.$isVRCPlus &&
|
||||
favoriteStore.localAvatarFavoritesList.includes(avatarId));
|
||||
D.isBlocked = state.cachedAvatarModerations.has(avatarId);
|
||||
const ref2 = state.cachedAvatars.get(avatarId);
|
||||
D.isBlocked = cachedAvatarModerations.has(avatarId);
|
||||
const ref2 = cachedAvatars.get(avatarId);
|
||||
if (typeof ref2 !== 'undefined') {
|
||||
D.ref = ref2;
|
||||
updateVRChatAvatarCache();
|
||||
@@ -277,12 +256,12 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
throw err;
|
||||
})
|
||||
.finally(() => {
|
||||
$app.$nextTick(() => (D.loading = false));
|
||||
nextTick(() => (D.loading = false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.getAvatarGallery`
|
||||
*
|
||||
* @param {string} avatarId
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
@@ -325,7 +304,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
json.created = new Date(json.created).toJSON();
|
||||
}
|
||||
|
||||
let ref = state.cachedAvatarModerations.get(json.targetAvatarId);
|
||||
let ref = cachedAvatarModerations.get(json.targetAvatarId);
|
||||
if (typeof ref === 'undefined') {
|
||||
ref = {
|
||||
avatarModerationType: '',
|
||||
@@ -333,7 +312,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
targetAvatarId: '',
|
||||
...json
|
||||
};
|
||||
state.cachedAvatarModerations.set(ref.targetAvatarId, ref);
|
||||
cachedAvatarModerations.set(ref.targetAvatarId, ref);
|
||||
} else {
|
||||
Object.assign(ref, json);
|
||||
}
|
||||
@@ -370,7 +349,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.getAvatarHistory`
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function getAvatarHistory() {
|
||||
@@ -428,16 +407,15 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
}
|
||||
|
||||
function promptClearAvatarHistory() {
|
||||
$app.$confirm('Continue? Clear Avatar History', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Clear Avatar History', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
clearAvatarHistory();
|
||||
}
|
||||
}
|
||||
});
|
||||
type: 'info'
|
||||
})
|
||||
.then(() => {
|
||||
clearAvatarHistory();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -453,12 +431,12 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
avatarName: '-'
|
||||
};
|
||||
}
|
||||
if (state.cachedAvatarNames.has(fileId)) {
|
||||
return state.cachedAvatarNames.get(fileId);
|
||||
if (cachedAvatarNames.has(fileId)) {
|
||||
return cachedAvatarNames.get(fileId);
|
||||
}
|
||||
try {
|
||||
const args = await imageRequest.getAvatarImages({ fileId });
|
||||
return storeAvatarImage(args, state.cachedAvatarNames);
|
||||
const args = await miscRequest.getFile({ fileId });
|
||||
return storeAvatarImage(args, cachedAvatarNames);
|
||||
} catch (error) {
|
||||
console.error('Failed to get avatar images:', error);
|
||||
return {
|
||||
@@ -483,7 +461,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
}
|
||||
});
|
||||
const json = JSON.parse(response.data);
|
||||
if (AppGlobal.debugWebRequests) {
|
||||
if (AppDebug.debugWebRequests) {
|
||||
console.log(json, response);
|
||||
}
|
||||
if (response.status === 200 && typeof json === 'object') {
|
||||
@@ -511,7 +489,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
} catch (err) {
|
||||
const msg = `Avatar search failed for ${search} with ${avatarProviderStore.avatarRemoteDatabaseProvider}\n${err}`;
|
||||
console.error(msg);
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: msg,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -563,7 +541,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
}
|
||||
});
|
||||
const json = JSON.parse(response.data);
|
||||
if (AppGlobal.debugWebRequests) {
|
||||
if (AppDebug.debugWebRequests) {
|
||||
console.log(json, response);
|
||||
}
|
||||
if (response.status === 200 && typeof json === 'object') {
|
||||
@@ -589,7 +567,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
} catch (err) {
|
||||
const msg = `Avatar lookup failed for ${authorId} with ${url}\n${err}`;
|
||||
console.error(msg);
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: msg,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -598,22 +576,20 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
}
|
||||
|
||||
function selectAvatarWithConfirmation(id) {
|
||||
$app.$confirm(`Continue? Select Avatar`, 'Confirm', {
|
||||
ElMessageBox.confirm(`Continue? Select Avatar`, 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
if (action !== 'confirm') {
|
||||
return;
|
||||
}
|
||||
type: 'info'
|
||||
})
|
||||
.then(() => {
|
||||
selectAvatarWithoutConfirmation(id);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function selectAvatarWithoutConfirmation(id) {
|
||||
if (userStore.currentUser.currentAvatar === id) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: 'Avatar already selected',
|
||||
type: 'info'
|
||||
});
|
||||
@@ -624,7 +600,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
avatarId: id
|
||||
})
|
||||
.then(() => {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: 'Avatar changed',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -633,7 +609,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
|
||||
function checkAvatarCache(fileId) {
|
||||
let avatarId = '';
|
||||
for (let ref of state.cachedAvatars.values()) {
|
||||
for (let ref of cachedAvatars.values()) {
|
||||
if (extractFileId(ref.imageUrl) === fileId) {
|
||||
avatarId = ref.id;
|
||||
}
|
||||
@@ -659,7 +635,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
) {
|
||||
const fileId = extractFileId(currentAvatarImageUrl);
|
||||
if (!fileId) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: 'Sorry, the author is unknown',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -682,13 +658,13 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
}
|
||||
if (!avatarId) {
|
||||
if (avatarInfo.ownerId === refUserId) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message:
|
||||
"It's personal (own) avatar or not found in avatar database",
|
||||
type: 'warning'
|
||||
});
|
||||
} else {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: 'Avatar not found in avatar database',
|
||||
type: 'warning'
|
||||
});
|
||||
@@ -712,6 +688,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
|
||||
return {
|
||||
state,
|
||||
|
||||
avatarDialog,
|
||||
avatarHistory,
|
||||
avatarHistoryArray,
|
||||
|
||||
+99
-188
@@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { favoriteRequest } from '../api';
|
||||
import { $app } from '../app';
|
||||
import { database } from '../service/database';
|
||||
import { processBulk } from '../service/request';
|
||||
import { watchState } from '../service/watchState';
|
||||
@@ -12,7 +12,7 @@ import { useAppearanceSettingsStore } from './settings/appearance';
|
||||
import { useGeneralSettingsStore } from './settings/general';
|
||||
import { useUserStore } from './user';
|
||||
import { useWorldStore } from './world';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||
@@ -41,7 +41,6 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
},
|
||||
cachedFavoriteGroupsByTypeName: new Map(),
|
||||
cachedFavorites: new Map(),
|
||||
favoriteWorldGroups: [],
|
||||
favoriteAvatarGroups: [],
|
||||
isFavoriteLoading: false,
|
||||
@@ -77,6 +76,21 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
cachedFavoritesByObjectId: new Map()
|
||||
});
|
||||
|
||||
let cachedFavorites = new Map();
|
||||
|
||||
const cachedFavoriteGroups = computed({
|
||||
get: () => state.cachedFavoriteGroups,
|
||||
set: (value) => {
|
||||
state.cachedFavoriteGroups = value;
|
||||
}
|
||||
});
|
||||
const cachedFavoriteGroupsByTypeName = computed({
|
||||
get: () => state.cachedFavoriteGroupsByTypeName,
|
||||
set: (value) => {
|
||||
state.cachedFavoriteGroupsByTypeName = value;
|
||||
}
|
||||
});
|
||||
|
||||
const favoriteFriends = computed(() => {
|
||||
if (state.sortFavoriteFriends) {
|
||||
state.sortFavoriteFriends = false;
|
||||
@@ -144,10 +158,6 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const cachedFavoriteGroups = state.cachedFavoriteGroups;
|
||||
const cachedFavoriteGroupsByTypeName = state.cachedFavoriteGroupsByTypeName;
|
||||
const cachedFavorites = state.cachedFavorites;
|
||||
|
||||
const favoriteLimits = computed({
|
||||
get() {
|
||||
return state.favoriteLimits;
|
||||
@@ -265,15 +275,6 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const favoriteObjects = computed({
|
||||
get() {
|
||||
return state.favoriteObjects;
|
||||
},
|
||||
set(value) {
|
||||
state.favoriteObjects = value;
|
||||
}
|
||||
});
|
||||
|
||||
const localWorldFavoritesList = computed({
|
||||
get() {
|
||||
return state.localWorldFavoritesList;
|
||||
@@ -283,87 +284,6 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const favoriteFriends_ = computed({
|
||||
get() {
|
||||
return state.favoriteFriends_;
|
||||
},
|
||||
set(value) {
|
||||
state.favoriteFriends_ = value;
|
||||
}
|
||||
});
|
||||
|
||||
const favoriteFriendsSorted = computed({
|
||||
get() {
|
||||
return state.favoriteFriendsSorted;
|
||||
},
|
||||
set(value) {
|
||||
state.favoriteFriendsSorted = value;
|
||||
}
|
||||
});
|
||||
|
||||
const favoriteWorlds_ = computed({
|
||||
get() {
|
||||
return state.favoriteWorlds_;
|
||||
},
|
||||
set(value) {
|
||||
state.favoriteWorlds_ = value;
|
||||
}
|
||||
});
|
||||
|
||||
const favoriteWorldsSorted = computed({
|
||||
get() {
|
||||
return state.favoriteWorldsSorted;
|
||||
},
|
||||
set(value) {
|
||||
state.favoriteWorldsSorted = value;
|
||||
}
|
||||
});
|
||||
|
||||
const favoriteAvatars_ = computed({
|
||||
get() {
|
||||
return state.favoriteAvatars_;
|
||||
},
|
||||
set(value) {
|
||||
state.favoriteAvatars_ = value;
|
||||
}
|
||||
});
|
||||
|
||||
const favoriteAvatarsSorted = computed({
|
||||
get() {
|
||||
return state.favoriteAvatarsSorted;
|
||||
},
|
||||
set(value) {
|
||||
state.favoriteAvatarsSorted = value;
|
||||
}
|
||||
});
|
||||
|
||||
const sortFavoriteFriends = computed({
|
||||
get() {
|
||||
return state.sortFavoriteFriends;
|
||||
},
|
||||
set(value) {
|
||||
state.sortFavoriteFriends = value;
|
||||
}
|
||||
});
|
||||
|
||||
const sortFavoriteWorlds = computed({
|
||||
get() {
|
||||
return state.sortFavoriteWorlds;
|
||||
},
|
||||
set(value) {
|
||||
state.sortFavoriteWorlds = value;
|
||||
}
|
||||
});
|
||||
|
||||
const sortFavoriteAvatars = computed({
|
||||
get() {
|
||||
return state.sortFavoriteAvatars;
|
||||
},
|
||||
set(value) {
|
||||
state.sortFavoriteAvatars = value;
|
||||
}
|
||||
});
|
||||
|
||||
const cachedFavoritesByObjectId = computed({
|
||||
get() {
|
||||
return state.cachedFavoritesByObjectId;
|
||||
@@ -399,7 +319,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
() => watchState.isLoggedIn,
|
||||
(isLoggedIn) => {
|
||||
friendStore.localFavoriteFriends.clear();
|
||||
state.cachedFavorites.clear();
|
||||
cachedFavorites.clear();
|
||||
state.cachedFavoritesByObjectId.clear();
|
||||
state.cachedFavoriteGroups.clear();
|
||||
state.cachedFavoriteGroupsByTypeName.clear();
|
||||
@@ -536,7 +456,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
|
||||
function handleFavoriteGroupClear(args) {
|
||||
const key = `${args.params.type}:${args.params.group}`;
|
||||
for (const ref of state.cachedFavorites.values()) {
|
||||
for (const ref of cachedFavorites.values()) {
|
||||
if (ref.$isDeleted || ref.$groupKey !== key) {
|
||||
continue;
|
||||
}
|
||||
@@ -573,7 +493,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
|
||||
function expireFavorites() {
|
||||
friendStore.localFavoriteFriends.clear();
|
||||
state.cachedFavorites.clear();
|
||||
cachedFavorites.clear();
|
||||
state.cachedFavoritesByObjectId.clear();
|
||||
state.favoriteObjects.clear();
|
||||
state.favoriteFriends_ = [];
|
||||
@@ -625,7 +545,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.applyFavorite`
|
||||
*
|
||||
* @param {'friend' | 'world' | 'avatar'} type
|
||||
* @param {string} objectId
|
||||
* @param {boolean} sortTop
|
||||
@@ -952,7 +872,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
state.cachedFavoriteGroupsByTypeName.set(group.key, group);
|
||||
}
|
||||
}
|
||||
for (ref of state.cachedFavorites.values()) {
|
||||
for (ref of cachedFavorites.values()) {
|
||||
ref.$groupRef = null;
|
||||
if (ref.$isDeleted) {
|
||||
continue;
|
||||
@@ -1054,7 +974,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
* @returns {any}
|
||||
*/
|
||||
function applyFavoriteCached(json) {
|
||||
let ref = state.cachedFavorites.get(json.id);
|
||||
let ref = cachedFavorites.get(json.id);
|
||||
if (typeof ref === 'undefined') {
|
||||
ref = {
|
||||
id: '',
|
||||
@@ -1069,7 +989,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
//
|
||||
...json
|
||||
};
|
||||
state.cachedFavorites.set(ref.id, ref);
|
||||
cachedFavorites.set(ref.id, ref);
|
||||
state.cachedFavoritesByObjectId.set(ref.favoriteId, ref);
|
||||
if (
|
||||
ref.type === 'friend' &&
|
||||
@@ -1103,7 +1023,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
*
|
||||
*/
|
||||
function deleteExpiredFavorites() {
|
||||
for (const ref of state.cachedFavorites.values()) {
|
||||
for (const ref of cachedFavorites.values()) {
|
||||
if (ref.$isDeleted || ref.$isExpired === false) {
|
||||
continue;
|
||||
}
|
||||
@@ -1141,7 +1061,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
avatar: [0, favoriteRequest.getFavoriteAvatars]
|
||||
};
|
||||
const tags = [];
|
||||
for (const ref of state.cachedFavorites.values()) {
|
||||
for (const ref of cachedFavorites.values()) {
|
||||
if (ref.$isDeleted) {
|
||||
continue;
|
||||
}
|
||||
@@ -1187,9 +1107,6 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.clearBulkFavoriteSelection`
|
||||
*/
|
||||
function clearBulkFavoriteSelection() {
|
||||
let ctx;
|
||||
for (ctx of state.favoriteFriends_) {
|
||||
@@ -1216,7 +1133,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.getLocalWorldFavoriteGroupLength`
|
||||
*
|
||||
* @param {string} group
|
||||
* @returns {*|number}
|
||||
*/
|
||||
@@ -1229,7 +1146,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.addLocalWorldFavorite`
|
||||
*
|
||||
* @param {string} worldId
|
||||
* @param {string} group
|
||||
*/
|
||||
@@ -1271,7 +1188,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.hasLocalWorldFavorite`
|
||||
*
|
||||
* @param {string} worldId
|
||||
* @param {string} group
|
||||
* @returns {boolean}
|
||||
@@ -1290,7 +1207,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.addLocalAvatarFavorite`
|
||||
*
|
||||
* @param {string} avatarId
|
||||
* @param {string} group
|
||||
*/
|
||||
@@ -1332,7 +1249,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.hasLocalAvatarFavorite`
|
||||
*
|
||||
* @param {string} avatarId
|
||||
* @param {string} group
|
||||
* @returns {boolean}
|
||||
@@ -1351,7 +1268,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.getLocalAvatarFavoriteGroupLength`
|
||||
*
|
||||
* @param {string} group
|
||||
* @returns {*|number}
|
||||
*/
|
||||
@@ -1394,7 +1311,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.deleteLocalAvatarFavoriteGroup`
|
||||
*
|
||||
* @param {string} group
|
||||
*/
|
||||
function deleteLocalAvatarFavoriteGroup(group) {
|
||||
@@ -1465,9 +1382,6 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.sortLocalAvatarFavorites`
|
||||
*/
|
||||
function sortLocalAvatarFavorites() {
|
||||
state.localAvatarFavoriteGroups.sort();
|
||||
if (!appearanceSettingsStore.sortFavorites) {
|
||||
@@ -1481,13 +1395,13 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.renameLocalAvatarFavoriteGroup`
|
||||
*
|
||||
* @param {string} newName
|
||||
* @param {string} group
|
||||
*/
|
||||
function renameLocalAvatarFavoriteGroup(newName, group) {
|
||||
if (state.localAvatarFavoriteGroups.includes(newName)) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: t('prompt.local_favorite_group_rename.message.error', {
|
||||
name: newName
|
||||
}),
|
||||
@@ -1505,12 +1419,12 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.newLocalAvatarFavoriteGroup`
|
||||
*
|
||||
* @param {string} group
|
||||
*/
|
||||
function newLocalAvatarFavoriteGroup(group) {
|
||||
if (state.localAvatarFavoriteGroups.includes(group)) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: t('prompt.new_local_favorite_group.message.error', {
|
||||
name: group
|
||||
}),
|
||||
@@ -1528,52 +1442,56 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.getLocalAvatarFavorites`
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function getLocalAvatarFavorites() {
|
||||
let ref;
|
||||
let i;
|
||||
state.localAvatarFavoriteGroups = [];
|
||||
state.localAvatarFavoritesList = [];
|
||||
state.localAvatarFavorites = {};
|
||||
const localGroups = new Set();
|
||||
const localListSet = new Set();
|
||||
const localFavorites = Object.create(null);
|
||||
|
||||
const avatarCache = await database.getAvatarCache();
|
||||
for (i = 0; i < avatarCache.length; ++i) {
|
||||
ref = avatarCache[i];
|
||||
for (let i = 0; i < avatarCache.length; ++i) {
|
||||
const ref = avatarCache[i];
|
||||
if (!avatarStore.cachedAvatars.has(ref.id)) {
|
||||
avatarStore.applyAvatar(ref);
|
||||
}
|
||||
}
|
||||
|
||||
const favorites = await database.getAvatarFavorites();
|
||||
for (i = 0; i < favorites.length; ++i) {
|
||||
for (let i = 0; i < favorites.length; ++i) {
|
||||
const favorite = favorites[i];
|
||||
if (!state.localAvatarFavoritesList.includes(favorite.avatarId)) {
|
||||
state.localAvatarFavoritesList.push(favorite.avatarId);
|
||||
|
||||
localListSet.add(favorite.avatarId);
|
||||
|
||||
if (!localFavorites[favorite.groupName]) {
|
||||
localFavorites[favorite.groupName] = [];
|
||||
}
|
||||
if (!state.localAvatarFavorites[favorite.groupName]) {
|
||||
state.localAvatarFavorites[favorite.groupName] = [];
|
||||
}
|
||||
if (!state.localAvatarFavoriteGroups.includes(favorite.groupName)) {
|
||||
state.localAvatarFavoriteGroups.push(favorite.groupName);
|
||||
}
|
||||
ref = avatarStore.cachedAvatars.get(favorite.avatarId);
|
||||
localGroups.add(favorite.groupName);
|
||||
|
||||
let ref = avatarStore.cachedAvatars.get(favorite.avatarId);
|
||||
if (typeof ref === 'undefined') {
|
||||
ref = {
|
||||
id: favorite.avatarId
|
||||
};
|
||||
ref = { id: favorite.avatarId };
|
||||
}
|
||||
state.localAvatarFavorites[favorite.groupName].unshift(ref);
|
||||
localFavorites[favorite.groupName].push(ref);
|
||||
}
|
||||
if (state.localAvatarFavoriteGroups.length === 0) {
|
||||
|
||||
let groupsArr = Array.from(localGroups);
|
||||
if (groupsArr.length === 0) {
|
||||
// default group
|
||||
state.localAvatarFavorites.Favorites = [];
|
||||
state.localAvatarFavoriteGroups.push('Favorites');
|
||||
localFavorites.Favorites = [];
|
||||
groupsArr = ['Favorites'];
|
||||
}
|
||||
|
||||
state.localAvatarFavoriteGroups = groupsArr;
|
||||
state.localAvatarFavoritesList = Array.from(localListSet);
|
||||
state.localAvatarFavorites = localFavorites;
|
||||
|
||||
sortLocalAvatarFavorites();
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.removeLocalAvatarFavorite`
|
||||
*
|
||||
* @param {string} avatarId
|
||||
* @param {string} group
|
||||
*/
|
||||
@@ -1631,7 +1549,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.deleteLocalWorldFavoriteGroup`
|
||||
*
|
||||
* @param {string} group
|
||||
*/
|
||||
function deleteLocalWorldFavoriteGroup(group) {
|
||||
@@ -1671,9 +1589,6 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.sortLocalWorldFavorites`
|
||||
*/
|
||||
function sortLocalWorldFavorites() {
|
||||
state.localWorldFavoriteGroups.sort();
|
||||
if (!appearanceSettingsStore.sortFavorites) {
|
||||
@@ -1687,13 +1602,13 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.renameLocalWorldFavoriteGroup`
|
||||
*
|
||||
* @param {string} newName
|
||||
* @param {string} group
|
||||
*/
|
||||
function renameLocalWorldFavoriteGroup(newName, group) {
|
||||
if (state.localWorldFavoriteGroups.includes(newName)) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: t('prompt.local_favorite_group_rename.message.error', {
|
||||
name: newName
|
||||
}),
|
||||
@@ -1711,7 +1626,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.removeLocalWorldFavorite`
|
||||
*
|
||||
* @param {string} worldId
|
||||
* @param {string} group
|
||||
*/
|
||||
@@ -1767,13 +1682,14 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.getLocalWorldFavorites`
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function getLocalWorldFavorites() {
|
||||
state.localWorldFavoriteGroups = [];
|
||||
state.localWorldFavoritesList = [];
|
||||
state.localWorldFavorites = {};
|
||||
const localGroups = new Set();
|
||||
const localListSet = new Set();
|
||||
const localFavorites = Object.create(null);
|
||||
|
||||
const worldCache = await database.getWorldCache();
|
||||
for (let i = 0; i < worldCache.length; ++i) {
|
||||
const ref = worldCache[i];
|
||||
@@ -1781,41 +1697,46 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
worldStore.applyWorld(ref);
|
||||
}
|
||||
}
|
||||
|
||||
const favorites = await database.getWorldFavorites();
|
||||
for (let i = 0; i < favorites.length; ++i) {
|
||||
const favorite = favorites[i];
|
||||
if (!state.localWorldFavoritesList.includes(favorite.worldId)) {
|
||||
state.localWorldFavoritesList.push(favorite.worldId);
|
||||
}
|
||||
if (!state.localWorldFavorites[favorite.groupName]) {
|
||||
state.localWorldFavorites[favorite.groupName] = [];
|
||||
}
|
||||
if (!state.localWorldFavoriteGroups.includes(favorite.groupName)) {
|
||||
state.localWorldFavoriteGroups.push(favorite.groupName);
|
||||
|
||||
localListSet.add(favorite.worldId);
|
||||
|
||||
if (!localFavorites[favorite.groupName]) {
|
||||
localFavorites[favorite.groupName] = [];
|
||||
}
|
||||
localGroups.add(favorite.groupName);
|
||||
|
||||
let ref = worldStore.cachedWorlds.get(favorite.worldId);
|
||||
if (typeof ref === 'undefined') {
|
||||
ref = {
|
||||
id: favorite.worldId
|
||||
};
|
||||
ref = { id: favorite.worldId };
|
||||
}
|
||||
state.localWorldFavorites[favorite.groupName].unshift(ref);
|
||||
localFavorites[favorite.groupName].push(ref);
|
||||
}
|
||||
if (state.localWorldFavoriteGroups.length === 0) {
|
||||
|
||||
let groupsArr = Array.from(localGroups);
|
||||
if (groupsArr.length === 0) {
|
||||
localFavorites.Favorites = [];
|
||||
// default group
|
||||
state.localWorldFavorites.Favorites = [];
|
||||
state.localWorldFavoriteGroups.push('Favorites');
|
||||
groupsArr = ['Favorites'];
|
||||
}
|
||||
|
||||
state.localWorldFavoriteGroups = groupsArr;
|
||||
state.localWorldFavoritesList = Array.from(localListSet);
|
||||
state.localWorldFavorites = localFavorites;
|
||||
|
||||
sortLocalWorldFavorites();
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.newLocalWorldFavoriteGroup`
|
||||
*
|
||||
* @param {string} group
|
||||
*/
|
||||
function newLocalWorldFavoriteGroup(group) {
|
||||
if (state.localWorldFavoriteGroups.includes(group)) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: t('prompt.new_local_favorite_group.message.error', {
|
||||
name: group
|
||||
}),
|
||||
@@ -1833,7 +1754,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.methods.deleteFavoriteNoConfirm`
|
||||
*
|
||||
* @param {string} objectId
|
||||
*/
|
||||
function deleteFavoriteNoConfirm(objectId) {
|
||||
@@ -1898,17 +1819,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
localAvatarFavoritesList,
|
||||
localAvatarFavoriteGroups,
|
||||
favoriteDialog,
|
||||
favoriteObjects,
|
||||
localWorldFavoritesList,
|
||||
favoriteFriends_,
|
||||
favoriteFriendsSorted,
|
||||
favoriteWorlds_,
|
||||
favoriteWorldsSorted,
|
||||
favoriteAvatars_,
|
||||
favoriteAvatarsSorted,
|
||||
sortFavoriteFriends,
|
||||
sortFavoriteWorlds,
|
||||
sortFavoriteAvatars,
|
||||
cachedFavoritesByObjectId,
|
||||
localWorldFavoriteGroups,
|
||||
groupedByGroupKeyFavoriteFriends,
|
||||
|
||||
+2
-1
@@ -25,7 +25,7 @@ export const useFeedStore = defineStore('Feed', () => {
|
||||
filter: [],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: {
|
||||
prop: 'created_at',
|
||||
order: 'descending'
|
||||
@@ -229,6 +229,7 @@ export const useFeedStore = defineStore('Feed', () => {
|
||||
|
||||
return {
|
||||
state,
|
||||
|
||||
feedTable,
|
||||
feedSessionTable,
|
||||
initFeedTable,
|
||||
|
||||
+98
-84
@@ -1,11 +1,11 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import { friendRequest, userRequest } from '../api';
|
||||
import { $app } from '../app';
|
||||
import configRepository from '../service/config';
|
||||
import { database } from '../service/database';
|
||||
import { AppGlobal } from '../service/appConfig';
|
||||
import { AppDebug } from '../service/appConfig';
|
||||
import { reconnectWebSocket } from '../service/websocket';
|
||||
import { watchState } from '../service/watchState';
|
||||
import {
|
||||
@@ -30,7 +30,7 @@ import { useSharedFeedStore } from './sharedFeed';
|
||||
import { useUiStore } from './ui';
|
||||
import { useUpdateLoopStore } from './updateLoop';
|
||||
import { useUserStore } from './user';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export const useFriendStore = defineStore('Friend', () => {
|
||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||
@@ -58,7 +58,6 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
localFavoriteFriends: new Set(),
|
||||
isRefreshFriendsLoading: false,
|
||||
onlineFriendCount: 0,
|
||||
friendLog: new Map(),
|
||||
friendLogTable: {
|
||||
data: [],
|
||||
filters: [
|
||||
@@ -81,7 +80,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: {
|
||||
prop: 'created_at',
|
||||
order: 'descending'
|
||||
@@ -97,6 +96,26 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
friendNumber: 0
|
||||
});
|
||||
|
||||
let friendLog = new Map();
|
||||
|
||||
const friends = computed({
|
||||
get() {
|
||||
return state.friends;
|
||||
},
|
||||
set(value) {
|
||||
state.friends = value;
|
||||
}
|
||||
});
|
||||
|
||||
const localFavoriteFriends = computed({
|
||||
get() {
|
||||
return state.localFavoriteFriends;
|
||||
},
|
||||
set(value) {
|
||||
state.localFavoriteFriends = value;
|
||||
}
|
||||
});
|
||||
|
||||
async function init() {
|
||||
const friendLogTableFiltersValue = JSON.parse(
|
||||
await configRepository.getString('VRCX_friendLogTableFilters', '[]')
|
||||
@@ -106,8 +125,6 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
|
||||
init();
|
||||
|
||||
const friends = state.friends;
|
||||
|
||||
// friends_(array) may not have change records in pinia because does not use action
|
||||
const onlineFriends_ = computed({
|
||||
get() {
|
||||
@@ -173,7 +190,6 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
state.sortOfflineFriends = value;
|
||||
}
|
||||
});
|
||||
const localFavoriteFriends = state.localFavoriteFriends;
|
||||
|
||||
// VIP friends
|
||||
const vipFriends = computed(() => {
|
||||
@@ -247,15 +263,6 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const friendLog = computed({
|
||||
get() {
|
||||
return state.friendLog;
|
||||
},
|
||||
set(value) {
|
||||
state.friendLog = value;
|
||||
}
|
||||
});
|
||||
|
||||
const friendLogTable = computed({
|
||||
get() {
|
||||
return state.friendLogTable;
|
||||
@@ -270,7 +277,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
(isLoggedIn) => {
|
||||
state.friends.clear();
|
||||
state.friendNumber = 0;
|
||||
state.friendLog.clear();
|
||||
friendLog.clear();
|
||||
state.friendLogTable.data = [];
|
||||
groupStore.groupInstances = [];
|
||||
state.vipFriends_ = [];
|
||||
@@ -343,7 +350,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
if (
|
||||
watchState.isFriendsLoaded &&
|
||||
ref.isFriend &&
|
||||
!state.friendLog.has(ref.id) &&
|
||||
!friendLog.has(ref.id) &&
|
||||
ref.id !== userStore.currentUser.id
|
||||
) {
|
||||
addFriendship(ref.id);
|
||||
@@ -429,7 +436,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
}
|
||||
}
|
||||
if (stateInput === 'online') {
|
||||
if (AppGlobal.debugFriendState && ctx.pendingOffline) {
|
||||
if (AppDebug.debugFriendState && ctx.pendingOffline) {
|
||||
const time = (Date.now() - ctx.pendingOfflineTime) / 1000;
|
||||
console.log(`${ctx.name} pendingOfflineCancelTime ${time}`);
|
||||
}
|
||||
@@ -534,12 +541,12 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
}
|
||||
// prevent status flapping
|
||||
if (ctx.pendingOffline) {
|
||||
if (AppGlobal.debugFriendState) {
|
||||
if (AppDebug.debugFriendState) {
|
||||
console.log(ctx.name, 'pendingOfflineAlreadyWaiting');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (AppGlobal.debugFriendState) {
|
||||
if (AppDebug.debugFriendState) {
|
||||
console.log(ctx.name, 'pendingOfflineBegin');
|
||||
}
|
||||
ctx.pendingOffline = true;
|
||||
@@ -547,7 +554,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
// wait 2minutes then check if user came back online
|
||||
workerTimers.setTimeout(() => {
|
||||
if (!ctx.pendingOffline) {
|
||||
if (AppGlobal.debugFriendState) {
|
||||
if (AppDebug.debugFriendState) {
|
||||
console.log(ctx.name, 'pendingOfflineAlreadyCancelled');
|
||||
}
|
||||
return;
|
||||
@@ -555,7 +562,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
ctx.pendingOffline = false;
|
||||
ctx.pendingOfflineTime = '';
|
||||
if (ctx.pendingState === ctx.state) {
|
||||
if (AppGlobal.debugFriendState) {
|
||||
if (AppDebug.debugFriendState) {
|
||||
console.log(
|
||||
ctx.name,
|
||||
'pendingOfflineCancelledStateMatched'
|
||||
@@ -563,7 +570,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (AppGlobal.debugFriendState) {
|
||||
if (AppDebug.debugFriendState) {
|
||||
console.log(ctx.name, 'pendingOfflineEnd');
|
||||
}
|
||||
updateFriendDelayedCheck(ctx, location, $location_at);
|
||||
@@ -602,7 +609,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
let worldName;
|
||||
const id = ctx.id;
|
||||
const newState = ctx.pendingState;
|
||||
if (AppGlobal.debugFriendState) {
|
||||
if (AppDebug.debugFriendState) {
|
||||
console.log(
|
||||
`${ctx.name} updateFriendState ${ctx.state} -> ${newState}`
|
||||
);
|
||||
@@ -736,7 +743,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* aka: `$app.refreshFriends`
|
||||
*
|
||||
* @param ref
|
||||
*/
|
||||
function refreshFriendsStatus(ref) {
|
||||
@@ -780,11 +787,11 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
const ref = userStore.cachedUsers.get(id);
|
||||
const isVIP = state.localFavoriteFriends.has(id);
|
||||
let name = '';
|
||||
const friend = state.friendLog.get(id);
|
||||
const friend = friendLog.get(id);
|
||||
if (friend) {
|
||||
name = friend.displayName;
|
||||
}
|
||||
const ctx = {
|
||||
const ctx = reactive({
|
||||
id,
|
||||
state: state_input || 'offline',
|
||||
isVIP,
|
||||
@@ -795,7 +802,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
pendingOfflineTime: '',
|
||||
pendingState: '',
|
||||
$nickName: ''
|
||||
};
|
||||
});
|
||||
if (watchState.isFriendsLoaded) {
|
||||
getUserMemo(id).then((memo) => {
|
||||
if (memo.userId === id) {
|
||||
@@ -809,7 +816,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
});
|
||||
}
|
||||
if (typeof ref === 'undefined') {
|
||||
const friendLogRef = state.friendLog.get(id);
|
||||
const friendLogRef = friendLog.get(id);
|
||||
if (friendLogRef?.displayName) {
|
||||
ctx.name = friendLogRef.displayName;
|
||||
}
|
||||
@@ -961,7 +968,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
}
|
||||
const ref = state.friends.get(friend.id);
|
||||
if (ref?.state !== state_input) {
|
||||
if (AppGlobal.debugFriendState) {
|
||||
if (AppDebug.debugFriendState) {
|
||||
console.log(
|
||||
`Refetching friend state it does not match ${friend.displayName} from ${ref?.state} to ${state_input}`,
|
||||
friend
|
||||
@@ -972,7 +979,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
});
|
||||
friends[i] = args.json;
|
||||
} else if (friend.location === 'traveling') {
|
||||
if (AppGlobal.debugFriendState) {
|
||||
if (AppDebug.debugFriendState) {
|
||||
console.log(
|
||||
'Refetching traveling friend',
|
||||
friend.displayName
|
||||
@@ -995,8 +1002,9 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async function refreshRemainingFriends(friends) {
|
||||
const friendsSet = new Set(friends.map((x) => x.id));
|
||||
for (const userId of userStore.currentUser.friends) {
|
||||
if (!friends.some((x) => x.id === userId)) {
|
||||
if (!friendsSet.has(userId)) {
|
||||
try {
|
||||
if (!watchState.isLoggedIn) {
|
||||
console.error(`User isn't logged in`);
|
||||
@@ -1062,26 +1070,33 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
}
|
||||
|
||||
const data = await database.getAllUserStats(userIds, displayNames);
|
||||
|
||||
const dataByDisplayName = new Map();
|
||||
const friendsByDisplayName = new Map();
|
||||
|
||||
for (const ref of data) {
|
||||
if (ref.displayName && ref.userId) {
|
||||
dataByDisplayName.set(ref.displayName, ref.userId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const ref of state.friends.values()) {
|
||||
if (ref?.ref?.id && ref.ref.displayName) {
|
||||
friendsByDisplayName.set(ref.ref.displayName, ref.id);
|
||||
}
|
||||
}
|
||||
|
||||
const friendListMap = new Map();
|
||||
for (item of data) {
|
||||
if (!item.userId) {
|
||||
// find userId from previous data with matching displayName
|
||||
for (ref of data) {
|
||||
if (ref.displayName === item.displayName && ref.userId) {
|
||||
item.userId = ref.userId;
|
||||
}
|
||||
}
|
||||
item.userId = dataByDisplayName.get(item.displayName);
|
||||
|
||||
// if still no userId, find userId from friends list
|
||||
if (!item.userId) {
|
||||
for (ref of state.friends.values()) {
|
||||
if (
|
||||
ref?.ref?.id &&
|
||||
ref.ref.displayName === item.displayName
|
||||
) {
|
||||
item.userId = ref.id;
|
||||
}
|
||||
}
|
||||
item.userId = friendsByDisplayName.get(item.displayName);
|
||||
}
|
||||
|
||||
// if still no userId, skip
|
||||
if (!item.userId) {
|
||||
continue;
|
||||
@@ -1118,7 +1133,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
function addFriendship(id) {
|
||||
if (
|
||||
!watchState.isFriendsLoaded ||
|
||||
state.friendLog.has(id) ||
|
||||
friendLog.has(id) ||
|
||||
id === userStore.currentUser.id
|
||||
) {
|
||||
return;
|
||||
@@ -1139,7 +1154,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
return;
|
||||
}
|
||||
handleFriendStatus(args);
|
||||
if (args.json.isFriend && !state.friendLog.has(id)) {
|
||||
if (args.json.isFriend && !friendLog.has(id)) {
|
||||
if (state.friendNumber === 0) {
|
||||
state.friendNumber = state.friends.size;
|
||||
}
|
||||
@@ -1165,7 +1180,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
trustLevel: ref.$trustLevel,
|
||||
friendNumber: ref.$friendNumber
|
||||
};
|
||||
state.friendLog.set(id, friendLogCurrent);
|
||||
friendLog.set(id, friendLogCurrent);
|
||||
database.setFriendLogCurrent(friendLogCurrent);
|
||||
uiStore.notifyMenu('friendLog');
|
||||
deleteFriendRequest(id);
|
||||
@@ -1208,7 +1223,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
* @param {string} id
|
||||
*/
|
||||
function deleteFriendship(id) {
|
||||
const ctx = state.friendLog.get(id);
|
||||
const ctx = friendLog.get(id);
|
||||
if (typeof ctx === 'undefined') {
|
||||
return;
|
||||
}
|
||||
@@ -1223,7 +1238,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
return;
|
||||
}
|
||||
handleFriendStatus(args);
|
||||
if (!args.json.isFriend && state.friendLog.has(id)) {
|
||||
if (!args.json.isFriend && friendLog.has(id)) {
|
||||
const friendLogHistory = {
|
||||
created_at: new Date().toJSON(),
|
||||
type: 'Unfriend',
|
||||
@@ -1233,7 +1248,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
state.friendLogTable.data.push(friendLogHistory);
|
||||
database.addFriendLogHistory(friendLogHistory);
|
||||
notificationStore.queueFriendLogNoty(friendLogHistory);
|
||||
state.friendLog.delete(id);
|
||||
friendLog.delete(id);
|
||||
database.deleteFriendLogCurrent(id);
|
||||
if (!appearanceSettingsStore.hideUnfriends) {
|
||||
uiStore.notifyMenu('friendLog');
|
||||
@@ -1255,9 +1270,9 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
set.add(id);
|
||||
addFriendship(id);
|
||||
}
|
||||
for (id of state.friendLog.keys()) {
|
||||
for (id of friendLog.keys()) {
|
||||
if (id === userStore.currentUser.id) {
|
||||
state.friendLog.delete(id);
|
||||
friendLog.delete(id);
|
||||
database.deleteFriendLogCurrent(id);
|
||||
} else if (!set.has(id)) {
|
||||
deleteFriendship(id);
|
||||
@@ -1270,7 +1285,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
* @param {object} ref
|
||||
*/
|
||||
function updateFriendship(ref) {
|
||||
const ctx = state.friendLog.get(ref.id);
|
||||
const ctx = friendLog.get(ref.id);
|
||||
if (!watchState.isFriendsLoaded || typeof ctx === 'undefined') {
|
||||
return;
|
||||
}
|
||||
@@ -1301,7 +1316,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
trustLevel: ref.$trustLevel,
|
||||
friendNumber: ref.$friendNumber
|
||||
};
|
||||
state.friendLog.set(ref.id, friendLogCurrent);
|
||||
friendLog.set(ref.id, friendLogCurrent);
|
||||
database.setFriendLogCurrent(friendLogCurrent);
|
||||
ctx.displayName = ref.displayName;
|
||||
uiStore.notifyMenu('friendLog');
|
||||
@@ -1325,7 +1340,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
trustLevel: ref.$trustLevel,
|
||||
friendNumber: ref.$friendNumber
|
||||
};
|
||||
state.friendLog.set(ref.id, friendLogCurrent3);
|
||||
friendLog.set(ref.id, friendLogCurrent3);
|
||||
database.setFriendLogCurrent(friendLogCurrent3);
|
||||
return;
|
||||
}
|
||||
@@ -1347,7 +1362,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
trustLevel: ref.$trustLevel,
|
||||
friendNumber: ref.$friendNumber
|
||||
};
|
||||
state.friendLog.set(ref.id, friendLogCurrent2);
|
||||
friendLog.set(ref.id, friendLogCurrent2);
|
||||
database.setFriendLogCurrent(friendLogCurrent2);
|
||||
uiStore.notifyMenu('friendLog');
|
||||
sharedFeedStore.updateSharedFeed(true);
|
||||
@@ -1372,7 +1387,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
trustLevel: ref.$trustLevel,
|
||||
friendNumber: 0
|
||||
};
|
||||
state.friendLog.set(friend.id, row);
|
||||
friendLog.set(friend.id, row);
|
||||
sqlValues.unshift(row);
|
||||
}
|
||||
database.setFriendLogCurrentArray(sqlValues);
|
||||
@@ -1414,7 +1429,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
|
||||
const friendLogCurrentArray = await database.getFriendLogCurrent();
|
||||
for (friend of friendLogCurrentArray) {
|
||||
state.friendLog.set(friend.userId, friend);
|
||||
friendLog.set(friend.userId, friend);
|
||||
}
|
||||
refreshFriendsStatus(currentUser);
|
||||
|
||||
@@ -1443,12 +1458,12 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
* @param {string} userId
|
||||
*/
|
||||
function setFriendNumber(friendNumber, userId) {
|
||||
const ref = state.friendLog.get(userId);
|
||||
const ref = friendLog.get(userId);
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
ref.friendNumber = friendNumber;
|
||||
state.friendLog.set(ref.userId, ref);
|
||||
friendLog.set(ref.userId, ref);
|
||||
database.setFriendLogCurrent(ref);
|
||||
const friendRef = state.friends.get(userId);
|
||||
if (friendRef?.ref) {
|
||||
@@ -1467,7 +1482,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
|
||||
// reset friendNumber and friendLog
|
||||
state.friendNumber = 0;
|
||||
for (const ref of state.friendLog.values()) {
|
||||
for (const ref of friendLog.values()) {
|
||||
ref.friendNumber = 0;
|
||||
}
|
||||
|
||||
@@ -1504,7 +1519,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
}
|
||||
var status = false;
|
||||
state.friendNumber = 0;
|
||||
for (const ref of state.friendLog.values()) {
|
||||
for (const ref of friendLog.values()) {
|
||||
ref.friendNumber = 0;
|
||||
}
|
||||
try {
|
||||
@@ -1595,7 +1610,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
const friendLogTable = getFriendLogFriendOrder();
|
||||
for (let i = friendLogTable.length - 1; i > -1; i--) {
|
||||
const friendLog = friendLogTable[i];
|
||||
const ref = state.friendLog.get(friendLog.id);
|
||||
const ref = friendLog.get(friendLog.id);
|
||||
if (!ref) {
|
||||
continue;
|
||||
}
|
||||
@@ -1603,7 +1618,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
break;
|
||||
}
|
||||
ref.friendNumber = --state.friendNumber;
|
||||
state.friendLog.set(ref.userId, ref);
|
||||
friendLog.set(ref.userId, ref);
|
||||
database.setFriendLogCurrent(ref);
|
||||
const friendRef = state.friends.get(friendLog.id);
|
||||
if (friendRef?.ref) {
|
||||
@@ -1714,7 +1729,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
trustLevel: ref.$trustLevel,
|
||||
friendNumber: i + 1
|
||||
};
|
||||
state.friendLog.set(userId, friendLogCurrent);
|
||||
friendLog.set(userId, friendLogCurrent);
|
||||
database.setFriendLogCurrent(friendLogCurrent);
|
||||
state.friendNumber = i + 1;
|
||||
}
|
||||
@@ -1729,12 +1744,12 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
return;
|
||||
}
|
||||
for (const friendLog of friendLogTable) {
|
||||
const ref = state.friendLog.get(friendLog.id);
|
||||
const ref = friendLog.get(friendLog.id);
|
||||
if (!ref || ref.friendNumber) {
|
||||
continue;
|
||||
}
|
||||
ref.friendNumber = ++state.friendNumber;
|
||||
state.friendLog.set(ref.userId, ref);
|
||||
friendLog.set(ref.userId, ref);
|
||||
database.setFriendLogCurrent(ref);
|
||||
const friendRef = state.friends.get(friendLog.id);
|
||||
if (friendRef?.ref) {
|
||||
@@ -1744,19 +1759,18 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
}
|
||||
|
||||
function confirmDeleteFriend(id) {
|
||||
$app.$confirm('Continue? Unfriend', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Unfriend', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: async (action) => {
|
||||
if (action === 'confirm') {
|
||||
const args = await friendRequest.deleteFriend({
|
||||
userId: id
|
||||
});
|
||||
handleFriendDelete(args);
|
||||
}
|
||||
}
|
||||
});
|
||||
type: 'info'
|
||||
})
|
||||
.then(async () => {
|
||||
const args = await friendRequest.deleteFriend({
|
||||
userId: id
|
||||
});
|
||||
handleFriendDelete(args);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
async function saveSidebarSortOrder() {
|
||||
@@ -1770,7 +1784,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
const userId = userStore.currentUser.id;
|
||||
state.isRefreshFriendsLoading = true;
|
||||
watchState.isFriendsLoaded = false;
|
||||
state.friendLog = new Map();
|
||||
friendLog = new Map();
|
||||
initFriendLogHistoryTable();
|
||||
|
||||
try {
|
||||
@@ -1780,8 +1794,8 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
await initFriendLog(userStore.currentUser);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!AppGlobal.dontLogMeOut) {
|
||||
$app.$message({
|
||||
if (!AppDebug.dontLogMeOut) {
|
||||
ElMessage({
|
||||
message: t('message.friend.load_failed'),
|
||||
type: 'error'
|
||||
});
|
||||
|
||||
+9
-47
@@ -1,6 +1,7 @@
|
||||
import Noty from 'noty';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import {
|
||||
inventoryRequest,
|
||||
@@ -8,8 +9,7 @@ import {
|
||||
vrcPlusIconRequest,
|
||||
vrcPlusImageRequest
|
||||
} from '../api';
|
||||
import { $app } from '../app';
|
||||
import { AppGlobal } from '../service/appConfig';
|
||||
import { AppDebug } from '../service/appConfig';
|
||||
import { watchState } from '../service/watchState';
|
||||
import {
|
||||
getEmojiFileName,
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
getPrintLocalDate
|
||||
} from '../shared/utils';
|
||||
import { useAdvancedSettingsStore } from './settings/advanced';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export const useGalleryStore = defineStore('Gallery', () => {
|
||||
const advancedSettingsStore = useAdvancedSettingsStore();
|
||||
@@ -45,8 +45,6 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
printTable: [],
|
||||
emojiTable: [],
|
||||
inventoryTable: [],
|
||||
previousImagesDialogVisible: false,
|
||||
previousImagesTable: [],
|
||||
fullscreenImageDialog: {
|
||||
visible: false,
|
||||
imageUrl: '',
|
||||
@@ -176,20 +174,6 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const previousImagesDialogVisible = computed({
|
||||
get: () => state.previousImagesDialogVisible,
|
||||
set: (value) => {
|
||||
state.previousImagesDialogVisible = value;
|
||||
}
|
||||
});
|
||||
|
||||
const previousImagesTable = computed({
|
||||
get: () => state.previousImagesTable,
|
||||
set: (value) => {
|
||||
state.previousImagesTable = value;
|
||||
}
|
||||
});
|
||||
|
||||
const fullscreenImageDialog = computed({
|
||||
get: () => state.fullscreenImageDialog,
|
||||
set: (value) => {
|
||||
@@ -200,14 +184,12 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
watch(
|
||||
() => watchState.isLoggedIn,
|
||||
(isLoggedIn) => {
|
||||
state.previousImagesTable = [];
|
||||
state.galleryTable = [];
|
||||
state.VRCPlusIconsTable = [];
|
||||
state.stickerTable = [];
|
||||
state.printTable = [];
|
||||
state.emojiTable = [];
|
||||
state.galleryDialogVisible = false;
|
||||
state.previousImagesDialogVisible = false;
|
||||
state.fullscreenImageDialog.visible = false;
|
||||
if (isLoggedIn) {
|
||||
tryDeleteOldPrints();
|
||||
@@ -290,7 +272,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
}
|
||||
if (files[0].size >= 100000000) {
|
||||
// 100MB
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: t('message.file.too_large'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -298,7 +280,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
return;
|
||||
}
|
||||
if (!files[0].type.match(/image.*/)) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: t('message.file.not_image'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -540,10 +522,10 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
for (const printId of idList) {
|
||||
await vrcPlusImageRequest.deletePrint(printId);
|
||||
const text = `Old print automatically deleted: ${printId}`;
|
||||
if (AppGlobal.errorNoty) {
|
||||
AppGlobal.errorNoty.close();
|
||||
if (AppDebug.errorNoty) {
|
||||
AppDebug.errorNoty.close();
|
||||
}
|
||||
AppGlobal.errorNoty = new Noty({
|
||||
AppDebug.errorNoty = new Noty({
|
||||
type: 'info',
|
||||
text
|
||||
}).show();
|
||||
@@ -554,24 +536,6 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
await refreshPrintTable();
|
||||
}
|
||||
|
||||
async function checkPreviousImageAvailable(images) {
|
||||
state.previousImagesTable = [];
|
||||
for (const image of images) {
|
||||
if (image.file && image.file.url) {
|
||||
const response = await fetch(image.file.url, {
|
||||
method: 'HEAD',
|
||||
redirect: 'follow'
|
||||
}).catch((error) => {
|
||||
console.error('Failed to check image availability:', error);
|
||||
return null;
|
||||
});
|
||||
if (response && response.status === 200) {
|
||||
state.previousImagesTable.push(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showFullscreenImageDialog(imageUrl, fileName) {
|
||||
if (!imageUrl) {
|
||||
return;
|
||||
@@ -656,6 +620,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
|
||||
return {
|
||||
state,
|
||||
|
||||
galleryTable,
|
||||
galleryDialogVisible,
|
||||
galleryDialogGalleryLoading,
|
||||
@@ -673,8 +638,6 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
printTable,
|
||||
emojiTable,
|
||||
inventoryTable,
|
||||
previousImagesDialogVisible,
|
||||
previousImagesTable,
|
||||
fullscreenImageDialog,
|
||||
|
||||
showGalleryDialog,
|
||||
@@ -689,7 +652,6 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
refreshEmojiTable,
|
||||
getInventory,
|
||||
tryDeleteOldPrints,
|
||||
checkPreviousImageAvailable,
|
||||
showFullscreenImageDialog,
|
||||
handleStickerAdd,
|
||||
handleGalleryImageAdd,
|
||||
|
||||
+5
-4
@@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import { $app } from '../app';
|
||||
import configRepository from '../service/config.js';
|
||||
import { database } from '../service/database';
|
||||
import {
|
||||
@@ -158,7 +158,7 @@ export const useGameStore = defineStore('Game', () => {
|
||||
}
|
||||
AppApi.FocusWindow();
|
||||
const message = 'VRChat crashed, attempting to rejoin last instance';
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message,
|
||||
type: 'info'
|
||||
});
|
||||
@@ -255,14 +255,14 @@ export const useGameStore = defineStore('Game', () => {
|
||||
);
|
||||
if (!result) {
|
||||
// failed to set key
|
||||
$app.$alert(
|
||||
ElMessageBox.alert(
|
||||
'VRCX has noticed VRChat debug logging is disabled. VRCX requires debug logging in order to function correctly. Please enable debug logging in VRChat quick menu settings > debug > enable debug logging, then rejoin the instance or restart VRChat.',
|
||||
'Enable debug logging'
|
||||
);
|
||||
console.error('Failed to enable debug logging', result);
|
||||
return;
|
||||
}
|
||||
$app.$alert(
|
||||
ElMessageBox.alert(
|
||||
'VRCX has noticed VRChat debug logging is disabled and automatically re-enabled it. VRCX requires debug logging in order to function correctly.',
|
||||
'Enabled debug logging'
|
||||
);
|
||||
@@ -281,6 +281,7 @@ export const useGameStore = defineStore('Game', () => {
|
||||
|
||||
return {
|
||||
state,
|
||||
|
||||
VRChatUsedCacheSize,
|
||||
VRChatTotalCacheSize,
|
||||
VRChatCacheSizeLoading,
|
||||
|
||||
+10
-8
@@ -1,12 +1,12 @@
|
||||
import dayjs from 'dayjs';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import { userRequest } from '../api';
|
||||
import { $app } from '../app';
|
||||
import configRepository from '../service/config';
|
||||
import { database } from '../service/database';
|
||||
import { AppGlobal } from '../service/appConfig';
|
||||
import { AppDebug } from '../service/appConfig';
|
||||
import gameLogService from '../service/gamelog.js';
|
||||
import { watchState } from '../service/watchState';
|
||||
import {
|
||||
@@ -69,7 +69,7 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
||||
filter: [],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: {
|
||||
prop: 'created_at',
|
||||
order: 'descending'
|
||||
@@ -627,7 +627,7 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
||||
// set $location_at to join time if user isn't a friend
|
||||
ref.$location_at = joinTime;
|
||||
} else {
|
||||
if (AppGlobal.debugGameLog || AppGlobal.debugWebRequests) {
|
||||
if (AppDebug.debugGameLog || AppDebug.debugWebRequests) {
|
||||
console.log('Fetching user from gameLog:', userId);
|
||||
}
|
||||
userRequest.getUser({ userId });
|
||||
@@ -744,7 +744,7 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
||||
vrcxStore.processScreenshot(gameLog.screenshotPath);
|
||||
break;
|
||||
case 'api-request':
|
||||
if (AppGlobal.debugWebRequests) {
|
||||
if (AppDebug.debugWebRequests) {
|
||||
console.log('API Request:', gameLog.url);
|
||||
}
|
||||
// const userId = '';
|
||||
@@ -1387,7 +1387,7 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
||||
rawLogs.slice(3)
|
||||
);
|
||||
if (
|
||||
AppGlobal.debugGameLog &&
|
||||
AppDebug.debugGameLog &&
|
||||
gameLog.type !== 'photon-id' &&
|
||||
gameLog.type !== 'api-request' &&
|
||||
gameLog.type !== 'udon-exception'
|
||||
@@ -1399,7 +1399,7 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
||||
|
||||
async function disableGameLogDialog() {
|
||||
if (gameStore.isGameRunning) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message:
|
||||
'VRChat needs to be closed before this option can be changed',
|
||||
type: 'error'
|
||||
@@ -1407,7 +1407,7 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
||||
return;
|
||||
}
|
||||
if (!advancedSettingsStore.gameLogDisabled) {
|
||||
$app.$confirm('Continue? Disable GameLog', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Disable GameLog', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
@@ -1431,11 +1431,13 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
||||
|
||||
return {
|
||||
state,
|
||||
|
||||
nowPlaying,
|
||||
gameLogTable,
|
||||
gameLogSessionTable,
|
||||
lastVideoUrl,
|
||||
lastResourceloadUrl,
|
||||
|
||||
initGameLogTable,
|
||||
clearNowPlaying,
|
||||
tryLoadPlayerList,
|
||||
|
||||
+28
-30
@@ -1,5 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { computed, reactive, watch, nextTick } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import {
|
||||
groupRequest,
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
userRequest,
|
||||
worldRequest
|
||||
} from '../api';
|
||||
import { $app } from '../app';
|
||||
import configRepository from '../service/config';
|
||||
import { watchState } from '../service/watchState';
|
||||
import { database } from '../service/database.js';
|
||||
@@ -83,12 +83,13 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
auditLogTypes: [],
|
||||
openWithUserId: ''
|
||||
},
|
||||
cachedGroups: new Map(),
|
||||
inGameGroupOrder: [],
|
||||
groupInstances: [],
|
||||
currentUserGroupsInit: false
|
||||
});
|
||||
|
||||
let cachedGroups = new Map();
|
||||
|
||||
const groupDialog = computed({
|
||||
get: () => state.groupDialog,
|
||||
set: (value) => {
|
||||
@@ -124,13 +125,6 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const cachedGroups = computed({
|
||||
get: () => state.cachedGroups,
|
||||
set: (value) => {
|
||||
state.cachedGroups = value;
|
||||
}
|
||||
});
|
||||
|
||||
const inGameGroupOrder = computed({
|
||||
get: () => state.inGameGroupOrder,
|
||||
set: (value) => {
|
||||
@@ -160,7 +154,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
state.moderateGroupDialog.visible = false;
|
||||
state.groupMemberModeration.visible = false;
|
||||
state.currentUserGroupsInit = false;
|
||||
state.cachedGroups.clear();
|
||||
cachedGroups.clear();
|
||||
state.currentUserGroups.clear();
|
||||
if (isLoggedIn) {
|
||||
initUserGroups();
|
||||
@@ -198,7 +192,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
.catch((err) => {
|
||||
D.loading = false;
|
||||
D.visible = false;
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: 'Failed to load group',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -506,7 +500,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
});
|
||||
}
|
||||
}
|
||||
$app.$nextTick(() => (D.isGetGroupDialogGroupLoading = false));
|
||||
nextTick(() => (D.isGetGroupDialogGroupLoading = false));
|
||||
return args;
|
||||
});
|
||||
}
|
||||
@@ -566,16 +560,19 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
}
|
||||
|
||||
function leaveGroupPrompt(groupId) {
|
||||
$app.$confirm('Are you sure you want to leave this group?', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
leaveGroup(groupId);
|
||||
}
|
||||
ElMessageBox.confirm(
|
||||
'Are you sure you want to leave this group?',
|
||||
'Confirm',
|
||||
{
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info'
|
||||
}
|
||||
});
|
||||
)
|
||||
.then(() => {
|
||||
leaveGroup(groupId);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function updateGroupPostSearch() {
|
||||
@@ -602,7 +599,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
})
|
||||
.then((args) => {
|
||||
handleGroupMemberProps(args);
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: 'Group visibility updated',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -616,7 +613,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
* @returns {object} ref
|
||||
*/
|
||||
function applyGroup(json) {
|
||||
let ref = state.cachedGroups.get(json.id);
|
||||
let ref = cachedGroups.get(json.id);
|
||||
if (json.rules) {
|
||||
json.rules = replaceBioSymbols(json.rules);
|
||||
}
|
||||
@@ -684,7 +681,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
$languages: [],
|
||||
...json
|
||||
};
|
||||
state.cachedGroups.set(ref.id, ref);
|
||||
cachedGroups.set(ref.id, ref);
|
||||
} else {
|
||||
if (state.currentUserGroups.has(ref.id)) {
|
||||
// compare group props
|
||||
@@ -845,7 +842,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
const json = args.json;
|
||||
for (const groupId in json) {
|
||||
const permissions = json[groupId];
|
||||
const group = state.cachedGroups.get(groupId);
|
||||
const group = cachedGroups.get(groupId);
|
||||
if (group) {
|
||||
group.myMember.permissions = permissions;
|
||||
}
|
||||
@@ -896,7 +893,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
json.$fetchedAt = args.json.fetchedAt;
|
||||
}
|
||||
const instanceRef = instanceStore.applyInstance(json);
|
||||
const groupRef = state.cachedGroups.get(json.ownerId);
|
||||
const groupRef = cachedGroups.get(json.ownerId);
|
||||
if (typeof groupRef === 'undefined') {
|
||||
if (watchState.isFriendsLoaded) {
|
||||
const args = await groupRequest.getGroup({
|
||||
@@ -936,7 +933,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
}
|
||||
// update myMember without fetching member
|
||||
if (json?.userId === userStore.currentUser.id) {
|
||||
ref = state.cachedGroups.get(json.groupId);
|
||||
ref = cachedGroups.get(json.groupId);
|
||||
if (typeof ref !== 'undefined') {
|
||||
const newJson = {
|
||||
id: json.groupId,
|
||||
@@ -980,7 +977,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
'[]'
|
||||
)
|
||||
);
|
||||
state.cachedGroups.clear();
|
||||
cachedGroups.clear();
|
||||
state.currentUserGroups.clear();
|
||||
for (const group of savedGroups) {
|
||||
const json = {
|
||||
@@ -999,7 +996,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
|
||||
if (groups) {
|
||||
const promises = groups.map(async (groupId) => {
|
||||
const groupRef = state.cachedGroups.get(groupId);
|
||||
const groupRef = cachedGroups.get(groupId);
|
||||
|
||||
if (
|
||||
typeof groupRef !== 'undefined' &&
|
||||
@@ -1096,6 +1093,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
|
||||
return {
|
||||
state,
|
||||
|
||||
groupDialog,
|
||||
currentUserGroups,
|
||||
inviteGroupDialog,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user