diff --git a/src/api/index.js b/src/api/index.js index 4928f549..0da1da03 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -21,6 +21,8 @@ import inviteMessagesRequest from './inviteMessages'; import imageRequest from './image'; import miscRequest from './misc'; import groupRequest from './group'; +import inventoryRequest from './inventory'; +import propRequest from './prop'; window.request = { userRequest, @@ -37,7 +39,9 @@ window.request = { inviteMessagesRequest, imageRequest, miscRequest, - groupRequest + groupRequest, + inventoryRequest, + propRequest }; export { @@ -55,5 +59,7 @@ export { inviteMessagesRequest, imageRequest, miscRequest, - groupRequest + groupRequest, + inventoryRequest, + propRequest }; diff --git a/src/api/inventory.js b/src/api/inventory.js new file mode 100644 index 00000000..6b4aaeeb --- /dev/null +++ b/src/api/inventory.js @@ -0,0 +1,74 @@ +const inventoryReq = { + /** + * @param {{ inventoryId: string }} params + * @returns {Promise<{json: any, params}>} + */ + getInventoryItem(params) { + return window.API.call(`inventory/${params.inventoryId}`, { + method: 'GET', + params + }).then((json) => { + const args = { + json, + params + }; + return args; + }); + }, + + /** + * @param {{ n: number, offset: number, order: string, types: string }} params + * @returns {Promise<{json: any, params}>} + */ + getInventoryItems(params) { + return window.API.call('inventory', { + method: 'GET', + params + }).then((json) => { + const args = { + json, + params + }; + return args; + }); + }, + + /** + * @param {{ inventoryId: string }} params + * @returns {Promise<{json: any, params}>} + */ + consumeInventoryBundle(params) { + return window.API.call(`inventory/${params.inventoryId}/consume`, { + method: 'PUT', + params + }).then((json) => { + const args = { + json, + params + }; + return args; + }); + }, + + /** + * @param {{ inventoryTemplateId: string }} params + * @returns {Promise<{json: any, params}>} + */ + getInventoryTemplate(params) { + return window.API.call( + `inventory/template/${params.inventoryTemplateId}`, + { + method: 'GET', + params + } + ).then((json) => { + const args = { + json, + params + }; + return args; + }); + } +}; + +export default inventoryReq; diff --git a/src/api/prop.js b/src/api/prop.js new file mode 100644 index 00000000..939aa2b2 --- /dev/null +++ b/src/api/prop.js @@ -0,0 +1,20 @@ +const propReq = { + /** + * @param {{ propId: string }} params + * @returns {Promise<{json: any, params}>} + */ + getProp(params) { + return window.API.call(`props/${params.propId}`, { + method: 'GET', + params + }).then((json) => { + const args = { + json, + params + }; + return args; + }); + } +}; + +export default propReq; diff --git a/src/app.js b/src/app.js index 589d878e..b94c987b 100644 --- a/src/app.js +++ b/src/app.js @@ -153,6 +153,7 @@ import _languages from './classes/languages.js'; import _groups from './classes/groups.js'; import _vrcRegistry from './classes/vrcRegistry.js'; import _restoreFriendOrder from './classes/restoreFriendOrder.js'; +import _inventory from './classes/inventory.js'; import { userNotes } from './classes/userNotes.js'; @@ -244,7 +245,8 @@ console.log(`isLinux: ${LINUX}`); languages: new _languages($app, API, $t), groups: new _groups($app, API, $t), vrcRegistry: new _vrcRegistry($app, API, $t), - restoreFriendOrder: new _restoreFriendOrder($app, API, $t) + restoreFriendOrder: new _restoreFriendOrder($app, API, $t), + inventory: new _inventory($app, API, $t) }; await configRepository.init(); @@ -2014,6 +2016,7 @@ console.log(`isLinux: ${LINUX}`); this.cachedFavoriteGroups.clear(); this.cachedFavoriteGroupsByTypeName.clear(); this.currentUserGroups.clear(); + this.currentUserInventory.clear(); this.queuedInstances.clear(); this.favoriteFriendGroups = []; this.favoriteWorldGroups = []; @@ -9859,7 +9862,8 @@ console.log(`isLinux: ${LINUX}`); 'stickers', 'pedestals', 'prints', - 'drones' + 'drones', + 'items' ]; $app.methods.createNewInstance = async function (worldId = '', options) { @@ -10803,6 +10807,7 @@ console.log(`isLinux: ${LINUX}`); $app.data.galleryDialogEmojisLoading = false; $app.data.galleryDialogStickersLoading = false; $app.data.galleryDialogPrintsLoading = false; + $app.data.galleryDialogInventoryLoading = false; API.$on('LOGIN', function () { $app.galleryTable = []; @@ -10815,6 +10820,7 @@ console.log(`isLinux: ${LINUX}`); this.refreshEmojiTable(); this.refreshStickerTable(); this.refreshPrintTable(); + this.getInventory(); workerTimers.setTimeout(() => this.setGalleryTab(pageNum), 100); }; diff --git a/src/classes/inventory.js b/src/classes/inventory.js new file mode 100644 index 00000000..d6228ba8 --- /dev/null +++ b/src/classes/inventory.js @@ -0,0 +1,55 @@ +import * as workerTimers from 'worker-timers'; +import configRepository from '../service/config.js'; +import database from '../service/database.js'; +import { baseClass, $app, API, $t, $utils } from './baseClass.js'; +import { inventoryRequest } from '../api'; + +export default class extends baseClass { + constructor(_app, _API, _t) { + super(_app, _API, _t); + } + + init() { + API.currentUserInventory = new Map(); + API.$on('LOGIN', function () { + API.currentUserInventory.clear(); + }); + } + + _data = { + inventoryTable: [] + }; + + _methods = { + async getInventory() { + this.inventoryTable = []; + API.currentUserInventory.clear(); + var params = { + n: 100, + offset: 0, + order: 'newest' + }; + this.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) { + API.currentUserInventory.set(item.id, item); + if (!item.flags.includes('ugc')) { + this.inventoryTable.push(item); + } + } + if (args.json.data.length === 0) { + break; + } + } + } catch (error) { + console.error('Error fetching inventory items:', error); + } finally { + this.galleryDialogInventoryLoading = false; + } + } + }; +} diff --git a/src/classes/websocket.js b/src/classes/websocket.js index 0ae4b327..90a8d597 100644 --- a/src/classes/websocket.js +++ b/src/classes/websocket.js @@ -559,6 +559,15 @@ export default class extends baseClass { // on avatar gallery image upload } else if (contentType === 'invitePhoto') { // on uploading invite photo + } else if (contentType === 'inventory') { + if ( + $app.galleryDialogVisible && + !$app.galleryDialogInventoryLoading + ) { + $app.getInventory(); + } + // on consuming a bundle + // {contentType: 'inventory', itemId: 'inv_', itemType: 'prop', actionType: 'add'} } else if (!contentType) { console.log( 'content-refresh without contentType', diff --git a/src/components/dialogs/UserDialog/GalleryDialog.vue b/src/components/dialogs/UserDialog/GalleryDialog.vue index 4e00192d..8bf4d2f3 100644 --- a/src/components/dialogs/UserDialog/GalleryDialog.vue +++ b/src/components/dialogs/UserDialog/GalleryDialog.vue @@ -444,6 +444,48 @@ + + + + {{ t('dialog.gallery_icons.inventory') }} + {{ inventoryTable.length }} + + + + + + + + + + + + + {{ item.created_at | formatDate('long') }} + + + + + {{ t('dialog.gallery_icons.consume_bundle') }} + + + @@ -451,7 +493,7 @@ diff --git a/src/components/dialogs/UserDialog/UserDialog.vue b/src/components/dialogs/UserDialog/UserDialog.vue index 3fb08cb2..0d36c7b1 100644 --- a/src/components/dialogs/UserDialog/UserDialog.vue +++ b/src/components/dialogs/UserDialog/UserDialog.vue @@ -1798,6 +1798,7 @@ :gallery-dialog-emojis-loading="galleryDialogEmojisLoading" :gallery-dialog-stickers-loading="galleryDialogStickersLoading" :gallery-dialog-prints-loading="galleryDialogPrintsLoading" + :gallery-dialog-inventory-loading="galleryDialogInventoryLoading" :gallery-table="galleryTable" :VRCPlusIconsTable="VRCPlusIconsTable" :emoji-table="emojiTable" @@ -1805,6 +1806,7 @@ :print-upload-note="printUploadNote" :print-crop-border="printCropBorder" :print-table="printTable" + :inventory-table="inventoryTable" @refreshGalleryTable="refreshGalleryTable" @refreshVRCPlusIconsTable="refreshVRCPlusIconsTable" @refreshStickerTable="refreshStickerTable" @@ -2002,6 +2004,10 @@ type: Boolean, required: true }, + galleryDialogInventoryLoading: { + type: Boolean, + required: true + }, galleryTable: { type: Array, required: true @@ -2030,6 +2036,10 @@ printTable: { type: Array, required: true + }, + inventoryTable: { + type: Array, + required: true } }); diff --git a/src/localization/en/en.json b/src/localization/en/en.json index d85b1207..00a15c11 100644 --- a/src/localization/en/en.json +++ b/src/localization/en/en.json @@ -1444,6 +1444,7 @@ "emojis": "Emojis", "stickers": "Stickers", "prints": "Prints", + "inventory": "Inventory", "refresh": "Refresh", "upload": "Upload", "clear": "Clear", @@ -1454,7 +1455,8 @@ "emoji_loop_pingpong": "Loop PingPong", "flipbook_info": "Select a 1024x1024 PNG spritesheet to use as an animated emoji, available frame grid sizes: 4, 16 or 64 (max FPS 64, max frames 64)", "note": "Note", - "crop_print_border": "Crop Print Border" + "crop_print_border": "Crop Print Border", + "consume_bundle": "Consume" }, "change_content_image": { "avatar": "Change Avatar Image", diff --git a/src/mixins/dialogs/dialogs.pug b/src/mixins/dialogs/dialogs.pug index f0376524..9733a264 100644 --- a/src/mixins/dialogs/dialogs.pug +++ b/src/mixins/dialogs/dialogs.pug @@ -88,6 +88,7 @@ mixin dialogs :galleryDialogEmojisLoading='galleryDialogEmojisLoading' :galleryDialogStickersLoading='galleryDialogStickersLoading' :galleryDialogPrintsLoading='galleryDialogPrintsLoading' + :galleryDialogInventoryLoading='galleryDialogInventoryLoading' :galleryTable='galleryTable' :VRCPlusIconsTable='VRCPlusIconsTable' :emojiTable='emojiTable' @@ -95,6 +96,7 @@ mixin dialogs :printUploadNote='printUploadNote' :printCropBorder='printCropBorder' :printTable='printTable' + :inventoryTable='inventoryTable' @refreshGalleryTable='refreshGalleryTable' @refreshEmojiTable='refreshEmojiTable' @refreshStickerTable='refreshStickerTable'