mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-16 21:33:51 +02:00
699 lines
21 KiB
JavaScript
699 lines
21 KiB
JavaScript
import Noty from 'noty';
|
|
import { defineStore } from 'pinia';
|
|
import { computed, reactive, watch } from 'vue';
|
|
import * as workerTimers from 'worker-timers';
|
|
import {
|
|
inventoryRequest,
|
|
userRequest,
|
|
vrcPlusIconRequest,
|
|
vrcPlusImageRequest
|
|
} from '../api';
|
|
import { $app } from '../app';
|
|
import { AppGlobal } from '../service/appConfig';
|
|
import { watchState } from '../service/watchState';
|
|
import {
|
|
getEmojiFileName,
|
|
getPrintFileName,
|
|
getPrintLocalDate
|
|
} from '../shared/utils';
|
|
import { useAdvancedSettingsStore } from './settings/advanced';
|
|
import { useI18n } from 'vue-i18n-bridge';
|
|
|
|
export const useGalleryStore = defineStore('Gallery', () => {
|
|
const advancedSettingsStore = useAdvancedSettingsStore();
|
|
const { t } = useI18n();
|
|
|
|
const state = reactive({
|
|
galleryTable: [],
|
|
// galleryDialog: {},
|
|
galleryDialogVisible: false,
|
|
galleryDialogGalleryLoading: false,
|
|
galleryDialogIconsLoading: false,
|
|
galleryDialogEmojisLoading: false,
|
|
galleryDialogStickersLoading: false,
|
|
galleryDialogPrintsLoading: false,
|
|
galleryDialogInventoryLoading: false,
|
|
uploadImage: '',
|
|
VRCPlusIconsTable: [],
|
|
printUploadNote: '',
|
|
printCropBorder: true,
|
|
printCache: [],
|
|
printQueue: [],
|
|
printQueueWorker: null,
|
|
stickerTable: [],
|
|
instanceStickersCache: [],
|
|
printTable: [],
|
|
emojiTable: [],
|
|
inventoryTable: [],
|
|
previousImagesDialogVisible: false,
|
|
previousImagesTable: [],
|
|
fullscreenImageDialog: {
|
|
visible: false,
|
|
imageUrl: '',
|
|
fileName: ''
|
|
},
|
|
instanceInventoryCache: [],
|
|
instanceInventoryQueue: [],
|
|
instanceInventoryQueueWorker: null
|
|
});
|
|
|
|
const galleryTable = computed({
|
|
get: () => state.galleryTable,
|
|
set: (value) => {
|
|
state.galleryTable = value;
|
|
}
|
|
});
|
|
|
|
const galleryDialogVisible = computed({
|
|
get: () => state.galleryDialogVisible,
|
|
set: (value) => {
|
|
state.galleryDialogVisible = value;
|
|
}
|
|
});
|
|
|
|
const galleryDialogGalleryLoading = computed({
|
|
get: () => state.galleryDialogGalleryLoading,
|
|
set: (value) => {
|
|
state.galleryDialogGalleryLoading = value;
|
|
}
|
|
});
|
|
|
|
const galleryDialogIconsLoading = computed({
|
|
get: () => state.galleryDialogIconsLoading,
|
|
set: (value) => {
|
|
state.galleryDialogIconsLoading = value;
|
|
}
|
|
});
|
|
|
|
const galleryDialogEmojisLoading = computed({
|
|
get: () => state.galleryDialogEmojisLoading,
|
|
set: (value) => {
|
|
state.galleryDialogEmojisLoading = value;
|
|
}
|
|
});
|
|
|
|
const galleryDialogStickersLoading = computed({
|
|
get: () => state.galleryDialogStickersLoading,
|
|
set: (value) => {
|
|
state.galleryDialogStickersLoading = value;
|
|
}
|
|
});
|
|
|
|
const galleryDialogPrintsLoading = computed({
|
|
get: () => state.galleryDialogPrintsLoading,
|
|
set: (value) => {
|
|
state.galleryDialogPrintsLoading = value;
|
|
}
|
|
});
|
|
|
|
const galleryDialogInventoryLoading = computed({
|
|
get: () => state.galleryDialogInventoryLoading,
|
|
set: (value) => {
|
|
state.galleryDialogInventoryLoading = value;
|
|
}
|
|
});
|
|
|
|
const uploadImage = computed({
|
|
get: () => state.uploadImage,
|
|
set: (value) => {
|
|
state.uploadImage = value;
|
|
}
|
|
});
|
|
|
|
const VRCPlusIconsTable = computed({
|
|
get: () => state.VRCPlusIconsTable,
|
|
set: (value) => {
|
|
state.VRCPlusIconsTable = value;
|
|
}
|
|
});
|
|
|
|
const printUploadNote = computed({
|
|
get: () => state.printUploadNote,
|
|
set: (value) => {
|
|
state.printUploadNote = value;
|
|
}
|
|
});
|
|
|
|
const printCropBorder = computed({
|
|
get: () => state.printCropBorder,
|
|
set: (value) => {
|
|
state.printCropBorder = value;
|
|
}
|
|
});
|
|
|
|
const stickerTable = computed({
|
|
get: () => state.stickerTable,
|
|
set: (value) => {
|
|
state.stickerTable = value;
|
|
}
|
|
});
|
|
|
|
const instanceStickersCache = computed({
|
|
get: () => state.instanceStickersCache,
|
|
set: (value) => {
|
|
state.instanceStickersCache = value;
|
|
}
|
|
});
|
|
|
|
const printTable = computed({
|
|
get: () => state.printTable,
|
|
set: (value) => {
|
|
state.printTable = value;
|
|
}
|
|
});
|
|
|
|
const emojiTable = computed({
|
|
get: () => state.emojiTable,
|
|
set: (value) => {
|
|
state.emojiTable = value;
|
|
}
|
|
});
|
|
|
|
const inventoryTable = computed({
|
|
get: () => state.inventoryTable,
|
|
set: (value) => {
|
|
state.inventoryTable = value;
|
|
}
|
|
});
|
|
|
|
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) => {
|
|
state.fullscreenImageDialog = value;
|
|
}
|
|
});
|
|
|
|
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();
|
|
}
|
|
},
|
|
{ flush: 'sync' }
|
|
);
|
|
|
|
function handleFilesList(args) {
|
|
if (args.params.tag === 'gallery') {
|
|
state.galleryTable = args.json.reverse();
|
|
}
|
|
if (args.params.tag === 'icon') {
|
|
state.VRCPlusIconsTable = args.json.reverse();
|
|
}
|
|
if (args.params.tag === 'sticker') {
|
|
state.stickerTable = args.json.reverse();
|
|
state.galleryDialogStickersLoading = false;
|
|
}
|
|
if (args.params.tag === 'emoji') {
|
|
state.emojiTable = args.json.reverse();
|
|
state.galleryDialogEmojisLoading = false;
|
|
}
|
|
}
|
|
|
|
function handleGalleryImageAdd(args) {
|
|
if (Object.keys(state.galleryTable).length !== 0) {
|
|
state.galleryTable.unshift(args.json);
|
|
}
|
|
}
|
|
|
|
function showGalleryDialog() {
|
|
state.galleryDialogVisible = true;
|
|
refreshGalleryTable();
|
|
refreshVRCPlusIconsTable();
|
|
refreshEmojiTable();
|
|
refreshStickerTable();
|
|
refreshPrintTable();
|
|
getInventory();
|
|
}
|
|
|
|
function refreshGalleryTable() {
|
|
state.galleryDialogGalleryLoading = true;
|
|
const params = {
|
|
n: 100,
|
|
tag: 'gallery'
|
|
};
|
|
vrcPlusIconRequest
|
|
.getFileList(params)
|
|
.then((args) => handleFilesList(args))
|
|
.catch((error) => {
|
|
console.error('Error fetching gallery files:', error);
|
|
})
|
|
.finally(() => {
|
|
state.galleryDialogGalleryLoading = false;
|
|
});
|
|
}
|
|
|
|
function refreshVRCPlusIconsTable() {
|
|
state.galleryDialogIconsLoading = 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(() => {
|
|
state.galleryDialogIconsLoading = false;
|
|
});
|
|
}
|
|
|
|
function inviteImageUpload(e) {
|
|
const files = e.target.files || e.dataTransfer.files;
|
|
if (!files.length) {
|
|
return;
|
|
}
|
|
if (files[0].size >= 100000000) {
|
|
// 100MB
|
|
$app.$message({
|
|
message: t('message.file.too_large'),
|
|
type: 'error'
|
|
});
|
|
clearInviteImageUpload();
|
|
return;
|
|
}
|
|
if (!files[0].type.match(/image.*/)) {
|
|
$app.$message({
|
|
message: t('message.file.not_image'),
|
|
type: 'error'
|
|
});
|
|
clearInviteImageUpload();
|
|
return;
|
|
}
|
|
const r = new FileReader();
|
|
r.onload = function () {
|
|
state.uploadImage = btoa(r.result);
|
|
};
|
|
r.readAsBinaryString(files[0]);
|
|
}
|
|
|
|
function clearInviteImageUpload() {
|
|
const buttonList = document.querySelectorAll(
|
|
'.inviteImageUploadButton'
|
|
);
|
|
buttonList.forEach((button) => (button.value = ''));
|
|
state.uploadImage = '';
|
|
}
|
|
|
|
function refreshStickerTable() {
|
|
state.galleryDialogStickersLoading = true;
|
|
const params = {
|
|
n: 100,
|
|
tag: 'sticker'
|
|
};
|
|
vrcPlusIconRequest
|
|
.getFileList(params)
|
|
.then((args) => handleFilesList(args))
|
|
.catch((error) => {
|
|
console.error('Error fetching stickers:', error);
|
|
})
|
|
.finally(() => {
|
|
state.galleryDialogStickersLoading = false;
|
|
});
|
|
}
|
|
|
|
function handleStickerAdd(args) {
|
|
if (Object.keys(state.stickerTable).length !== 0) {
|
|
state.stickerTable.unshift(args.json);
|
|
}
|
|
}
|
|
|
|
async function trySaveStickerToFile(displayName, userId, inventoryId) {
|
|
if (state.instanceStickersCache.includes(inventoryId)) {
|
|
return;
|
|
}
|
|
state.instanceStickersCache.push(inventoryId);
|
|
if (state.instanceStickersCache.size > 100) {
|
|
state.instanceStickersCache.shift();
|
|
}
|
|
const args = await inventoryRequest.getUserInventoryItem({
|
|
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() {
|
|
state.galleryDialogPrintsLoading = true;
|
|
const params = {
|
|
n: 100
|
|
};
|
|
try {
|
|
const args = await vrcPlusImageRequest.getPrints(params);
|
|
args.json.sort((a, b) => {
|
|
return new Date(b.timestamp) - new Date(a.timestamp);
|
|
});
|
|
state.printTable = args.json;
|
|
} catch (error) {
|
|
console.error('Error fetching prints:', error);
|
|
} finally {
|
|
state.galleryDialogPrintsLoading = false;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 userRequest.getCachedUser({
|
|
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() {
|
|
state.galleryDialogEmojisLoading = true;
|
|
const params = {
|
|
n: 100,
|
|
tag: 'emoji'
|
|
};
|
|
vrcPlusIconRequest
|
|
.getFileList(params)
|
|
.then((args) => handleFilesList(args))
|
|
.catch((error) => {
|
|
console.error('Error fetching emojis:', error);
|
|
})
|
|
.finally(() => {
|
|
state.galleryDialogEmojisLoading = false;
|
|
});
|
|
}
|
|
|
|
async function getInventory() {
|
|
state.inventoryTable = [];
|
|
advancedSettingsStore.currentUserInventory.clear();
|
|
const params = {
|
|
n: 100,
|
|
offset: 0,
|
|
order: 'newest'
|
|
};
|
|
state.galleryDialogInventoryLoading = 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')) {
|
|
state.inventoryTable.push(item);
|
|
}
|
|
}
|
|
if (args.json.data.length === 0) {
|
|
break;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching inventory items:', error);
|
|
} finally {
|
|
state.galleryDialogInventoryLoading = false;
|
|
}
|
|
}
|
|
|
|
async function tryDeleteOldPrints() {
|
|
if (!advancedSettingsStore.deleteOldPrints) {
|
|
return;
|
|
}
|
|
await refreshPrintTable();
|
|
const printLimit = 64 - 2; // 2 reserved for new prints
|
|
const printCount = state.printTable.length;
|
|
if (printCount <= printLimit) {
|
|
return;
|
|
}
|
|
const deleteCount = printCount - printLimit;
|
|
if (deleteCount <= 0) {
|
|
return;
|
|
}
|
|
const idList = [];
|
|
for (let i = 0; i < deleteCount; i++) {
|
|
const print = state.printTable[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 (AppGlobal.errorNoty) {
|
|
AppGlobal.errorNoty.close();
|
|
}
|
|
AppGlobal.errorNoty = new Noty({
|
|
type: 'info',
|
|
text
|
|
}).show();
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to delete old print:', err);
|
|
}
|
|
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.log(error);
|
|
});
|
|
if (response.status === 200) {
|
|
state.previousImagesTable.push(image);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function showFullscreenImageDialog(imageUrl, fileName) {
|
|
if (!imageUrl) {
|
|
return;
|
|
}
|
|
const D = state.fullscreenImageDialog;
|
|
D.imageUrl = imageUrl;
|
|
D.fileName = fileName;
|
|
D.visible = true;
|
|
}
|
|
|
|
function queueCheckInstanceInventory(inventoryId, userId) {
|
|
if (
|
|
state.instanceInventoryCache.includes(inventoryId) ||
|
|
state.instanceStickersCache.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
|
|
);
|
|
}
|
|
}
|
|
|
|
async function trySaveEmojiToFile(inventoryId, userId) {
|
|
const args = await inventoryRequest.getUserInventoryItem({
|
|
inventoryId,
|
|
userId
|
|
});
|
|
|
|
if (
|
|
args.json.itemType !== 'emoji' ||
|
|
!args.json.flags.includes('ugc')
|
|
) {
|
|
// Not an emoji or ugc, skipping
|
|
return;
|
|
}
|
|
|
|
const userArgs = await userRequest.getCachedUser({
|
|
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);
|
|
|
|
const filePath = await AppApi.SaveEmojiToFile(
|
|
imageUrl,
|
|
advancedSettingsStore.ugcFolderPath,
|
|
monthFolder,
|
|
emojiFileName
|
|
);
|
|
if (filePath) {
|
|
console.log(
|
|
`Emoji saved to file: ${monthFolder}\\${emojiFileName}`
|
|
);
|
|
}
|
|
|
|
if (state.instanceInventoryQueue.length === 0) {
|
|
workerTimers.clearInterval(state.instanceInventoryQueueWorker);
|
|
state.instanceInventoryQueueWorker = null;
|
|
}
|
|
}
|
|
|
|
return {
|
|
state,
|
|
galleryTable,
|
|
galleryDialogVisible,
|
|
galleryDialogGalleryLoading,
|
|
galleryDialogIconsLoading,
|
|
galleryDialogEmojisLoading,
|
|
galleryDialogStickersLoading,
|
|
galleryDialogPrintsLoading,
|
|
galleryDialogInventoryLoading,
|
|
uploadImage,
|
|
VRCPlusIconsTable,
|
|
printUploadNote,
|
|
printCropBorder,
|
|
stickerTable,
|
|
instanceStickersCache,
|
|
printTable,
|
|
emojiTable,
|
|
inventoryTable,
|
|
previousImagesDialogVisible,
|
|
previousImagesTable,
|
|
fullscreenImageDialog,
|
|
|
|
showGalleryDialog,
|
|
refreshGalleryTable,
|
|
refreshVRCPlusIconsTable,
|
|
inviteImageUpload,
|
|
clearInviteImageUpload,
|
|
refreshStickerTable,
|
|
trySaveStickerToFile,
|
|
refreshPrintTable,
|
|
queueSavePrintToFile,
|
|
refreshEmojiTable,
|
|
getInventory,
|
|
tryDeleteOldPrints,
|
|
checkPreviousImageAvailable,
|
|
showFullscreenImageDialog,
|
|
handleStickerAdd,
|
|
handleGalleryImageAdd,
|
|
handleFilesList,
|
|
queueCheckInstanceInventory
|
|
};
|
|
});
|