mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-23 16:53:50 +02:00
add new composables for instance and search functionalities
This commit is contained in:
@@ -22,12 +22,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onBeforeMount, onMounted } from 'vue';
|
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 { Toaster } from './components/ui/sonner';
|
||||||
import { TooltipProvider } from './components/ui/tooltip';
|
import { TooltipProvider } from './components/ui/tooltip';
|
||||||
import { createGlobalStores } from './stores';
|
import { createGlobalStores } from './stores';
|
||||||
import { initNoty } from './plugins/noty';
|
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 AlertDialogModal from './components/ui/alert-dialog/AlertDialogModal.vue';
|
||||||
import MacOSTitleBar from './components/MacOSTitleBar.vue';
|
import MacOSTitleBar from './components/MacOSTitleBar.vue';
|
||||||
@@ -51,6 +51,9 @@
|
|||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.$pinia = store;
|
window.$pinia = store;
|
||||||
|
// Bridge: attach coordinator functions to store for C# IPC callbacks
|
||||||
|
store.game.updateIsGameRunning = runUpdateIsGameRunningFlow;
|
||||||
|
store.gameLog.addGameLogEvent = addGameLogEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
|
|||||||
@@ -550,7 +550,7 @@
|
|||||||
DialogTitle
|
DialogTitle
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
|
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 { Button } from '@/components/ui/button';
|
||||||
import { Check as CheckIcon } from 'lucide-vue-next';
|
import { Check as CheckIcon } from 'lucide-vue-next';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
@@ -561,7 +561,6 @@
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
buildLegacyInstanceTag,
|
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
getLaunchURL,
|
getLaunchURL,
|
||||||
hasGroupPermission,
|
hasGroupPermission,
|
||||||
@@ -569,23 +568,22 @@
|
|||||||
parseLocation,
|
parseLocation,
|
||||||
userImage,
|
userImage,
|
||||||
userStatusClass
|
userStatusClass
|
||||||
} from '../../shared/utils';
|
} from '../../../shared/utils';
|
||||||
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
|
||||||
import {
|
import {
|
||||||
useFriendStore,
|
useFriendStore,
|
||||||
useGroupStore,
|
useGroupStore,
|
||||||
useInstanceStore,
|
|
||||||
useInviteStore,
|
useInviteStore,
|
||||||
useLaunchStore,
|
useLaunchStore,
|
||||||
useLocationStore,
|
useLocationStore,
|
||||||
useUserStore
|
useUserStore
|
||||||
} from '../../stores';
|
} from '../../../stores';
|
||||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
import { ToggleGroup, ToggleGroupItem } from '../../ui/toggle-group';
|
||||||
import { groupRequest, instanceRequest, queryRequest } from '../../api';
|
import { instanceRequest, queryRequest } from '../../../api';
|
||||||
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
|
import { VirtualCombobox } from '../../ui/virtual-combobox';
|
||||||
import { VirtualCombobox } from '../ui/virtual-combobox';
|
import { useNewInstanceBuilder } from './useNewInstanceBuilder';
|
||||||
|
|
||||||
import InviteDialog from './InviteDialog/InviteDialog.vue';
|
import InviteDialog from '../InviteDialog/InviteDialog.vue';
|
||||||
import configRepository from '../../services/config';
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
newInstanceDialogLocationTag: {
|
newInstanceDialogLocationTag: {
|
||||||
@@ -602,40 +600,20 @@
|
|||||||
|
|
||||||
const { friends, vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore());
|
const { friends, vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore());
|
||||||
const { currentUserGroups } = storeToRefs(useGroupStore());
|
const { currentUserGroups } = storeToRefs(useGroupStore());
|
||||||
const { cachedGroups, handleGroupPermissions } = useGroupStore();
|
|
||||||
const { lastLocation } = storeToRefs(useLocationStore());
|
const { lastLocation } = storeToRefs(useLocationStore());
|
||||||
const { showLaunchDialog, tryOpenInstanceInVrc } = useLaunchStore();
|
const { showLaunchDialog, tryOpenInstanceInVrc } = useLaunchStore();
|
||||||
const { createNewInstance } = useInstanceStore();
|
const { currentUser } = storeToRefs(useUserStore());
|
||||||
const { currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
|
|
||||||
const { canOpenInstanceInGame } = useInviteStore();
|
const { canOpenInstanceInGame } = useInviteStore();
|
||||||
|
|
||||||
const newInstanceDialog = ref({
|
const {
|
||||||
visible: false,
|
newInstanceDialog,
|
||||||
// loading: false,
|
buildInstance,
|
||||||
selectedTab: 'Normal',
|
buildLegacyInstance,
|
||||||
instanceCreated: false,
|
updateNewInstanceDialog,
|
||||||
queueEnabled: false,
|
handleCreateNewInstance,
|
||||||
worldId: '',
|
newInstanceTabClick,
|
||||||
instanceId: '',
|
handleRoleIdsChange
|
||||||
instanceName: '',
|
} = useNewInstanceBuilder(toRef(props, 'newInstanceDialogLocationTag'));
|
||||||
userId: '',
|
|
||||||
accessType: 'public',
|
|
||||||
region: 'US West',
|
|
||||||
groupRegion: '',
|
|
||||||
groupId: '',
|
|
||||||
groupAccessType: 'plus',
|
|
||||||
ageGate: false,
|
|
||||||
strict: false,
|
|
||||||
location: '',
|
|
||||||
shortName: '',
|
|
||||||
displayName: '',
|
|
||||||
url: '',
|
|
||||||
secureOrShortName: '',
|
|
||||||
lastSelectedGroupId: '',
|
|
||||||
selectedGroupRoles: [],
|
|
||||||
roleIds: [],
|
|
||||||
groupRef: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
const inviteDialog = ref({
|
const inviteDialog = ref({
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -762,25 +740,6 @@
|
|||||||
return groups;
|
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();
|
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
|
* @param location
|
||||||
@@ -978,114 +807,7 @@
|
|||||||
return args;
|
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
|
* @param location
|
||||||
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -413,12 +413,11 @@
|
|||||||
import ImageCropDialog from '../ImageCropDialog.vue';
|
import ImageCropDialog from '../ImageCropDialog.vue';
|
||||||
import WorldDialogInfoTab from './WorldDialogInfoTab.vue';
|
import WorldDialogInfoTab from './WorldDialogInfoTab.vue';
|
||||||
import WorldDialogInstancesTab from './WorldDialogInstancesTab.vue';
|
import WorldDialogInstancesTab from './WorldDialogInstancesTab.vue';
|
||||||
import { showUserDialog } from '../../../coordinators/userCoordinator';
|
import { showUserDialog } from '../../../coordinators/userCoordinator';
|
||||||
|
|
||||||
const SetWorldTagsDialog = defineAsyncComponent(() => import('./SetWorldTagsDialog.vue'));
|
const SetWorldTagsDialog = defineAsyncComponent(() => import('./SetWorldTagsDialog.vue'));
|
||||||
const WorldAllowedDomainsDialog = defineAsyncComponent(() => import('./WorldAllowedDomainsDialog.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 { currentUser, userDialog } = storeToRefs(useUserStore());
|
||||||
const { worldDialog } = storeToRefs(useWorldStore());
|
const { worldDialog } = storeToRefs(useWorldStore());
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '../../shared/utils';
|
} from '../../shared/utils';
|
||||||
import { createMediaParsers } from './mediaParsers';
|
import { createMediaParsers } from './mediaParsers';
|
||||||
import { database } from '../../services/database';
|
import { database } from '../../services/database';
|
||||||
|
import { tryLoadPlayerList } from '../../coordinators/gameLogCoordinator';
|
||||||
import { useAdvancedSettingsStore } from '../settings/advanced';
|
import { useAdvancedSettingsStore } from '../settings/advanced';
|
||||||
import { useFriendStore } from '../friend';
|
import { useFriendStore } from '../friend';
|
||||||
import { useGameStore } from '../game';
|
import { useGameStore } from '../game';
|
||||||
@@ -26,8 +27,6 @@ import { useVrStore } from '../vr';
|
|||||||
import { useVrcxStore } from '../vrcx';
|
import { useVrcxStore } from '../vrcx';
|
||||||
import { watchState } from '../../services/watchState';
|
import { watchState } from '../../services/watchState';
|
||||||
|
|
||||||
import { tryLoadPlayerList, addGameLogEvent } from '../../coordinators/gameLogCoordinator';
|
|
||||||
|
|
||||||
import configRepository from '../../services/config';
|
import configRepository from '../../services/config';
|
||||||
|
|
||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
@@ -182,6 +181,9 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
|||||||
vrStore.updateVrNowPlaying();
|
vrStore.updateVrNowPlaying();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
function resetLastMediaUrls() {
|
function resetLastMediaUrls() {
|
||||||
lastVideoUrl.value = '';
|
lastVideoUrl.value = '';
|
||||||
lastResourceloadUrl.value = '';
|
lastResourceloadUrl.value = '';
|
||||||
@@ -470,9 +472,6 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
|||||||
addGameLogVRDancing,
|
addGameLogVRDancing,
|
||||||
addGameLogZuwaZuwaDance,
|
addGameLogZuwaZuwaDance,
|
||||||
addGameLogLSMedia,
|
addGameLogLSMedia,
|
||||||
addGameLogPopcornPalace,
|
addGameLogPopcornPalace
|
||||||
|
|
||||||
// Re-exported from coordinator (called by C# via window.$pinia)
|
|
||||||
addGameLogEvent
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -393,25 +393,20 @@
|
|||||||
useUserStore,
|
useUserStore,
|
||||||
useWorldStore
|
useWorldStore
|
||||||
} from '../../stores';
|
} from '../../stores';
|
||||||
import {
|
import { convertFileUrlToImageUrl, replaceBioSymbols, userImage } from '../../shared/utils';
|
||||||
compareByCreatedAt,
|
import { refreshUserDialogAvatars, showUserDialog } from '../../coordinators/userCoordinator';
|
||||||
compareByName,
|
import { groupRequest } from '../../api';
|
||||||
compareByUpdatedAt,
|
import { useSearchAvatar } from './composables/useSearchAvatar';
|
||||||
convertFileUrlToImageUrl,
|
import { useSearchWorld } from './composables/useSearchWorld';
|
||||||
replaceBioSymbols,
|
|
||||||
userImage
|
|
||||||
} from '../../shared/utils';
|
|
||||||
import { groupRequest, worldRequest } from '../../api';
|
|
||||||
import { showUserDialog, refreshUserDialogAvatars } from '../../coordinators/userCoordinator';
|
|
||||||
|
|
||||||
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
|
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
|
||||||
const { avatarRemoteDatabase } = storeToRefs(useAdvancedSettingsStore());
|
|
||||||
const { avatarRemoteDatabaseProviderList, avatarRemoteDatabaseProvider } = storeToRefs(useAvatarProviderStore());
|
const { avatarRemoteDatabaseProviderList, avatarRemoteDatabaseProvider } = storeToRefs(useAvatarProviderStore());
|
||||||
const { setAvatarProvider } = useAvatarProviderStore();
|
const { setAvatarProvider } = useAvatarProviderStore();
|
||||||
|
const { avatarRemoteDatabase } = storeToRefs(useAdvancedSettingsStore());
|
||||||
const { userDialog } = storeToRefs(useUserStore());
|
const { userDialog } = storeToRefs(useUserStore());
|
||||||
|
|
||||||
const { showAvatarDialog, lookupAvatars, cachedAvatars } = useAvatarStore();
|
const { showAvatarDialog } = useAvatarStore();
|
||||||
const { cachedWorlds, showWorldDialog } = useWorldStore();
|
const { showWorldDialog } = useWorldStore();
|
||||||
const { showGroupDialog } = useGroupStore();
|
const { showGroupDialog } = useGroupStore();
|
||||||
const { searchText, searchUserResults } = storeToRefs(useSearchStore());
|
const { searchText, searchUserResults } = storeToRefs(useSearchStore());
|
||||||
const { clearSearch, moreSearchUser } = useSearchStore();
|
const { clearSearch, moreSearchUser } = useSearchStore();
|
||||||
@@ -427,56 +422,41 @@ import { showUserDialog, refreshUserDialogAvatars } from '../../coordinators/use
|
|||||||
{ value: 'group', label: t('view.search.group.header') }
|
{ 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 searchUserParams = ref({});
|
||||||
const searchUserByBio = ref(false);
|
const searchUserByBio = ref(false);
|
||||||
const searchUserSortByLastLoggedIn = ref(false);
|
const searchUserSortByLastLoggedIn = ref(false);
|
||||||
|
|
||||||
const isSearchUserLoading = ref(false);
|
const isSearchUserLoading = ref(false);
|
||||||
const isSearchWorldLoading = ref(false);
|
|
||||||
const isSearchAvatarLoading = ref(false);
|
|
||||||
const isSearchGroupLoading = 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 searchGroupParams = ref({});
|
||||||
const searchGroupResults = ref([]);
|
const searchGroupResults = ref([]);
|
||||||
|
|
||||||
@@ -493,11 +473,8 @@ import { showUserDialog, refreshUserDialogAvatars } from '../../coordinators/use
|
|||||||
*/
|
*/
|
||||||
function handleClearSearch() {
|
function handleClearSearch() {
|
||||||
searchUserParams.value = {};
|
searchUserParams.value = {};
|
||||||
searchWorldParams.value = {};
|
clearWorldSearch();
|
||||||
searchWorldResults.value = [];
|
clearAvatarSearch();
|
||||||
searchAvatarResults.value = [];
|
|
||||||
searchAvatarPage.value = [];
|
|
||||||
searchAvatarPageNum.value = 0;
|
|
||||||
searchGroupParams.value = {};
|
searchGroupParams.value = {};
|
||||||
searchGroupResults.value = [];
|
searchGroupResults.value = [];
|
||||||
clearSearch();
|
clearSearch();
|
||||||
@@ -564,237 +541,6 @@ import { showUserDialog, refreshUserDialogAvatars } from '../../coordinators/use
|
|||||||
isSearchUserLoading.value = false;
|
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);
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|||||||
212
src/views/Search/composables/useSearchAvatar.js
Normal file
212
src/views/Search/composables/useSearchAvatar.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
160
src/views/Search/composables/useSearchWorld.js
Normal file
160
src/views/Search/composables/useSearchWorld.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user