add new composables for instance and search functionalities

This commit is contained in:
pa
2026-03-10 22:17:16 +09:00
parent fe176f22ff
commit 699bf620e5
8 changed files with 779 additions and 601 deletions

View File

@@ -22,12 +22,12 @@
<script setup>
import { computed, onBeforeMount, onMounted } from 'vue';
import { addGameLogEvent, getGameLogTable } from './coordinators/gameLogCoordinator';
import { runCheckVRChatDebugLoggingFlow, runUpdateIsGameRunningFlow } from './coordinators/gameCoordinator';
import { Toaster } from './components/ui/sonner';
import { TooltipProvider } from './components/ui/tooltip';
import { createGlobalStores } from './stores';
import { initNoty } from './plugins/noty';
import { getGameLogTable } from './coordinators/gameLogCoordinator';
import { runCheckVRChatDebugLoggingFlow } from './coordinators/gameCoordinator';
import AlertDialogModal from './components/ui/alert-dialog/AlertDialogModal.vue';
import MacOSTitleBar from './components/MacOSTitleBar.vue';
@@ -51,6 +51,9 @@
if (typeof window !== 'undefined') {
window.$pinia = store;
// Bridge: attach coordinator functions to store for C# IPC callbacks
store.game.updateIsGameRunning = runUpdateIsGameRunningFlow;
store.gameLog.addGameLogEvent = addGameLogEvent;
}
onBeforeMount(() => {

View File

@@ -550,7 +550,7 @@
DialogTitle
} from '@/components/ui/dialog';
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
import { computed, ref, watch } from 'vue';
import { computed, ref, toRef } from 'vue';
import { Button } from '@/components/ui/button';
import { Check as CheckIcon } from 'lucide-vue-next';
import { Checkbox } from '@/components/ui/checkbox';
@@ -561,7 +561,6 @@
import { useI18n } from 'vue-i18n';
import {
buildLegacyInstanceTag,
copyToClipboard,
getLaunchURL,
hasGroupPermission,
@@ -569,23 +568,22 @@
parseLocation,
userImage,
userStatusClass
} from '../../shared/utils';
} from '../../../shared/utils';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
import {
useFriendStore,
useGroupStore,
useInstanceStore,
useInviteStore,
useLaunchStore,
useLocationStore,
useUserStore
} from '../../stores';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
import { groupRequest, instanceRequest, queryRequest } from '../../api';
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
import { VirtualCombobox } from '../ui/virtual-combobox';
} from '../../../stores';
import { ToggleGroup, ToggleGroupItem } from '../../ui/toggle-group';
import { instanceRequest, queryRequest } from '../../../api';
import { VirtualCombobox } from '../../ui/virtual-combobox';
import { useNewInstanceBuilder } from './useNewInstanceBuilder';
import InviteDialog from './InviteDialog/InviteDialog.vue';
import configRepository from '../../services/config';
import InviteDialog from '../InviteDialog/InviteDialog.vue';
const props = defineProps({
newInstanceDialogLocationTag: {
@@ -602,40 +600,20 @@
const { friends, vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore());
const { currentUserGroups } = storeToRefs(useGroupStore());
const { cachedGroups, handleGroupPermissions } = useGroupStore();
const { lastLocation } = storeToRefs(useLocationStore());
const { showLaunchDialog, tryOpenInstanceInVrc } = useLaunchStore();
const { createNewInstance } = useInstanceStore();
const { currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
const { currentUser } = storeToRefs(useUserStore());
const { canOpenInstanceInGame } = useInviteStore();
const newInstanceDialog = ref({
visible: false,
// loading: false,
selectedTab: 'Normal',
instanceCreated: false,
queueEnabled: false,
worldId: '',
instanceId: '',
instanceName: '',
userId: '',
accessType: 'public',
region: 'US West',
groupRegion: '',
groupId: '',
groupAccessType: 'plus',
ageGate: false,
strict: false,
location: '',
shortName: '',
displayName: '',
url: '',
secureOrShortName: '',
lastSelectedGroupId: '',
selectedGroupRoles: [],
roleIds: [],
groupRef: {}
});
const {
newInstanceDialog,
buildInstance,
buildLegacyInstance,
updateNewInstanceDialog,
handleCreateNewInstance,
newInstanceTabClick,
handleRoleIdsChange
} = useNewInstanceBuilder(toRef(props, 'newInstanceDialogLocationTag'));
const inviteDialog = ref({
visible: false,
@@ -762,25 +740,6 @@
return groups;
});
/**
*
* @param value
*/
function handleRoleIdsChange(value) {
const next = Array.isArray(value) ? value.map((v) => String(v ?? '')).filter(Boolean) : [];
newInstanceDialog.value.roleIds = next;
buildInstance();
}
watch(
() => props.newInstanceDialogLocationTag,
(value) => {
initNewInstanceDialog(value);
}
);
initializeNewInstanceDialog();
/**
*
*/
@@ -829,136 +788,6 @@
closeInviteDialog();
}
/**
*
* @param tag
*/
async function initNewInstanceDialog(tag) {
if (!isRealInstance(tag)) {
return;
}
const D = newInstanceDialog.value;
const L = parseLocation(tag);
if (D.worldId === L.worldId) {
// reopening dialog, keep last open instance
D.visible = true;
return;
}
D.worldId = L.worldId;
D.instanceCreated = false;
D.lastSelectedGroupId = '';
D.selectedGroupRoles = [];
D.groupRef = {};
D.roleIds = [];
D.strict = false;
D.shortName = '';
D.secureOrShortName = '';
if (!isLocalUserVrcPlusSupporter.value) {
D.displayName = '';
}
const args = await groupRequest.getGroupPermissions({ userId: currentUser.value.id });
handleGroupPermissions(args);
buildInstance();
buildLegacyInstance();
updateNewInstanceDialog();
D.visible = true;
}
/**
*
*/
function initializeNewInstanceDialog() {
configRepository
.getBool('instanceDialogQueueEnabled', true)
.then((value) => (newInstanceDialog.value.queueEnabled = value));
configRepository
.getString('instanceDialogInstanceName', '')
.then((value) => (newInstanceDialog.value.instanceName = value));
configRepository
.getString('instanceDialogUserId', '')
.then((value) => (newInstanceDialog.value.userId = value));
configRepository
.getString('instanceDialogAccessType', 'public')
.then((value) => (newInstanceDialog.value.accessType = value));
configRepository
.getString('instanceRegion', 'US West')
.then((value) => (newInstanceDialog.value.region = value));
configRepository
.getString('instanceDialogGroupId', '')
.then((value) => (newInstanceDialog.value.groupId = value));
configRepository
.getString('instanceDialogGroupAccessType', 'plus')
.then((value) => (newInstanceDialog.value.groupAccessType = value));
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,
displayName
} = newInstanceDialog.value;
configRepository.setString('instanceDialogAccessType', accessType);
configRepository.setString('instanceRegion', region);
configRepository.setString('instanceDialogInstanceName', instanceName);
configRepository.setString('instanceDialogUserId', userId === currentUser.value.id ? '' : userId);
configRepository.setString('instanceDialogGroupId', groupId);
configRepository.setString('instanceDialogGroupAccessType', groupAccessType);
configRepository.setBool('instanceDialogQueueEnabled', queueEnabled);
configRepository.setBool('instanceDialogAgeGate', ageGate);
configRepository.setString('instanceDialogDisplayName', displayName);
}
/**
*
* @param tabName
*/
function newInstanceTabClick(tabName) {
if (tabName === 'Normal') {
buildInstance();
} else {
buildLegacyInstance();
}
}
/**
*
* @param noChanges
*/
function updateNewInstanceDialog(noChanges) {
const D = newInstanceDialog.value;
if (D.instanceId) {
D.location = `${D.worldId}:${D.instanceId}`;
} else {
D.location = D.worldId;
}
const L = parseLocation(D.location);
if (noChanges) {
L.shortName = D.shortName;
} else {
D.shortName = '';
}
D.url = getLaunchURL(L);
}
/**
*
* @param location
@@ -978,114 +807,7 @@
return args;
});
}
/**
*
*/
async function handleCreateNewInstance() {
const args = await createNewInstance(newInstanceDialog.value.worldId, newInstanceDialog.value);
if (args) {
newInstanceDialog.value.location = args.json.location;
newInstanceDialog.value.instanceId = args.json.instanceId;
newInstanceDialog.value.secureOrShortName = args.json.shortName || args.json.secureName;
newInstanceDialog.value.instanceCreated = true;
updateNewInstanceDialog();
}
}
/**
*
*/
function buildInstance() {
const D = newInstanceDialog.value;
D.instanceCreated = false;
D.instanceId = '';
D.shortName = '';
D.secureOrShortName = '';
if (!D.userId) {
D.userId = currentUser.value.id;
}
if (D.groupId && D.groupId !== D.lastSelectedGroupId) {
D.roleIds = [];
const ref = cachedGroups.get(D.groupId);
if (typeof ref !== 'undefined') {
D.groupRef = ref;
D.selectedGroupRoles = ref.roles;
groupRequest
.getGroupRoles({
groupId: D.groupId
})
.then((args) => {
D.lastSelectedGroupId = D.groupId;
D.selectedGroupRoles = args.json;
ref.roles = args.json;
});
}
}
if (!D.groupId) {
D.roleIds = [];
D.groupRef = {};
D.selectedGroupRoles = [];
D.lastSelectedGroupId = '';
}
saveNewInstanceDialog();
}
/**
*
*/
function buildLegacyInstance() {
const D = newInstanceDialog.value;
D.instanceCreated = false;
D.shortName = '';
D.secureOrShortName = '';
if (D.instanceName) {
D.instanceName = D.instanceName.replace(/[^A-Za-z0-9]/g, '');
}
if (!D.userId) {
D.userId = currentUser.value.id;
}
if (D.accessType !== 'invite' && D.accessType !== 'friends') {
D.strict = false;
}
const instanceName = D.instanceName || String((99999 * Math.random() + 1).toFixed(0)).padStart(5, '0');
D.instanceId = buildLegacyInstanceTag({
instanceName,
userId: D.userId,
accessType: D.accessType,
groupId: D.groupId,
groupAccessType: D.groupAccessType,
region: D.region,
ageGate: D.ageGate,
strict: D.strict
});
if (D.groupId && D.groupId !== D.lastSelectedGroupId) {
D.roleIds = [];
const ref = cachedGroups.get(D.groupId);
if (typeof ref !== 'undefined') {
D.groupRef = ref;
D.selectedGroupRoles = ref.roles;
groupRequest
.getGroupRoles({
groupId: D.groupId
})
.then((args) => {
D.lastSelectedGroupId = D.groupId;
D.selectedGroupRoles = args.json;
ref.roles = args.json;
});
}
}
if (!D.groupId) {
D.roleIds = [];
D.selectedGroupRoles = [];
D.groupRef = {};
D.lastSelectedGroupId = '';
}
updateNewInstanceDialog(false);
saveNewInstanceDialog();
}
/**
*
* @param location

View File

@@ -0,0 +1,337 @@
import { ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import {
buildLegacyInstanceTag,
getLaunchURL,
isRealInstance,
parseLocation
} from '../../../shared/utils';
import { useGroupStore, useInstanceStore, useUserStore } from '../../../stores';
import { groupRequest } from '../../../api';
import { handleGroupPermissions } from '../../../coordinators/groupCoordinator';
import configRepository from '../../../services/config';
/**
* Instance builder composable for NewInstanceDialog.
* Manages instance state, config persistence, and build logic for Normal/Legacy tabs.
* @param {import('vue').Ref<string>} locationTagRef - reactive location tag from props
*/
export function useNewInstanceBuilder(locationTagRef) {
const { cachedGroups } = useGroupStore();
const { currentUser, isLocalUserVrcPlusSupporter } =
storeToRefs(useUserStore());
const { createNewInstance } = useInstanceStore();
const newInstanceDialog = ref({
visible: false,
// loading: false,
selectedTab: 'Normal',
instanceCreated: false,
queueEnabled: false,
worldId: '',
instanceId: '',
instanceName: '',
userId: '',
accessType: 'public',
region: 'US West',
groupRegion: '',
groupId: '',
groupAccessType: 'plus',
ageGate: false,
strict: false,
location: '',
shortName: '',
displayName: '',
url: '',
secureOrShortName: '',
lastSelectedGroupId: '',
selectedGroupRoles: [],
roleIds: [],
groupRef: {}
});
// --- Config persistence ---
/**
*
*/
function initializeNewInstanceDialog() {
configRepository
.getBool('instanceDialogQueueEnabled', true)
.then((value) => (newInstanceDialog.value.queueEnabled = value));
configRepository
.getString('instanceDialogInstanceName', '')
.then((value) => (newInstanceDialog.value.instanceName = value));
configRepository
.getString('instanceDialogUserId', '')
.then((value) => (newInstanceDialog.value.userId = value));
configRepository
.getString('instanceDialogAccessType', 'public')
.then((value) => (newInstanceDialog.value.accessType = value));
configRepository
.getString('instanceRegion', 'US West')
.then((value) => (newInstanceDialog.value.region = value));
configRepository
.getString('instanceDialogGroupId', '')
.then((value) => (newInstanceDialog.value.groupId = value));
configRepository
.getString('instanceDialogGroupAccessType', 'plus')
.then((value) => (newInstanceDialog.value.groupAccessType = value));
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,
displayName
} = newInstanceDialog.value;
configRepository.setString('instanceDialogAccessType', accessType);
configRepository.setString('instanceRegion', region);
configRepository.setString('instanceDialogInstanceName', instanceName);
configRepository.setString(
'instanceDialogUserId',
userId === currentUser.value.id ? '' : userId
);
configRepository.setString('instanceDialogGroupId', groupId);
configRepository.setString(
'instanceDialogGroupAccessType',
groupAccessType
);
configRepository.setBool('instanceDialogQueueEnabled', queueEnabled);
configRepository.setBool('instanceDialogAgeGate', ageGate);
configRepository.setString('instanceDialogDisplayName', displayName);
}
// --- Group role loading (shared between buildInstance & buildLegacyInstance) ---
/**
* @param {object} D - newInstanceDialog.value
*/
function refreshGroupRoles(D) {
if (D.groupId && D.groupId !== D.lastSelectedGroupId) {
D.roleIds = [];
const ref = cachedGroups.get(D.groupId);
if (typeof ref !== 'undefined') {
D.groupRef = ref;
D.selectedGroupRoles = ref.roles;
groupRequest
.getGroupRoles({
groupId: D.groupId
})
.then((args) => {
D.lastSelectedGroupId = D.groupId;
D.selectedGroupRoles = args.json;
ref.roles = args.json;
});
}
}
if (!D.groupId) {
D.roleIds = [];
D.groupRef = {};
D.selectedGroupRoles = [];
D.lastSelectedGroupId = '';
}
}
// --- Build logic ---
/**
*
* @param noChanges
*/
function updateNewInstanceDialog(noChanges) {
const D = newInstanceDialog.value;
if (D.instanceId) {
D.location = `${D.worldId}:${D.instanceId}`;
} else {
D.location = D.worldId;
}
const L = parseLocation(D.location);
if (noChanges) {
L.shortName = D.shortName;
} else {
D.shortName = '';
}
D.url = getLaunchURL(L);
}
/**
*
*/
function buildInstance() {
const D = newInstanceDialog.value;
D.instanceCreated = false;
D.instanceId = '';
D.shortName = '';
D.secureOrShortName = '';
if (!D.userId) {
D.userId = currentUser.value.id;
}
refreshGroupRoles(D);
saveNewInstanceDialog();
}
/**
*
*/
function buildLegacyInstance() {
const D = newInstanceDialog.value;
D.instanceCreated = false;
D.shortName = '';
D.secureOrShortName = '';
if (D.instanceName) {
D.instanceName = D.instanceName.replace(/[^A-Za-z0-9]/g, '');
}
if (!D.userId) {
D.userId = currentUser.value.id;
}
if (D.accessType !== 'invite' && D.accessType !== 'friends') {
D.strict = false;
}
const instanceName =
D.instanceName ||
String((99999 * Math.random() + 1).toFixed(0)).padStart(5, '0');
D.instanceId = buildLegacyInstanceTag({
instanceName,
userId: D.userId,
accessType: D.accessType,
groupId: D.groupId,
groupAccessType: D.groupAccessType,
region: D.region,
ageGate: D.ageGate,
strict: D.strict
});
refreshGroupRoles(D);
updateNewInstanceDialog(false);
saveNewInstanceDialog();
}
// --- Dialog lifecycle ---
/**
*
* @param tag
*/
async function initNewInstanceDialog(tag) {
if (!isRealInstance(tag)) {
return;
}
const D = newInstanceDialog.value;
const L = parseLocation(tag);
if (D.worldId === L.worldId) {
// reopening dialog, keep last open instance
D.visible = true;
return;
}
D.worldId = L.worldId;
D.instanceCreated = false;
D.lastSelectedGroupId = '';
D.selectedGroupRoles = [];
D.groupRef = {};
D.roleIds = [];
D.strict = false;
D.shortName = '';
D.secureOrShortName = '';
if (!isLocalUserVrcPlusSupporter.value) {
D.displayName = '';
}
const args = await groupRequest.getGroupPermissions({
userId: currentUser.value.id
});
handleGroupPermissions(args);
buildInstance();
buildLegacyInstance();
updateNewInstanceDialog();
D.visible = true;
}
/**
*
*/
async function handleCreateNewInstance() {
const args = await createNewInstance(
newInstanceDialog.value.worldId,
newInstanceDialog.value
);
if (args) {
newInstanceDialog.value.location = args.json.location;
newInstanceDialog.value.instanceId = args.json.instanceId;
newInstanceDialog.value.secureOrShortName =
args.json.shortName || args.json.secureName;
newInstanceDialog.value.instanceCreated = true;
updateNewInstanceDialog();
}
}
// --- UI handlers ---
/**
*
* @param tabName
*/
function newInstanceTabClick(tabName) {
if (tabName === 'Normal') {
buildInstance();
} else {
buildLegacyInstance();
}
}
/**
*
* @param value
*/
function handleRoleIdsChange(value) {
const next = Array.isArray(value)
? value.map((v) => String(v ?? '')).filter(Boolean)
: [];
newInstanceDialog.value.roleIds = next;
buildInstance();
}
// --- Init ---
watch(
() => locationTagRef.value,
(value) => {
initNewInstanceDialog(value);
}
);
initializeNewInstanceDialog();
return {
newInstanceDialog,
buildInstance,
buildLegacyInstance,
updateNewInstanceDialog,
handleCreateNewInstance,
newInstanceTabClick,
handleRoleIdsChange
};
}

View File

@@ -413,13 +413,12 @@
import ImageCropDialog from '../ImageCropDialog.vue';
import WorldDialogInfoTab from './WorldDialogInfoTab.vue';
import WorldDialogInstancesTab from './WorldDialogInstancesTab.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const SetWorldTagsDialog = defineAsyncComponent(() => import('./SetWorldTagsDialog.vue'));
const WorldAllowedDomainsDialog = defineAsyncComponent(() => import('./WorldAllowedDomainsDialog.vue'));
const NewInstanceDialog = defineAsyncComponent(() => import('../NewInstanceDialog.vue'));
const NewInstanceDialog = defineAsyncComponent(() => import('../NewInstanceDialog/NewInstanceDialog.vue'));
const { currentUser, userDialog } = storeToRefs(useUserStore());
const { worldDialog } = storeToRefs(useWorldStore());
const { cachedWorlds } = useWorldStore();

View File

@@ -13,6 +13,7 @@ import {
} 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 { useGameStore } from '../game';
@@ -26,8 +27,6 @@ import { useVrStore } from '../vr';
import { useVrcxStore } from '../vrcx';
import { watchState } from '../../services/watchState';
import { tryLoadPlayerList, addGameLogEvent } from '../../coordinators/gameLogCoordinator';
import configRepository from '../../services/config';
import * as workerTimers from 'worker-timers';
@@ -182,6 +181,9 @@ export const useGameLogStore = defineStore('GameLog', () => {
vrStore.updateVrNowPlaying();
}
/**
*
*/
function resetLastMediaUrls() {
lastVideoUrl.value = '';
lastResourceloadUrl.value = '';
@@ -470,9 +472,6 @@ export const useGameLogStore = defineStore('GameLog', () => {
addGameLogVRDancing,
addGameLogZuwaZuwaDance,
addGameLogLSMedia,
addGameLogPopcornPalace,
// Re-exported from coordinator (called by C# via window.$pinia)
addGameLogEvent
addGameLogPopcornPalace
};
});

View File

@@ -393,25 +393,20 @@
useUserStore,
useWorldStore
} from '../../stores';
import {
compareByCreatedAt,
compareByName,
compareByUpdatedAt,
convertFileUrlToImageUrl,
replaceBioSymbols,
userImage
} from '../../shared/utils';
import { groupRequest, worldRequest } from '../../api';
import { showUserDialog, refreshUserDialogAvatars } from '../../coordinators/userCoordinator';
import { convertFileUrlToImageUrl, replaceBioSymbols, userImage } from '../../shared/utils';
import { refreshUserDialogAvatars, showUserDialog } from '../../coordinators/userCoordinator';
import { groupRequest } from '../../api';
import { useSearchAvatar } from './composables/useSearchAvatar';
import { useSearchWorld } from './composables/useSearchWorld';
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
const { avatarRemoteDatabase } = storeToRefs(useAdvancedSettingsStore());
const { avatarRemoteDatabaseProviderList, avatarRemoteDatabaseProvider } = storeToRefs(useAvatarProviderStore());
const { setAvatarProvider } = useAvatarProviderStore();
const { avatarRemoteDatabase } = storeToRefs(useAdvancedSettingsStore());
const { userDialog } = storeToRefs(useUserStore());
const { showAvatarDialog, lookupAvatars, cachedAvatars } = useAvatarStore();
const { cachedWorlds, showWorldDialog } = useWorldStore();
const { showAvatarDialog } = useAvatarStore();
const { showWorldDialog } = useWorldStore();
const { showGroupDialog } = useGroupStore();
const { searchText, searchUserResults } = storeToRefs(useSearchStore());
const { clearSearch, moreSearchUser } = useSearchStore();
@@ -427,56 +422,41 @@ import { showUserDialog, refreshUserDialogAvatars } from '../../coordinators/use
{ value: 'group', label: t('view.search.group.header') }
]);
const {
searchAvatarFilter,
searchAvatarSort,
searchAvatarFilterRemote,
searchAvatarPageNum,
searchAvatarResults,
searchAvatarPage,
isSearchAvatarLoading,
searchAvatar,
moreSearchAvatar,
handleSearchAvatarFilterChange,
handleSearchAvatarFilterRemoteChange,
handleSearchAvatarSortChange,
clearAvatarSearch
} = useSearchAvatar();
const {
searchWorldLabs,
searchWorldParams,
searchWorldCategoryIndex,
searchWorldResults,
isSearchWorldLoading,
searchWorld,
moreSearchWorld,
handleSearchWorldCategorySelect,
clearWorldSearch
} = useSearchWorld();
const searchUserParams = ref({});
const searchUserByBio = ref(false);
const searchUserSortByLastLoggedIn = ref(false);
const isSearchUserLoading = ref(false);
const isSearchWorldLoading = ref(false);
const isSearchAvatarLoading = ref(false);
const isSearchGroupLoading = ref(false);
const searchWorldOption = ref('');
const searchWorldLabs = ref(false);
const searchWorldParams = ref({});
const searchWorldCategoryIndex = ref(null);
const searchWorldResults = ref([]);
/**
*
* @param value
*/
function handleSearchAvatarFilterChange(value) {
searchAvatarFilter.value = value;
searchAvatar();
}
/**
*
* @param value
*/
function handleSearchAvatarFilterRemoteChange(value) {
searchAvatarFilterRemote.value = value;
searchAvatar();
}
/**
*
* @param value
*/
function handleSearchAvatarSortChange(value) {
searchAvatarSort.value = value;
searchAvatar();
}
const searchAvatarFilter = ref('');
const searchAvatarSort = ref('');
const searchAvatarFilterRemote = ref('');
const searchAvatarPageNum = ref(0);
const searchAvatarResults = ref([]);
const searchAvatarPage = ref([]);
const searchGroupParams = ref({});
const searchGroupResults = ref([]);
@@ -493,11 +473,8 @@ import { showUserDialog, refreshUserDialogAvatars } from '../../coordinators/use
*/
function handleClearSearch() {
searchUserParams.value = {};
searchWorldParams.value = {};
searchWorldResults.value = [];
searchAvatarResults.value = [];
searchAvatarPage.value = [];
searchAvatarPageNum.value = 0;
clearWorldSearch();
clearAvatarSearch();
searchGroupParams.value = {};
searchGroupResults.value = [];
clearSearch();
@@ -564,237 +541,6 @@ import { showUserDialog, refreshUserDialogAvatars } from '../../coordinators/use
isSearchUserLoading.value = false;
}
/**
*
* @param ref
*/
function searchWorld(ref) {
searchWorldOption.value = '';
searchWorldCategoryIndex.value = ref?.index ?? null;
const params = {
n: 10,
offset: 0
};
switch (ref.sortHeading) {
case 'featured':
params.sort = 'order';
params.featured = 'true';
break;
case 'trending':
params.sort = 'popularity';
params.featured = 'false';
break;
case 'updated':
params.sort = 'updated';
break;
case 'created':
params.sort = 'created';
break;
case 'publication':
params.sort = 'publicationDate';
break;
case 'shuffle':
params.sort = 'shuffle';
break;
case 'active':
searchWorldOption.value = 'active';
break;
case 'recent':
searchWorldOption.value = 'recent';
break;
case 'favorite':
searchWorldOption.value = 'favorites';
break;
case 'labs':
params.sort = 'labsPublicationDate';
break;
case 'heat':
params.sort = 'heat';
params.featured = 'false';
break;
default:
params.sort = 'relevance';
params.search = replaceBioSymbols(searchText.value);
break;
}
params.order = ref.sortOrder || 'descending';
if (ref.sortOwnership === 'mine') {
params.user = 'me';
params.releaseStatus = 'all';
}
if (ref.tag) {
params.tag = ref.tag;
}
if (!searchWorldLabs.value) {
if (params.tag) {
params.tag += ',system_approved';
} else {
params.tag = 'system_approved';
}
}
// TODO: option.platform
searchWorldParams.value = params;
moreSearchWorld();
}
/**
*
* @param index
*/
function handleSearchWorldCategorySelect(index) {
searchWorldCategoryIndex.value = index;
const row = cachedConfig.value?.dynamicWorldRows?.find((r) => r.index === index);
searchWorld(row || {});
}
/**
*
* @param go
*/
function moreSearchWorld(go) {
const params = searchWorldParams.value;
if (go) {
params.offset += params.n * go;
if (params.offset < 0) {
params.offset = 0;
}
}
isSearchWorldLoading.value = true;
worldRequest
.getWorlds(params, searchWorldOption.value)
.finally(() => {
isSearchWorldLoading.value = false;
})
.then((args) => {
const map = new Map();
for (const json of args.json) {
const ref = cachedWorlds.get(json.id);
if (typeof ref !== 'undefined') {
map.set(ref.id, ref);
}
}
searchWorldResults.value = Array.from(map.values());
return args;
});
}
/**
*
*/
async function searchAvatar() {
let ref;
isSearchAvatarLoading.value = true;
if (!searchAvatarFilter.value) {
searchAvatarFilter.value = 'all';
}
if (!searchAvatarSort.value) {
searchAvatarSort.value = 'name';
}
if (!searchAvatarFilterRemote.value) {
searchAvatarFilterRemote.value = 'all';
}
if (searchAvatarFilterRemote.value !== 'local') {
searchAvatarSort.value = 'name';
}
const avatars = new Map();
const query = searchText.value;
const queryUpper = query.toUpperCase();
if (!query) {
for (ref of cachedAvatars.values()) {
switch (searchAvatarFilter.value) {
case 'all':
avatars.set(ref.id, ref);
break;
case 'public':
if (ref.releaseStatus === 'public') {
avatars.set(ref.id, ref);
}
break;
case 'private':
if (ref.releaseStatus === 'private') {
avatars.set(ref.id, ref);
}
break;
}
}
isSearchAvatarLoading.value = false;
} else {
if (searchAvatarFilterRemote.value === 'all' || searchAvatarFilterRemote.value === 'local') {
for (ref of cachedAvatars.values()) {
let match = ref.name.toUpperCase().includes(queryUpper);
if (!match && ref.description) {
match = ref.description.toUpperCase().includes(queryUpper);
}
if (!match && ref.authorName) {
match = ref.authorName.toUpperCase().includes(queryUpper);
}
if (match) {
switch (searchAvatarFilter.value) {
case 'all':
avatars.set(ref.id, ref);
break;
case 'public':
if (ref.releaseStatus === 'public') {
avatars.set(ref.id, ref);
}
break;
case 'private':
if (ref.releaseStatus === 'private') {
avatars.set(ref.id, ref);
}
break;
}
}
}
}
if (
(searchAvatarFilterRemote.value === 'all' || searchAvatarFilterRemote.value === 'remote') &&
avatarRemoteDatabase.value &&
query.length >= 3
) {
const data = await lookupAvatars('search', query);
if (data && typeof data === 'object') {
data.forEach((avatar) => {
avatars.set(avatar.id, avatar);
});
}
}
isSearchAvatarLoading.value = false;
}
const avatarsArray = Array.from(avatars.values());
if (searchAvatarFilterRemote.value === 'local') {
switch (searchAvatarSort.value) {
case 'updated':
avatarsArray.sort(compareByUpdatedAt);
break;
case 'created':
avatarsArray.sort(compareByCreatedAt);
break;
case 'name':
avatarsArray.sort(compareByName);
break;
}
}
searchAvatarPageNum.value = 0;
searchAvatarResults.value = avatarsArray;
searchAvatarPage.value = avatarsArray.slice(0, 10);
}
/**
*
* @param n
*/
function moreSearchAvatar(n) {
let offset;
if (n === -1) {
searchAvatarPageNum.value--;
offset = searchAvatarPageNum.value * 10;
}
if (n === 1) {
searchAvatarPageNum.value++;
offset = searchAvatarPageNum.value * 10;
}
searchAvatarPage.value = searchAvatarResults.value.slice(offset, offset + 10);
}
/**
*
*/

View File

@@ -0,0 +1,212 @@
import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import {
compareByCreatedAt,
compareByName,
compareByUpdatedAt
} from '../../../shared/utils';
import {
useAdvancedSettingsStore,
useAvatarStore,
useSearchStore
} from '../../../stores';
/**
* Avatar search composable for Search view.
* Manages avatar search state, local/remote filtering, sorting, and pagination.
*/
export function useSearchAvatar() {
const { avatarRemoteDatabase } = storeToRefs(useAdvancedSettingsStore());
const { lookupAvatars, cachedAvatars } = useAvatarStore();
const { searchText } = storeToRefs(useSearchStore());
const searchAvatarFilter = ref('');
const searchAvatarSort = ref('');
const searchAvatarFilterRemote = ref('');
const searchAvatarPageNum = ref(0);
const searchAvatarResults = ref([]);
const searchAvatarPage = ref([]);
const isSearchAvatarLoading = ref(false);
/**
*
*/
async function searchAvatar() {
let ref;
isSearchAvatarLoading.value = true;
if (!searchAvatarFilter.value) {
searchAvatarFilter.value = 'all';
}
if (!searchAvatarSort.value) {
searchAvatarSort.value = 'name';
}
if (!searchAvatarFilterRemote.value) {
searchAvatarFilterRemote.value = 'all';
}
if (searchAvatarFilterRemote.value !== 'local') {
searchAvatarSort.value = 'name';
}
const avatars = new Map();
const query = searchText.value;
const queryUpper = query.toUpperCase();
if (!query) {
for (ref of cachedAvatars.values()) {
switch (searchAvatarFilter.value) {
case 'all':
avatars.set(ref.id, ref);
break;
case 'public':
if (ref.releaseStatus === 'public') {
avatars.set(ref.id, ref);
}
break;
case 'private':
if (ref.releaseStatus === 'private') {
avatars.set(ref.id, ref);
}
break;
}
}
isSearchAvatarLoading.value = false;
} else {
if (
searchAvatarFilterRemote.value === 'all' ||
searchAvatarFilterRemote.value === 'local'
) {
for (ref of cachedAvatars.values()) {
let match = ref.name.toUpperCase().includes(queryUpper);
if (!match && ref.description) {
match = ref.description
.toUpperCase()
.includes(queryUpper);
}
if (!match && ref.authorName) {
match = ref.authorName
.toUpperCase()
.includes(queryUpper);
}
if (match) {
switch (searchAvatarFilter.value) {
case 'all':
avatars.set(ref.id, ref);
break;
case 'public':
if (ref.releaseStatus === 'public') {
avatars.set(ref.id, ref);
}
break;
case 'private':
if (ref.releaseStatus === 'private') {
avatars.set(ref.id, ref);
}
break;
}
}
}
}
if (
(searchAvatarFilterRemote.value === 'all' ||
searchAvatarFilterRemote.value === 'remote') &&
avatarRemoteDatabase.value &&
query.length >= 3
) {
const data = await lookupAvatars('search', query);
if (data && typeof data === 'object') {
data.forEach((avatar) => {
avatars.set(avatar.id, avatar);
});
}
}
isSearchAvatarLoading.value = false;
}
const avatarsArray = Array.from(avatars.values());
if (searchAvatarFilterRemote.value === 'local') {
switch (searchAvatarSort.value) {
case 'updated':
avatarsArray.sort(compareByUpdatedAt);
break;
case 'created':
avatarsArray.sort(compareByCreatedAt);
break;
case 'name':
avatarsArray.sort(compareByName);
break;
}
}
searchAvatarPageNum.value = 0;
searchAvatarResults.value = avatarsArray;
searchAvatarPage.value = avatarsArray.slice(0, 10);
}
/**
*
* @param n
*/
function moreSearchAvatar(n) {
let offset;
if (n === -1) {
searchAvatarPageNum.value--;
offset = searchAvatarPageNum.value * 10;
}
if (n === 1) {
searchAvatarPageNum.value++;
offset = searchAvatarPageNum.value * 10;
}
searchAvatarPage.value = searchAvatarResults.value.slice(
offset,
offset + 10
);
}
/**
*
* @param value
*/
function handleSearchAvatarFilterChange(value) {
searchAvatarFilter.value = value;
searchAvatar();
}
/**
*
* @param value
*/
function handleSearchAvatarFilterRemoteChange(value) {
searchAvatarFilterRemote.value = value;
searchAvatar();
}
/**
*
* @param value
*/
function handleSearchAvatarSortChange(value) {
searchAvatarSort.value = value;
searchAvatar();
}
/**
*
*/
function clearAvatarSearch() {
searchAvatarResults.value = [];
searchAvatarPage.value = [];
searchAvatarPageNum.value = 0;
}
return {
searchAvatarFilter,
searchAvatarSort,
searchAvatarFilterRemote,
searchAvatarPageNum,
searchAvatarResults,
searchAvatarPage,
isSearchAvatarLoading,
searchAvatar,
moreSearchAvatar,
handleSearchAvatarFilterChange,
handleSearchAvatarFilterRemoteChange,
handleSearchAvatarSortChange,
clearAvatarSearch
};
}

View File

@@ -0,0 +1,160 @@
import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import { replaceBioSymbols } from '../../../shared/utils';
import { useAuthStore, useSearchStore, useWorldStore } from '../../../stores';
import { worldRequest } from '../../../api';
/**
* World search composable for Search view.
* Manages world search state, category selection, and pagination.
*/
export function useSearchWorld() {
const { cachedWorlds } = useWorldStore();
const { searchText } = storeToRefs(useSearchStore());
const { cachedConfig } = storeToRefs(useAuthStore());
const searchWorldOption = ref('');
const searchWorldLabs = ref(false);
const searchWorldParams = ref({});
const searchWorldCategoryIndex = ref(null);
const searchWorldResults = ref([]);
const isSearchWorldLoading = ref(false);
/**
*
* @param ref
*/
function searchWorld(ref) {
searchWorldOption.value = '';
searchWorldCategoryIndex.value = ref?.index ?? null;
const params = {
n: 10,
offset: 0
};
switch (ref.sortHeading) {
case 'featured':
params.sort = 'order';
params.featured = 'true';
break;
case 'trending':
params.sort = 'popularity';
params.featured = 'false';
break;
case 'updated':
params.sort = 'updated';
break;
case 'created':
params.sort = 'created';
break;
case 'publication':
params.sort = 'publicationDate';
break;
case 'shuffle':
params.sort = 'shuffle';
break;
case 'active':
searchWorldOption.value = 'active';
break;
case 'recent':
searchWorldOption.value = 'recent';
break;
case 'favorite':
searchWorldOption.value = 'favorites';
break;
case 'labs':
params.sort = 'labsPublicationDate';
break;
case 'heat':
params.sort = 'heat';
params.featured = 'false';
break;
default:
params.sort = 'relevance';
params.search = replaceBioSymbols(searchText.value);
break;
}
params.order = ref.sortOrder || 'descending';
if (ref.sortOwnership === 'mine') {
params.user = 'me';
params.releaseStatus = 'all';
}
if (ref.tag) {
params.tag = ref.tag;
}
if (!searchWorldLabs.value) {
if (params.tag) {
params.tag += ',system_approved';
} else {
params.tag = 'system_approved';
}
}
// TODO: option.platform
searchWorldParams.value = params;
moreSearchWorld();
}
/**
*
* @param index
*/
function handleSearchWorldCategorySelect(index) {
searchWorldCategoryIndex.value = index;
const row = cachedConfig.value?.dynamicWorldRows?.find(
(r) => r.index === index
);
searchWorld(row || {});
}
/**
*
* @param go
*/
function moreSearchWorld(go) {
const params = searchWorldParams.value;
if (go) {
params.offset += params.n * go;
if (params.offset < 0) {
params.offset = 0;
}
}
isSearchWorldLoading.value = true;
worldRequest
.getWorlds(params, searchWorldOption.value)
.finally(() => {
isSearchWorldLoading.value = false;
})
.then((args) => {
const map = new Map();
for (const json of args.json) {
const ref = cachedWorlds.get(json.id);
if (typeof ref !== 'undefined') {
map.set(ref.id, ref);
}
}
searchWorldResults.value = Array.from(map.values());
return args;
});
}
/**
*
*/
function clearWorldSearch() {
searchWorldParams.value = {};
searchWorldResults.value = [];
}
return {
searchWorldOption,
searchWorldLabs,
searchWorldParams,
searchWorldCategoryIndex,
searchWorldResults,
isSearchWorldLoading,
searchWorld,
moreSearchWorld,
handleSearchWorldCategorySelect,
clearWorldSearch
};
}