This commit is contained in:
pa
2026-03-09 02:49:59 +09:00
parent 64b27ce7f1
commit 90a17bb0ba
39 changed files with 9487 additions and 4384 deletions

View File

@@ -0,0 +1,601 @@
import { nextTick, ref } from 'vue';
import {
handleImageUploadInput,
readFileAsBase64,
resizeImageToFitLimits,
uploadImageLegacy,
withUploadTimeout
} from '../../../shared/utils/imageUpload';
import {
favoriteRequest,
miscRequest,
userRequest,
worldRequest
} from '../../../api';
import { openExternalLink, replaceVrcPackageUrl } from '../../../shared/utils';
/**
* Composable for WorldDialog commands, prompt functions, and image upload.
* @param {import('vue').Ref} worldDialog - reactive ref to the world dialog state
* @param {object} deps - external dependencies
* @param {Function} deps.t - i18n translation function
* @param {Function} deps.toast - toast notification function
* @param {object} deps.modalStore - modal store for confirm/prompt dialogs
* @param {import('vue').Ref} deps.userDialog - reactive ref to the user dialog state
* @param {Map} deps.cachedWorlds - cached worlds map
* @param {Function} deps.showWorldDialog - function to show world dialog
* @param {Function} deps.showFavoriteDialog - function to show favorite dialog
* @param {Function} deps.newInstanceSelfInvite - function for new instance self invite
* @param {Function} deps.showPreviousInstancesListDialog - function to show previous instances
* @param {Function} deps.showFullscreenImageDialog - function to show fullscreen image
* @returns {object} commands composable API
*/
export function useWorldDialogCommands(
worldDialog,
{
t,
toast,
modalStore,
userDialog,
cachedWorlds,
showWorldDialog,
showFavoriteDialog,
newInstanceSelfInvite,
showPreviousInstancesListDialog: openPreviousInstancesListDialog,
showFullscreenImageDialog
}
) {
const worldAllowedDomainsDialog = ref({
visible: false,
worldId: '',
urlList: []
});
const isSetWorldTagsDialogVisible = ref(false);
const newInstanceDialogLocationTag = ref('');
const cropDialogOpen = ref(false);
const cropDialogFile = ref(null);
const changeWorldImageLoading = ref(false);
/**
*
*/
function showChangeWorldImageDialog() {
document.getElementById('WorldImageUploadButton').click();
}
/**
*
* @param e
*/
function onFileChangeWorldImage(e) {
const { file, clearInput } = handleImageUploadInput(e, {
inputSelector: '#WorldImageUploadButton',
tooLargeMessage: () => t('message.file.too_large'),
invalidTypeMessage: () => t('message.file.not_image')
});
if (!file) {
return;
}
if (!worldDialog.value.visible || worldDialog.value.loading) {
clearInput();
return;
}
clearInput();
cropDialogFile.value = file;
cropDialogOpen.value = true;
}
/**
*
* @param blob
*/
async function onCropConfirmWorld(blob) {
changeWorldImageLoading.value = true;
try {
await withUploadTimeout(
(async () => {
const base64Body = await readFileAsBase64(blob);
const base64File = await resizeImageToFitLimits(base64Body);
await uploadImageLegacy('world', {
entityId: worldDialog.value.id,
imageUrl: worldDialog.value.ref.imageUrl,
base64File,
blob
});
})()
);
toast.success(t('message.upload.success'));
} catch (error) {
console.error('World image upload process failed:', error);
toast.error(t('message.upload.error'));
} finally {
changeWorldImageLoading.value = false;
cropDialogOpen.value = false;
}
}
/**
*
* @param tag
*/
function showNewInstanceDialog(tag) {
// trigger watcher
newInstanceDialogLocationTag.value = '';
nextTick(() => (newInstanceDialogLocationTag.value = tag));
}
/**
*
*/
function copyWorldUrl() {
navigator.clipboard
.writeText(`https://vrchat.com/home/world/${worldDialog.value.id}`)
.then(() => {
toast.success(t('message.world.url_copied'));
})
.catch((err) => {
console.error('copy failed:', err);
toast.error(t('message.copy_failed'));
});
}
/**
*
*/
function copyWorldName() {
navigator.clipboard
.writeText(worldDialog.value.ref.name)
.then(() => {
toast.success(t('message.world.name_copied'));
})
.catch((err) => {
console.error('copy failed:', err);
toast.error(t('message.copy_failed'));
});
}
/**
*
*/
function showWorldAllowedDomainsDialog() {
const D = worldAllowedDomainsDialog.value;
D.worldId = worldDialog.value.id;
D.urlList = worldDialog.value.ref?.urlList ?? [];
D.visible = true;
}
/**
*
* @param worldRef
*/
function showPreviousInstancesListDialog(worldRef) {
openPreviousInstancesListDialog('world', worldRef);
}
/**
*
* @param world
*/
function promptRenameWorld(world) {
modalStore
.prompt({
title: t('prompt.rename_world.header'),
description: t('prompt.rename_world.description'),
confirmText: t('prompt.rename_world.ok'),
cancelText: t('prompt.rename_world.cancel'),
inputValue: world.ref.name,
errorMessage: t('prompt.rename_world.input_error')
})
.then(({ ok, value }) => {
if (!ok) return;
if (value && value !== world.ref.name) {
worldRequest
.saveWorld({
id: world.id,
name: value
})
.then((args) => {
toast.success(
t('prompt.rename_world.message.success')
);
return args;
});
}
})
.catch(() => {});
}
/**
*
* @param world
*/
function promptChangeWorldDescription(world) {
modalStore
.prompt({
title: t('prompt.change_world_description.header'),
description: t('prompt.change_world_description.description'),
confirmText: t('prompt.change_world_description.ok'),
cancelText: t('prompt.change_world_description.cancel'),
inputValue: world.ref.description,
errorMessage: t('prompt.change_world_description.input_error')
})
.then(({ ok, value }) => {
if (!ok) return;
if (value && value !== world.ref.description) {
worldRequest
.saveWorld({
id: world.id,
description: value
})
.then((args) => {
toast.success(
t(
'prompt.change_world_description.message.success'
)
);
return args;
});
}
})
.catch(() => {});
}
/**
*
* @param world
*/
function promptChangeWorldCapacity(world) {
modalStore
.prompt({
title: t('prompt.change_world_capacity.header'),
description: t('prompt.change_world_capacity.description'),
confirmText: t('prompt.change_world_capacity.ok'),
cancelText: t('prompt.change_world_capacity.cancel'),
inputValue: world.ref.capacity,
pattern: /\d+$/,
errorMessage: t('prompt.change_world_capacity.input_error')
})
.then(({ ok, value }) => {
if (!ok) return;
if (value && value !== world.ref.capacity) {
worldRequest
.saveWorld({
id: world.id,
capacity: Number(value)
})
.then((args) => {
toast.success(
t(
'prompt.change_world_capacity.message.success'
)
);
return args;
});
}
})
.catch(() => {});
}
/**
*
* @param world
*/
function promptChangeWorldRecommendedCapacity(world) {
modalStore
.prompt({
title: t('prompt.change_world_recommended_capacity.header'),
description: t(
'prompt.change_world_recommended_capacity.description'
),
confirmText: t('prompt.change_world_capacity.ok'),
cancelText: t('prompt.change_world_capacity.cancel'),
inputValue: world.ref.recommendedCapacity,
pattern: /\d+$/,
errorMessage: t(
'prompt.change_world_recommended_capacity.input_error'
)
})
.then(({ ok, value }) => {
if (!ok) return;
if (value && value !== world.ref.recommendedCapacity) {
worldRequest
.saveWorld({
id: world.id,
recommendedCapacity: Number(value)
})
.then((args) => {
toast.success(
t(
'prompt.change_world_recommended_capacity.message.success'
)
);
return args;
});
}
})
.catch(() => {});
}
/**
*
* @param world
*/
function promptChangeWorldYouTubePreview(world) {
modalStore
.prompt({
title: t('prompt.change_world_preview.header'),
description: t('prompt.change_world_preview.description'),
confirmText: t('prompt.change_world_preview.ok'),
cancelText: t('prompt.change_world_preview.cancel'),
inputValue: world.ref.previewYoutubeId,
errorMessage: t('prompt.change_world_preview.input_error')
})
.then(({ ok, value }) => {
if (!ok) return;
if (value && value !== world.ref.previewYoutubeId) {
let processedValue = value;
if (value.length > 11) {
try {
const url = new URL(value);
const id1 = url.pathname;
const id2 = url.searchParams.get('v');
if (id1 && id1.length === 12) {
processedValue = id1.substring(1, 12);
}
if (id2 && id2.length === 11) {
processedValue = id2;
}
} catch {
toast.error(
t('prompt.change_world_preview.message.error')
);
return;
}
}
if (processedValue !== world.ref.previewYoutubeId) {
worldRequest
.saveWorld({
id: world.id,
previewYoutubeId: processedValue
})
.then((args) => {
toast.success(
t(
'prompt.change_world_preview.message.success'
)
);
return args;
});
}
}
})
.catch(() => {});
}
/**
*
* @param command
*/
function worldDialogCommand(command) {
const D = worldDialog.value;
if (D.visible === false) {
return;
}
switch (command) {
case 'Delete Favorite':
case 'Make Home':
case 'Reset Home':
case 'Publish':
case 'Unpublish':
case 'Delete Persistent Data':
case 'Delete':
const commandLabelMap = {
'Delete Favorite': t(
'dialog.world.actions.favorites_tooltip'
),
'Make Home': t('dialog.world.actions.make_home'),
'Reset Home': t('dialog.world.actions.reset_home'),
Publish: t('dialog.world.actions.publish_to_labs'),
Unpublish: t('dialog.world.actions.unpublish'),
'Delete Persistent Data': t(
'dialog.world.actions.delete_persistent_data'
),
Delete: t('dialog.world.actions.delete')
};
modalStore
.confirm({
description: t('confirm.command_question', {
command: commandLabelMap[command] ?? command
}),
title: t('confirm.title')
})
.then(({ ok }) => {
if (!ok) return;
switch (command) {
case 'Delete Favorite':
favoriteRequest.deleteFavorite({
objectId: D.id
});
break;
case 'Make Home':
userRequest
.saveCurrentUser({
homeLocation: D.id
})
.then((args) => {
toast.success(
t('message.world.home_updated')
);
return args;
});
break;
case 'Reset Home':
userRequest
.saveCurrentUser({
homeLocation: ''
})
.then((args) => {
toast.success(
t('message.world.home_reset')
);
return args;
});
break;
case 'Publish':
worldRequest
.publishWorld({
worldId: D.id
})
.then((args) => {
toast.success(
t('message.world.published')
);
return args;
});
break;
case 'Unpublish':
worldRequest
.unpublishWorld({
worldId: D.id
})
.then((args) => {
toast.success(
t('message.world.unpublished')
);
return args;
});
break;
case 'Delete Persistent Data':
miscRequest
.deleteWorldPersistData({
worldId: D.id
})
.then((args) => {
if (
args.params.worldId ===
worldDialog.value.id &&
worldDialog.value.visible
) {
worldDialog.value.hasPersistData = false;
}
toast.success(
t(
'message.world.persistent_data_deleted'
)
);
return args;
});
break;
case 'Delete':
worldRequest
.deleteWorld({
worldId: D.id
})
.then((args) => {
const { json } = args;
cachedWorlds.delete(json.id);
if (
worldDialog.value.ref.authorId ===
json.authorId
) {
const map = new Map();
for (const ref of cachedWorlds.values()) {
if (
ref.authorId ===
json.authorId
) {
map.set(ref.id, ref);
}
}
const array = Array.from(
map.values()
);
userDialog.value.worlds = array;
}
toast.success(
t('message.world.deleted')
);
D.visible = false;
return args;
});
break;
}
})
.catch(() => {});
break;
case 'Previous Instances':
showPreviousInstancesListDialog(D.ref);
break;
case 'Share':
copyWorldUrl();
break;
case 'Change Allowed Domains':
showWorldAllowedDomainsDialog();
break;
case 'Change Tags':
isSetWorldTagsDialogVisible.value = true;
break;
case 'Download Unity Package':
openExternalLink(
replaceVrcPackageUrl(worldDialog.value.ref.unityPackageUrl)
);
break;
case 'Change Image':
showChangeWorldImageDialog();
break;
case 'Refresh':
const { tag, shortName } = worldDialog.value.$location;
showWorldDialog(tag, shortName, { forceRefresh: true });
break;
case 'New Instance':
showNewInstanceDialog(D.$location.tag);
break;
case 'Add Favorite':
showFavoriteDialog('world', D.id);
break;
case 'New Instance and Self Invite':
newInstanceSelfInvite(D.id);
break;
case 'Rename':
promptRenameWorld(D);
break;
case 'Change Description':
promptChangeWorldDescription(D);
break;
case 'Change Capacity':
promptChangeWorldCapacity(D);
break;
case 'Change Recommended Capacity':
promptChangeWorldRecommendedCapacity(D);
break;
case 'Change YouTube Preview':
promptChangeWorldYouTubePreview(D);
break;
default:
break;
}
}
return {
worldAllowedDomainsDialog,
isSetWorldTagsDialogVisible,
newInstanceDialogLocationTag,
cropDialogOpen,
cropDialogFile,
changeWorldImageLoading,
worldDialogCommand,
onFileChangeWorldImage,
onCropConfirmWorld,
showNewInstanceDialog,
copyWorldUrl,
copyWorldName,
showWorldAllowedDomainsDialog,
showPreviousInstancesListDialog,
showFullscreenImageDialog,
promptRenameWorld,
promptChangeWorldDescription,
promptChangeWorldCapacity,
promptChangeWorldRecommendedCapacity,
promptChangeWorldYouTubePreview
};
}