Add invite message editing to tools tab

This commit is contained in:
Natsumi
2025-11-26 17:31:30 +11:00
parent d99a2c0cf1
commit b899d7e09b
5 changed files with 334 additions and 33 deletions

View File

@@ -16,27 +16,20 @@
<div style="flex: 1; display: flex; align-items: center; margin-left: 15px">
<div style="flex: 1">
<div>
<el-popover placement="top" trigger="click">
<template #reference>
<span
class="dialog-title"
style="margin-right: 5px; cursor: pointer"
@click="copyToClipboard(worldDialog.ref.name)">
<el-icon
v-if="
currentUser.$homeLocation &&
currentUser.$homeLocation.worldId === worldDialog.id
"
style="margin-right: 5px"
><HomeFilled
/></el-icon>
{{ worldDialog.ref.name }}
</span>
</template>
<span style="display: block; text-align: center; font-family: monospace">{{
textToHex(worldDialog.ref.name)
}}</span>
</el-popover>
<span
class="dialog-title"
style="margin-right: 5px; cursor: pointer"
@click="copyWorldName">
<el-icon
v-if="
currentUser.$homeLocation &&
currentUser.$homeLocation.worldId === worldDialog.id
"
style="margin-right: 5px"
><HomeFilled
/></el-icon>
{{ worldDialog.ref.name }}
</span>
</div>
<div style="margin-top: 5px">
<span

View File

@@ -376,6 +376,11 @@
"screenshot_description": "Manage screenshots and view metadata",
"inventory": "VRC+ Images & Inventory Management",
"inventory_description": "Manage VRC+ Images & Inventory"
},
"other": {
"header": "Other",
"edit_invite_message": "Edit Invite Messages",
"edit_invite_message_description": "Edit invite and invite response messages"
}
},
"profile": {
@@ -400,18 +405,6 @@
"vrc_sdk_downloads": {
"header": "VRC SDK Downloads"
},
"direct_access": {
"header": "Direct Access",
"username": "Username",
"user_id": "User ID",
"world_instance": "World/Instance",
"avatar": "Avatar"
},
"invite_messages": "Invite Messages",
"invite_response_messages": "Invite Response Messages",
"invite_request_messages": "Invite Request Messages",
"invite_request_response_messages": "Invite Request Response Messages",
"past_display_names": "Past Display Names",
"config_json": "Config JSON",
"current_user_json": "Current User JSON",
"feedback": "Feedback",
@@ -1601,6 +1594,13 @@
"cancel": "Cancel",
"save": "Save"
},
"edit_invite_messages": {
"header": "Edit Invite Messages",
"invite_message_tab": "Invite",
"invite_request_tab": "Invite Request",
"invite_request_response_tab": "Invite Request Response",
"invite_response_tab": "Invite Response"
},
"invite_message": {
"header": "Send Invite Message",
"confirmation": "Are you sure you want to send?",

View File

@@ -155,6 +155,30 @@
</el-card>
</div>
</div>
<div class="tool-category">
<div class="category-header" @click="toggleCategory('other')">
<el-icon class="rotation-transition" :class="{ 'is-rotated': !categoryCollapsed['other'] }"
><ArrowRight
/></el-icon>
<span class="category-title">{{ t('view.tools.other.header') }}</span>
</div>
<div class="tools-grid" v-show="!categoryCollapsed['other']">
<el-card :body-style="{ padding: '0px' }" class="tool-card">
<div class="tool-content" @click="showEditInviteMessageDialog">
<div class="tool-icon">
<i class="ri-edit-box-line"></i>
</div>
<div class="tool-info">
<div class="tool-name">{{ t('view.tools.other.edit_invite_message') }}</div>
<div class="tool-description">
{{ t('view.tools.other.edit_invite_message_description') }}
</div>
</div>
</div>
</el-card>
</div>
</div>
</div>
</div>
<template v-if="isToolsTabVisible">
@@ -175,6 +199,9 @@
v-model:isExportFriendsListDialogVisible="isExportFriendsListDialogVisible"
:friends="friends" />
<ExportAvatarsListDialog v-model:isExportAvatarsListDialogVisible="isExportAvatarsListDialogVisible" />
<EditInviteMessageDialog
v-model:isEditInviteMessagesDialogVisible="isEditInviteMessagesDialogVisible"
@close="isEditInviteMessagesDialogVisible = false" />
</template>
</div>
</template>
@@ -193,6 +220,7 @@
const ScreenshotMetadataDialog = defineAsyncComponent(() => import('./dialogs/ScreenshotMetadataDialog.vue'));
const NoteExportDialog = defineAsyncComponent(() => import('./dialogs/NoteExportDialog.vue'));
const GalleryDialog = defineAsyncComponent(() => import('./dialogs/GalleryDialog.vue'));
const EditInviteMessageDialog = defineAsyncComponent(() => import('./dialogs/EditInviteMessagesDialog.vue'));
const ExportDiscordNamesDialog = defineAsyncComponent(() => import('./dialogs/ExportDiscordNamesDialog.vue'));
const ExportFriendsListDialog = defineAsyncComponent(() => import('./dialogs/ExportFriendsListDialog.vue'));
@@ -215,6 +243,7 @@
const isExportDiscordNamesDialogVisible = ref(false);
const isExportFriendsListDialogVisible = ref(false);
const isExportAvatarsListDialogVisible = ref(false);
const isEditInviteMessagesDialogVisible = ref(false);
const isToolsTabVisible = computed(() => {
return useRoute().name === 'tools';
});
@@ -235,6 +264,10 @@
categoryCollapsed.value[category] = !categoryCollapsed.value[category];
};
const showEditInviteMessageDialog = () => {
isEditInviteMessagesDialogVisible.value = true;
};
function showExportDiscordNamesDialog() {
isExportDiscordNamesDialogVisible.value = true;
}

View File

@@ -0,0 +1,89 @@
<template>
<el-dialog
class="x-dialog"
:model-value="isEditInviteMessageDialogVisible"
:title="t('dialog.edit_invite_message.header')"
width="400px"
@close="closeDialog">
<div style="font-size: 12px">
<span>{{ t('dialog.edit_invite_message.description') }}</span>
<el-input
v-model="message"
type="textarea"
size="small"
maxlength="64"
show-word-limit
:autosize="{ minRows: 2, maxRows: 5 }"
placeholder=""
style="margin-top: 10px"></el-input>
</div>
<template #footer>
<el-button @click="closeDialog">{{ t('dialog.edit_invite_message.cancel') }}</el-button>
<el-button type="primary" @click="saveEditInviteMessage">{{
t('dialog.edit_invite_message.save')
}}</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { inviteMessagesRequest } from '../../../api';
const { t } = useI18n();
const props = defineProps({
isEditInviteMessageDialogVisible: { type: Boolean, default: false },
inviteMessage: { type: Object, required: true }
});
const emit = defineEmits(['update:isEditInviteMessageDialogVisible', 'updateInviteMessages']);
const message = ref('');
watch(
() => props.inviteMessage,
(inviteMessage) => {
if (inviteMessage) {
message.value = inviteMessage.message;
}
},
{ deep: true }
);
function saveEditInviteMessage() {
closeDialog();
if (props.inviteMessage.message !== message.value) {
const slot = props.inviteMessage.slot;
const messageType = props.inviteMessage.messageType;
const params = {
message: message.value
};
inviteMessagesRequest
.editInviteMessage(params, messageType, slot)
.catch((err) => {
throw err;
})
.then((args) => {
if (args.json[slot].message === props.inviteMessage.message) {
ElMessage({
message: "VRChat API didn't update message, try again",
type: 'error'
});
throw new Error("VRChat API didn't update message, try again");
} else {
ElMessage({ message: 'Invite message updated', type: 'success' });
emit('updateInviteMessages', messageType);
}
return args;
});
}
}
function closeDialog() {
emit('update:isEditInviteMessageDialogVisible', false);
}
</script>

View File

@@ -0,0 +1,186 @@
<template>
<el-dialog
class="x-dialog"
:model-value="isEditInviteMessagesDialogVisible"
:title="t('dialog.edit_invite_messages.header')"
width="1000px"
@close="closeDialog">
<el-tabs v-model="activeTab" style="margin-top: 10px">
<el-tab-pane :label="t('dialog.edit_invite_messages.invite_message_tab')" name="message">
<DataTable
v-bind="inviteMessageTable"
style="margin-top: 10px; cursor: pointer"
@row-click="showEditInviteMessageDialog">
<el-table-column
:label="t('table.profile.invite_messages.slot')"
prop="slot"
:sortable="true"
width="70"></el-table-column>
<el-table-column
:label="t('table.profile.invite_messages.message')"
prop="message"></el-table-column>
<el-table-column
:label="t('table.profile.invite_messages.cool_down')"
prop="updatedAt"
:sortable="true"
width="110"
align="right">
<template #default="scope">
<countdown-timer :datetime="scope.row.updatedAt" :hours="1"></countdown-timer>
</template>
</el-table-column>
</DataTable>
</el-tab-pane>
<el-tab-pane :label="t('dialog.edit_invite_messages.invite_request_tab')" name="request">
<DataTable
v-bind="inviteRequestMessageTable"
style="margin-top: 10px; cursor: pointer"
@row-click="showEditInviteMessageDialog">
<el-table-column
:label="t('table.profile.invite_messages.slot')"
prop="slot"
:sortable="true"
width="70"></el-table-column>
<el-table-column
:label="t('table.profile.invite_messages.message')"
prop="message"></el-table-column>
<el-table-column
:label="t('table.profile.invite_messages.cool_down')"
prop="updatedAt"
:sortable="true"
width="110"
align="right">
<template #default="scope">
<countdown-timer :datetime="scope.row.updatedAt" :hours="1"></countdown-timer>
</template>
</el-table-column>
</DataTable>
</el-tab-pane>
<el-tab-pane :label="t('dialog.edit_invite_messages.invite_request_response_tab')" name="requestResponse">
<DataTable
v-bind="inviteRequestResponseMessageTable"
style="margin-top: 10px; cursor: pointer"
@row-click="showEditInviteMessageDialog">
<el-table-column
:label="t('table.profile.invite_messages.slot')"
prop="slot"
:sortable="true"
width="70"></el-table-column>
<el-table-column
:label="t('table.profile.invite_messages.message')"
prop="message"></el-table-column>
<el-table-column
:label="t('table.profile.invite_messages.cool_down')"
prop="updatedAt"
:sortable="true"
width="110"
align="right">
<template #default="scope">
<countdown-timer :datetime="scope.row.updatedAt" :hours="1"></countdown-timer>
</template>
</el-table-column>
</DataTable>
</el-tab-pane>
<el-tab-pane :label="t('dialog.edit_invite_messages.invite_response_tab')" name="response">
<DataTable
v-bind="inviteResponseMessageTable"
style="margin-top: 10px; cursor: pointer"
@row-click="showEditInviteMessageDialog">
<el-table-column
:label="t('table.profile.invite_messages.slot')"
prop="slot"
:sortable="true"
width="70"></el-table-column>
<el-table-column
:label="t('table.profile.invite_messages.message')"
prop="message"></el-table-column>
<el-table-column
:label="t('table.profile.invite_messages.cool_down')"
prop="updatedAt"
:sortable="true"
width="110"
align="right">
<template #default="scope">
<countdown-timer :datetime="scope.row.updatedAt" :hours="1"></countdown-timer>
</template>
</el-table-column>
</DataTable>
</el-tab-pane>
</el-tabs>
</el-dialog>
<template v-if="isEditInviteMessagesDialogVisible">
<EditInviteMessageDialog
v-model:isEditInviteMessageDialogVisible="isEditInviteMessageDialogVisible"
:inviteMessage="inviteMessage"
@close="isEditInviteMessageDialogVisible = false"
@updateInviteMessages="refreshInviteMessageTableData" />
</template>
</template>
<script setup>
import { ref, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useInviteStore } from '../../../stores';
import DataTable from '../../../components/DataTable.vue';
import EditInviteMessageDialog from './EditInviteMessageDialog.vue';
const {
inviteMessageTable,
inviteRequestMessageTable,
inviteRequestResponseMessageTable,
inviteResponseMessageTable
} = storeToRefs(useInviteStore());
const { refreshInviteMessageTableData } = useInviteStore();
const { t } = useI18n();
const props = defineProps({
isEditInviteMessagesDialogVisible: {
type: Boolean
}
});
const activeTab = ref('message');
const isEditInviteMessageDialogVisible = ref(false);
const inviteMessage = ref({});
watch(
() => props.isEditInviteMessagesDialogVisible,
(newVal) => {
if (newVal) {
refreshInviteMessageTableData('message');
refreshInviteMessageTableData('request');
refreshInviteMessageTableData('requestResponse');
refreshInviteMessageTableData('response');
}
}
);
const emit = defineEmits(['close']);
function closeDialog() {
emit('close');
}
function showEditInviteMessageDialog(row) {
if (row.updatedAt) {
const cooldownEnd = new Date(row.updatedAt);
cooldownEnd.setHours(cooldownEnd.getHours() + 1);
const now = new Date();
if (now < cooldownEnd) {
ElMessage({
message: 'This invite message is on cooldown and cannot be edited yet.',
type: 'warning'
});
return;
}
}
inviteMessage.value = row;
isEditInviteMessageDialogVisible.value = true;
}
</script>