Files
VRCX/src/stores/gameLog/index.js
2026-03-14 22:41:49 +09:00

492 lines
13 KiB
JavaScript

import { reactive, ref, shallowRef, watch } from 'vue';
import { defineStore } from 'pinia';
import { useRouter } from 'vue-router';
import {
compareGameLogRows,
findUserByDisplayName,
formatSeconds,
gameLogSearchFilter,
getGroupName
} from '../../shared/utils';
import { createMediaParsers } from './mediaParsers';
import { database } from '../../services/database';
import { tryLoadPlayerList } from '../../coordinators/gameLogCoordinator';
import { useAdvancedSettingsStore } from '../settings/advanced';
import { useFriendStore } from '../friend';
import { useNotificationStore } from '../notification';
import { useUiStore } from '../ui';
import { useUserStore } from '../user';
import { useVrStore } from '../vr';
import { useVrcxStore } from '../vrcx';
import { watchState } from '../../services/watchState';
import configRepository from '../../services/config';
import * as workerTimers from 'worker-timers';
export const useGameLogStore = defineStore('GameLog', () => {
const notificationStore = useNotificationStore();
const vrStore = useVrStore();
const friendStore = useFriendStore();
const userStore = useUserStore();
const uiStore = useUiStore();
const vrcxStore = useVrcxStore();
const advancedSettingsStore = useAdvancedSettingsStore();
const router = useRouter();
const state = reactive({
lastLocationAvatarList: new Map()
});
const gameLogTableData = shallowRef([]);
const gameLogTable = ref({
loading: false,
search: '',
filter: [],
pageSize: 20,
pageSizeLinked: true,
vip: false
});
const nowPlaying = ref({
url: '',
name: '',
length: 0,
startTime: 0,
offset: 0,
elapsed: 0,
percentage: 0,
remainingText: '',
playing: false,
thumbnailUrl: ''
});
const lastVideoUrl = ref('');
const lastResourceloadUrl = ref('');
// Latest entry ref for GameLog Widget to watch
const latestGameLogEntry = ref(null);
watch(
() => watchState.isLoggedIn,
() => {
gameLogTableData.value = [];
},
{ flush: 'sync' }
);
watch(
router.currentRoute,
(value) => {
if (value.name === 'game-log') {
initGameLogTable();
} else {
gameLogTableData.value = [];
}
},
{ immediate: true }
);
watch(
() => watchState.isFavoritesLoaded,
(isFavoritesLoaded) => {
if (isFavoritesLoaded && gameLogTable.value.vip) {
gameLogTableLookup(); // re-apply VIP filter after friends are loaded
}
}
);
watch(
() => watchState.isFriendsLoaded,
(isFriendsLoaded) => {
if (isFriendsLoaded) {
tryLoadPlayerList();
}
},
{ flush: 'sync' }
);
/**
*
*/
async function init() {
gameLogTable.value.filter = JSON.parse(
await configRepository.getString('VRCX_gameLogTableFilters', '[]')
);
gameLogTable.value.vip = await configRepository.getBool(
'VRCX_gameLogTableVIPFilter',
false
);
}
init();
/**
*
* @param entry
*/
function insertGameLogSorted(entry) {
const arr = gameLogTableData.value;
if (arr.length === 0) {
gameLogTableData.value = [entry];
return;
}
if (compareGameLogRows(entry, arr[0]) < 0) {
gameLogTableData.value = [entry, ...arr];
return;
}
if (compareGameLogRows(entry, arr[arr.length - 1]) > 0) {
gameLogTableData.value = [...arr, entry];
return;
}
for (let i = 1; i < arr.length; i++) {
if (compareGameLogRows(entry, arr[i]) < 0) {
gameLogTableData.value = [
...arr.slice(0, i),
entry,
...arr.slice(i)
];
return;
}
}
gameLogTableData.value = [...arr, entry];
}
/**
*
*/
function clearNowPlaying() {
nowPlaying.value = {
url: '',
name: '',
length: 0,
startTime: 0,
offset: 0,
elapsed: 0,
percentage: 0,
remainingText: '',
playing: false,
thumbnailUrl: ''
};
vrStore.updateVrNowPlaying();
}
/**
*
*/
function resetLastMediaUrls() {
lastVideoUrl.value = '';
lastResourceloadUrl.value = '';
}
/**
*
* @param data
*/
function setNowPlaying(data) {
const ctx = structuredClone(data);
if (nowPlaying.value.url !== ctx.videoUrl) {
if (!ctx.userId && ctx.displayName) {
ctx.userId =
findUserByDisplayName(
userStore.cachedUsers,
ctx.displayName,
userStore.cachedUserIdsByDisplayName
)?.id ?? '';
}
notificationStore.queueGameLogNoty(ctx);
addGameLog(ctx);
database.addGamelogVideoPlayToDatabase(ctx);
let displayName = '';
if (ctx.displayName) {
displayName = ` (${ctx.displayName})`;
}
const name = `${ctx.videoName}${displayName}`;
nowPlaying.value = {
url: ctx.videoUrl,
name,
length: ctx.videoLength,
startTime: Date.parse(ctx.created_at) / 1000,
offset: ctx.videoPos,
elapsed: 0,
percentage: 0,
remainingText: '',
playing: false,
thumbnailUrl: ctx.thumbnailUrl
};
} else {
nowPlaying.value = {
...nowPlaying.value,
length: ctx.videoLength,
offset: ctx.videoPos,
elapsed: 0,
percentage: 0,
remainingText: '',
thumbnailUrl: ctx.thumbnailUrl
};
if (ctx.updatedAt && ctx.videoPos) {
nowPlaying.value.startTime =
Date.parse(ctx.updatedAt) / 1000 - ctx.videoPos;
} else {
nowPlaying.value.startTime =
Date.parse(ctx.created_at) / 1000 - ctx.videoPos;
}
}
vrStore.updateVrNowPlaying();
if (!nowPlaying.value.playing && ctx.videoLength > 0) {
nowPlaying.value.playing = true;
updateNowPlaying();
}
}
const {
addGameLogVideo,
addGameLogPyPyDance,
addGameLogVRDancing,
addGameLogZuwaZuwaDance,
addGameLogLSMedia,
addGameLogPopcornPalace
} = createMediaParsers({
nowPlaying,
setNowPlaying,
clearNowPlaying,
userStore,
advancedSettingsStore
});
/**
*
*/
function updateNowPlaying() {
const np = nowPlaying.value;
if (!nowPlaying.value.playing) {
return;
}
const now = Date.now() / 1000;
np.elapsed = Math.round((now - np.startTime) * 10) / 10;
if (np.elapsed >= np.length) {
clearNowPlaying();
return;
}
np.remainingText = formatSeconds(np.length - np.elapsed);
np.percentage = Math.round(((np.elapsed * 100) / np.length) * 10) / 10;
vrStore.updateVrNowPlaying();
workerTimers.setTimeout(() => updateNowPlaying(), 1000);
}
/**
*
* @param row
*/
function gameLogIsFriend(row) {
if (typeof row.isFriend !== 'undefined') {
return row.isFriend;
}
if (!row.userId) {
return false;
}
return friendStore.friends.has(row.userId);
}
/**
*
* @param row
*/
function gameLogIsFavorite(row) {
if (typeof row.isFavorite !== 'undefined') {
return row.isFavorite;
}
if (!row.userId) {
return false;
}
return friendStore.localFavoriteFriends.has(row.userId);
}
/**
*
*/
async function gameLogTableLookup() {
await configRepository.setString(
'VRCX_gameLogTableFilters',
JSON.stringify(gameLogTable.value.filter)
);
await configRepository.setBool(
'VRCX_gameLogTableVIPFilter',
gameLogTable.value.vip
);
gameLogTable.value.loading = true;
try {
let vipList = [];
if (gameLogTable.value.vip) {
vipList = Array.from(friendStore.localFavoriteFriends.values());
}
const search = gameLogTable.value.search.trim();
let rows = [];
if (search) {
rows = await database.searchGameLogDatabase(
search,
gameLogTable.value.filter,
vipList,
vrcxStore.searchLimit
);
} else {
rows = await database.lookupGameLogDatabase(
gameLogTable.value.filter,
vipList
);
}
for (const row of rows) {
row.isFriend = gameLogIsFriend(row);
row.isFavorite = gameLogIsFavorite(row);
}
gameLogTableData.value = rows;
} finally {
gameLogTable.value.loading = false;
}
}
/**
*
* @param entry
*/
function addGameLog(entry) {
entry.isFriend = gameLogIsFriend(entry);
entry.isFavorite = gameLogIsFavorite(entry);
// Update ref for GameLog Widget (independent data stream)
latestGameLogEntry.value = entry;
// If the VIP friend filter is enabled, logs from other friends will be ignored.
if (
gameLogTable.value.vip &&
!friendStore.localFavoriteFriends.has(entry.userId) &&
(entry.type === 'OnPlayerJoined' ||
entry.type === 'OnPlayerLeft' ||
entry.type === 'VideoPlay' ||
entry.type === 'PortalSpawn' ||
entry.type === 'External')
) {
return;
}
if (
entry.type === 'LocationDestination' ||
entry.type === 'AvatarChange' ||
entry.type === 'ChatBoxMessage' ||
(entry.userId === userStore.currentUser.id &&
(entry.type === 'OnPlayerJoined' ||
entry.type === 'OnPlayerLeft'))
) {
return;
}
if (
gameLogTable.value.filter.length > 0 &&
!gameLogTable.value.filter.includes(entry.type)
) {
return;
}
if (!gameLogSearch(entry)) {
return;
}
insertGameLogSorted(entry);
sweepGameLog();
uiStore.notifyMenu('game-log');
}
/**
*
* @param input
*/
async function addGamelogLocationToDatabase(input) {
const groupName = await getGroupName(input.location);
const entry = {
...input,
groupName
};
database.addGamelogLocationToDatabase(entry);
}
/**
*
* @param row
*/
function gameLogSearch(row) {
return gameLogSearchFilter(row, gameLogTable.value.search);
}
/**
*
*/
function sweepGameLog() {
const j = gameLogTableData.value.length;
if (j > vrcxStore.maxTableSize + 50) {
gameLogTableData.value = gameLogTableData.value.slice(0, -50);
}
}
/**
*
*/
async function initGameLogTable() {
gameLogTable.value.loading = true;
const rows = await database.lookupGameLogDatabase(
gameLogTable.value.filter,
[]
);
for (const row of rows) {
row.isFriend = gameLogIsFriend(row);
row.isFavorite = gameLogIsFavorite(row);
}
gameLogTableData.value = rows;
gameLogTable.value.loading = false;
}
/**
* @param {string} value
*/
function setLastVideoUrl(value) {
lastVideoUrl.value = value;
}
/**
* @param {string} value
*/
function setLastResourceloadUrl(value) {
lastResourceloadUrl.value = value;
}
return {
state,
nowPlaying,
gameLogTable,
gameLogTableData,
lastVideoUrl,
lastResourceloadUrl,
latestGameLogEntry,
clearNowPlaying,
resetLastMediaUrls,
gameLogIsFriend,
gameLogIsFavorite,
gameLogTableLookup,
addGameLog,
addGamelogLocationToDatabase,
// Media parsers (used by coordinator)
addGameLogVideo,
addGameLogPyPyDance,
addGameLogVRDancing,
addGameLogZuwaZuwaDance,
addGameLogLSMedia,
addGameLogPopcornPalace,
setLastVideoUrl,
setLastResourceloadUrl
};
});