mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-17 13:53:52 +02:00
677 lines
19 KiB
JavaScript
677 lines
19 KiB
JavaScript
import { reactive, ref, shallowReactive, watch } from 'vue';
|
|
import { defineStore } from 'pinia';
|
|
import { toast } from 'vue-sonner';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import {
|
|
getEmojiFileName,
|
|
getPrintFileName,
|
|
getPrintLocalDate,
|
|
openExternalLink
|
|
} from '../shared/utils';
|
|
import {
|
|
inventoryRequest,
|
|
queryRequest,
|
|
vrcPlusIconRequest,
|
|
vrcPlusImageRequest
|
|
} from '../api';
|
|
import { AppDebug } from '../services/appConfig';
|
|
import { handleImageUploadInput } from '../coordinators/imageUploadCoordinator';
|
|
import { router } from '../plugins/router';
|
|
import { useAdvancedSettingsStore } from './settings/advanced';
|
|
import { useModalStore } from './modal';
|
|
import { watchState } from '../services/watchState';
|
|
|
|
import * as workerTimers from 'worker-timers';
|
|
|
|
export const useGalleryStore = defineStore('Gallery', () => {
|
|
const advancedSettingsStore = useAdvancedSettingsStore();
|
|
const { t } = useI18n();
|
|
const modalStore = useModalStore();
|
|
|
|
const state = reactive({
|
|
printCache: [],
|
|
printQueue: [],
|
|
printQueueWorker: null,
|
|
instanceInventoryCache: [],
|
|
instanceInventoryQueue: [],
|
|
instanceInventoryQueueWorker: null
|
|
});
|
|
|
|
const cachedEmoji = shallowReactive(new Map());
|
|
|
|
const galleryTable = ref([]);
|
|
|
|
const galleryDialogVisible = ref(false);
|
|
|
|
const galleryDialogGalleryLoading = ref(false);
|
|
|
|
const galleryDialogIconsLoading = ref(false);
|
|
|
|
const galleryDialogEmojisLoading = ref(false);
|
|
|
|
const galleryDialogStickersLoading = ref(false);
|
|
|
|
const galleryDialogPrintsLoading = ref(false);
|
|
|
|
const galleryDialogInventoryLoading = ref(false);
|
|
|
|
const uploadImage = ref('');
|
|
|
|
const VRCPlusIconsTable = ref([]);
|
|
|
|
const printUploadNote = ref('');
|
|
|
|
const printCropBorder = ref(true);
|
|
|
|
const stickerTable = ref([]);
|
|
|
|
const instanceStickersCache = ref([]);
|
|
|
|
const printTable = ref([]);
|
|
|
|
const emojiTable = ref([]);
|
|
|
|
const inventoryTable = ref([]);
|
|
|
|
const fullscreenImageDialog = ref({
|
|
visible: false,
|
|
imageUrl: '',
|
|
fileName: ''
|
|
});
|
|
|
|
watch(
|
|
() => watchState.isLoggedIn,
|
|
(isLoggedIn) => {
|
|
cachedEmoji.clear();
|
|
galleryTable.value = [];
|
|
VRCPlusIconsTable.value = [];
|
|
stickerTable.value = [];
|
|
printTable.value = [];
|
|
emojiTable.value = [];
|
|
galleryDialogVisible.value = false;
|
|
fullscreenImageDialog.value.visible = false;
|
|
if (isLoggedIn) {
|
|
tryDeleteOldPrints();
|
|
}
|
|
},
|
|
{ flush: 'sync' }
|
|
);
|
|
|
|
/**
|
|
*
|
|
* @param args
|
|
*/
|
|
function handleFilesList(args) {
|
|
if (args.params.tag === 'gallery') {
|
|
galleryTable.value = args.json.reverse();
|
|
}
|
|
if (args.params.tag === 'icon') {
|
|
VRCPlusIconsTable.value = args.json.reverse();
|
|
}
|
|
if (args.params.tag === 'sticker') {
|
|
stickerTable.value = args.json.reverse();
|
|
galleryDialogStickersLoading.value = false;
|
|
}
|
|
if (args.params.tag === 'emoji') {
|
|
emojiTable.value = args.json.reverse();
|
|
galleryDialogEmojisLoading.value = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param args
|
|
*/
|
|
function handleGalleryImageAdd(args) {
|
|
if (Object.keys(galleryTable.value).length !== 0) {
|
|
galleryTable.value.unshift(args.json);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function showGalleryPage() {
|
|
galleryDialogVisible.value = true;
|
|
if (router.currentRoute.value?.name === 'gallery') {
|
|
loadGalleryData();
|
|
return;
|
|
}
|
|
router.push({ name: 'gallery' });
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function loadGalleryData() {
|
|
refreshGalleryTable();
|
|
refreshVRCPlusIconsTable();
|
|
refreshEmojiTable();
|
|
refreshStickerTable();
|
|
refreshPrintTable();
|
|
getInventory();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function refreshGalleryTable() {
|
|
galleryDialogGalleryLoading.value = true;
|
|
const params = {
|
|
n: 100,
|
|
tag: 'gallery'
|
|
};
|
|
vrcPlusIconRequest
|
|
.getFileList(params)
|
|
.then((args) => handleFilesList(args))
|
|
.catch((error) => {
|
|
console.error('Error fetching gallery files:', error);
|
|
})
|
|
.finally(() => {
|
|
galleryDialogGalleryLoading.value = false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function refreshVRCPlusIconsTable() {
|
|
galleryDialogIconsLoading.value = true;
|
|
const params = {
|
|
n: 100,
|
|
tag: 'icon'
|
|
};
|
|
vrcPlusIconRequest
|
|
.getFileList(params)
|
|
.then((args) => handleFilesList(args))
|
|
.catch((error) => {
|
|
console.error('Error fetching VRC Plus icons:', error);
|
|
})
|
|
.finally(() => {
|
|
galleryDialogIconsLoading.value = false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param e
|
|
*/
|
|
function inviteImageUpload(e) {
|
|
const { file } = handleImageUploadInput(e, {
|
|
inputSelector: null,
|
|
tooLargeMessage: () => t('message.file.too_large'),
|
|
invalidTypeMessage: () => t('message.file.not_image'),
|
|
onClear: clearInviteImageUpload
|
|
});
|
|
if (!file) {
|
|
return;
|
|
}
|
|
const r = new FileReader();
|
|
r.onload = function () {
|
|
uploadImage.value = btoa(r.result);
|
|
};
|
|
r.readAsBinaryString(file);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function clearInviteImageUpload() {
|
|
const buttonList = document.querySelectorAll(
|
|
'.inviteImageUploadButton'
|
|
);
|
|
buttonList.forEach((button) => (button.value = ''));
|
|
uploadImage.value = '';
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function refreshStickerTable() {
|
|
galleryDialogStickersLoading.value = true;
|
|
const params = {
|
|
n: 100,
|
|
tag: 'sticker'
|
|
};
|
|
vrcPlusIconRequest
|
|
.getFileList(params)
|
|
.then((args) => handleFilesList(args))
|
|
.catch((error) => {
|
|
console.error('Error fetching stickers:', error);
|
|
})
|
|
.finally(() => {
|
|
galleryDialogStickersLoading.value = false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param args
|
|
*/
|
|
function handleStickerAdd(args) {
|
|
if (Object.keys(stickerTable.value).length !== 0) {
|
|
stickerTable.value.unshift(args.json);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param displayName
|
|
* @param userId
|
|
* @param inventoryId
|
|
*/
|
|
async function trySaveStickerToFile(displayName, userId, inventoryId) {
|
|
if (instanceStickersCache.value.includes(inventoryId)) {
|
|
return;
|
|
}
|
|
instanceStickersCache.value.push(inventoryId);
|
|
if (instanceStickersCache.value.length > 100) {
|
|
instanceStickersCache.value.shift();
|
|
}
|
|
const args = await queryRequest.fetch('userInventoryItem', {
|
|
inventoryId,
|
|
userId
|
|
});
|
|
|
|
if (
|
|
args.json.itemType !== 'sticker' ||
|
|
!args.json.flags.includes('ugc')
|
|
) {
|
|
// Not a sticker or ugc, skipping
|
|
return;
|
|
}
|
|
const imageUrl = args.json.metadata?.imageUrl ?? args.json.imageUrl;
|
|
const createdAt = args.json.created_at;
|
|
const monthFolder = createdAt.slice(0, 7);
|
|
const fileNameDate = createdAt
|
|
.replace(/:/g, '-')
|
|
.replace(/T/g, '_')
|
|
.replace(/Z/g, '');
|
|
const fileName = `${displayName}_${fileNameDate}_${inventoryId}.png`;
|
|
const filePath = await AppApi.SaveStickerToFile(
|
|
imageUrl,
|
|
advancedSettingsStore.ugcFolderPath,
|
|
monthFolder,
|
|
fileName
|
|
);
|
|
if (filePath) {
|
|
console.log(`Sticker saved to file: ${monthFolder}\\${fileName}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async function refreshPrintTable() {
|
|
galleryDialogPrintsLoading.value = true;
|
|
const params = {
|
|
n: 100
|
|
};
|
|
try {
|
|
const args = await vrcPlusImageRequest.getPrints(params);
|
|
args.json.sort((a, b) => {
|
|
return (
|
|
new Date(b.timestamp).getTime() -
|
|
new Date(a.timestamp).getTime()
|
|
);
|
|
});
|
|
printTable.value = args.json;
|
|
} catch (error) {
|
|
console.error('Error fetching prints:', error);
|
|
} finally {
|
|
galleryDialogPrintsLoading.value = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param printId
|
|
*/
|
|
function queueSavePrintToFile(printId) {
|
|
if (state.printCache.includes(printId)) {
|
|
return;
|
|
}
|
|
state.printCache.push(printId);
|
|
if (state.printCache.length > 100) {
|
|
state.printCache.shift();
|
|
}
|
|
|
|
state.printQueue.push(printId);
|
|
|
|
if (!state.printQueueWorker) {
|
|
state.printQueueWorker = workerTimers.setInterval(() => {
|
|
const printId = state.printQueue.shift();
|
|
if (printId) {
|
|
trySavePrintToFile(printId);
|
|
}
|
|
}, 2_500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param printId
|
|
*/
|
|
async function trySavePrintToFile(printId) {
|
|
const args = await vrcPlusImageRequest.getPrint({ printId });
|
|
const imageUrl = args.json?.files?.image;
|
|
if (!imageUrl) {
|
|
console.error('Print image URL is missing', args);
|
|
return;
|
|
}
|
|
const print = args.json;
|
|
const createdAt = getPrintLocalDate(print);
|
|
try {
|
|
const owner = await queryRequest.fetch('user', {
|
|
userId: print.ownerId
|
|
});
|
|
console.log(
|
|
`Print spawned by ${owner?.json?.displayName} id:${print.id} note:${print.note} authorName:${print.authorName} at:${new Date().toISOString()}`
|
|
);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
const monthFolder = createdAt.toISOString().slice(0, 7);
|
|
const fileName = getPrintFileName(print);
|
|
const filePath = await AppApi.SavePrintToFile(
|
|
imageUrl,
|
|
advancedSettingsStore.ugcFolderPath,
|
|
monthFolder,
|
|
fileName
|
|
);
|
|
if (filePath) {
|
|
console.log(`Print saved to file: ${monthFolder}\\${fileName}`);
|
|
if (advancedSettingsStore.cropInstancePrints) {
|
|
if (!(await AppApi.CropPrintImage(filePath))) {
|
|
console.error('Failed to crop print image');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (state.printQueue.length === 0) {
|
|
workerTimers.clearInterval(state.printQueueWorker);
|
|
state.printQueueWorker = null;
|
|
}
|
|
}
|
|
|
|
// #endregion
|
|
// #region | Emoji
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function refreshEmojiTable() {
|
|
galleryDialogEmojisLoading.value = true;
|
|
const params = {
|
|
n: 100,
|
|
tag: 'emoji'
|
|
};
|
|
vrcPlusIconRequest
|
|
.getFileList(params)
|
|
.then((args) => handleFilesList(args))
|
|
.catch((error) => {
|
|
console.error('Error fetching emojis:', error);
|
|
})
|
|
.finally(() => {
|
|
galleryDialogEmojisLoading.value = false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async function getInventory() {
|
|
inventoryTable.value = [];
|
|
advancedSettingsStore.currentUserInventory.clear();
|
|
const params = {
|
|
n: 100,
|
|
offset: 0,
|
|
order: 'newest'
|
|
};
|
|
galleryDialogInventoryLoading.value = true;
|
|
try {
|
|
for (let i = 0; i < 100; i++) {
|
|
params.offset = i * params.n;
|
|
const args = await inventoryRequest.getInventoryItems(params);
|
|
for (const item of args.json.data) {
|
|
advancedSettingsStore.currentUserInventory.set(
|
|
item.id,
|
|
item
|
|
);
|
|
if (!item.flags.includes('ugc')) {
|
|
inventoryTable.value.push(item);
|
|
}
|
|
}
|
|
if (args.json.data.length === 0) {
|
|
break;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching inventory items:', error);
|
|
} finally {
|
|
galleryDialogInventoryLoading.value = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async function tryDeleteOldPrints() {
|
|
if (!advancedSettingsStore.autoDeleteOldPrints) {
|
|
return;
|
|
}
|
|
await refreshPrintTable();
|
|
const printLimit = 64 - 2; // 2 reserved for new prints
|
|
const printCount = printTable.value.length;
|
|
if (printCount <= printLimit) {
|
|
return;
|
|
}
|
|
const deleteCount = printCount - printLimit;
|
|
if (deleteCount <= 0) {
|
|
return;
|
|
}
|
|
const idList = [];
|
|
for (let i = 0; i < deleteCount; i++) {
|
|
const print = printTable.value[printCount - 1 - i];
|
|
idList.push(print.id);
|
|
}
|
|
console.log(`Deleting ${deleteCount} old prints`, idList);
|
|
try {
|
|
for (const printId of idList) {
|
|
await vrcPlusImageRequest.deletePrint(printId);
|
|
const text = `Old print automatically deleted: ${printId}`;
|
|
if (AppDebug.errorNoty) {
|
|
toast.dismiss(AppDebug.errorNoty);
|
|
}
|
|
AppDebug.errorNoty = toast.info(text);
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to delete old print:', err);
|
|
}
|
|
await refreshPrintTable();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param imageUrl
|
|
* @param fileName
|
|
*/
|
|
function showFullscreenImageDialog(imageUrl, fileName) {
|
|
if (!imageUrl) {
|
|
return;
|
|
}
|
|
const D = fullscreenImageDialog.value;
|
|
D.imageUrl = imageUrl;
|
|
D.fileName = fileName;
|
|
D.visible = true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param inventoryId
|
|
* @param userId
|
|
*/
|
|
function queueCheckInstanceInventory(inventoryId, userId) {
|
|
if (
|
|
state.instanceInventoryCache.includes(inventoryId) ||
|
|
instanceStickersCache.value.includes(inventoryId)
|
|
) {
|
|
return;
|
|
}
|
|
state.instanceInventoryCache.push(inventoryId);
|
|
if (state.instanceInventoryCache.length > 100) {
|
|
state.instanceInventoryCache.shift();
|
|
}
|
|
|
|
state.instanceInventoryQueue.push({ inventoryId, userId });
|
|
|
|
if (!state.instanceInventoryQueueWorker) {
|
|
state.instanceInventoryQueueWorker = workerTimers.setInterval(
|
|
() => {
|
|
const item = state.instanceInventoryQueue.shift();
|
|
if (item?.inventoryId) {
|
|
trySaveEmojiToFile(item.inventoryId, item.userId);
|
|
}
|
|
},
|
|
2_500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param inventoryId
|
|
* @param userId
|
|
*/
|
|
async function trySaveEmojiToFile(inventoryId, userId) {
|
|
const args = await queryRequest.fetch('userInventoryItem', {
|
|
inventoryId,
|
|
userId
|
|
});
|
|
|
|
if (
|
|
args.json.itemType !== 'emoji' ||
|
|
!args.json.flags.includes('ugc')
|
|
) {
|
|
// Not an emoji or ugc, skipping
|
|
return;
|
|
}
|
|
|
|
const userArgs = await queryRequest.fetch('user', {
|
|
userId: args.json.holderId
|
|
});
|
|
const displayName = userArgs.json?.displayName ?? '';
|
|
|
|
const emoji = args.json.metadata;
|
|
emoji.name = `${displayName}_${inventoryId}`;
|
|
|
|
const emojiFileName = getEmojiFileName(emoji);
|
|
const imageUrl = args.json.metadata?.imageUrl ?? args.json.imageUrl;
|
|
const createdAt = args.json.created_at;
|
|
const monthFolder = createdAt.slice(0, 7);
|
|
|
|
try {
|
|
const filePath = await AppApi.SaveEmojiToFile(
|
|
imageUrl,
|
|
advancedSettingsStore.ugcFolderPath,
|
|
monthFolder,
|
|
emojiFileName
|
|
);
|
|
if (filePath) {
|
|
console.log(
|
|
`Emoji saved to file: ${monthFolder}\\${emojiFileName}`
|
|
);
|
|
}
|
|
} catch (e) {
|
|
if (e.message.includes('Could not find file')) {
|
|
modalStore
|
|
.confirm({
|
|
description:
|
|
'Windows has blocked VRCX from creating files on your system. Please allow VRCX to create files to save emojis, would you like to see instructions on how to fix this?',
|
|
title: 'Failed to create emoji folder',
|
|
cancelText: 'Ignore'
|
|
})
|
|
.then(({ ok }) => {
|
|
if (!ok) return;
|
|
openExternalLink(
|
|
'https://www.youtube.com/watch?v=1mwmmCdA4D8&t=213s'
|
|
);
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
console.error('Failed to save emoji to file:', e);
|
|
}
|
|
|
|
if (state.instanceInventoryQueue.length === 0) {
|
|
workerTimers.clearInterval(state.instanceInventoryQueueWorker);
|
|
state.instanceInventoryQueueWorker = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param fileId
|
|
*/
|
|
async function getCachedEmoji(fileId) {
|
|
return new Promise((resolve, reject) => {
|
|
let ref = cachedEmoji.get(fileId);
|
|
if (typeof ref !== 'undefined') {
|
|
resolve(ref);
|
|
return;
|
|
}
|
|
queryRequest
|
|
.fetch('file', { fileId })
|
|
.then((args) => {
|
|
cachedEmoji.set(fileId, args.json);
|
|
resolve(args.json);
|
|
})
|
|
.catch(reject);
|
|
});
|
|
}
|
|
|
|
return {
|
|
state,
|
|
|
|
galleryTable,
|
|
galleryDialogVisible,
|
|
galleryDialogGalleryLoading,
|
|
galleryDialogIconsLoading,
|
|
galleryDialogEmojisLoading,
|
|
galleryDialogStickersLoading,
|
|
galleryDialogPrintsLoading,
|
|
galleryDialogInventoryLoading,
|
|
uploadImage,
|
|
VRCPlusIconsTable,
|
|
printUploadNote,
|
|
printCropBorder,
|
|
stickerTable,
|
|
instanceStickersCache,
|
|
printTable,
|
|
emojiTable,
|
|
inventoryTable,
|
|
fullscreenImageDialog,
|
|
cachedEmoji,
|
|
|
|
showGalleryPage,
|
|
loadGalleryData,
|
|
refreshGalleryTable,
|
|
refreshVRCPlusIconsTable,
|
|
inviteImageUpload,
|
|
clearInviteImageUpload,
|
|
refreshStickerTable,
|
|
trySaveStickerToFile,
|
|
refreshPrintTable,
|
|
queueSavePrintToFile,
|
|
refreshEmojiTable,
|
|
getInventory,
|
|
tryDeleteOldPrints,
|
|
showFullscreenImageDialog,
|
|
handleStickerAdd,
|
|
handleGalleryImageAdd,
|
|
handleFilesList,
|
|
queueCheckInstanceInventory,
|
|
getCachedEmoji
|
|
};
|
|
});
|