Files
VRCX/src/components/dialogs/GroupDialog/GroupPostEditDialog.vue
2026-03-08 23:59:11 +09:00

294 lines
12 KiB
Vue

<template>
<Dialog v-model:open="groupPostEditDialog.visible">
<DialogContent class="sm:max-w-162.5">
<DialogHeader>
<DialogTitle>{{ t('dialog.group_post_edit.header') }}</DialogTitle>
</DialogHeader>
<div v-if="groupPostEditDialog.visible">
<FieldGroup class="gap-4">
<Field>
<FieldLabel>{{ t('dialog.group_post_edit.title') }}</FieldLabel>
<FieldContent>
<InputGroupField v-model="groupPostEditDialog.title" size="sm" />
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('dialog.group_post_edit.message') }}</FieldLabel>
<FieldContent>
<InputGroupTextareaField
v-model="groupPostEditDialog.text"
:rows="4"
input-class="resize-none mt-2" />
</FieldContent>
</Field>
<Field v-if="!groupPostEditDialog.postId">
<FieldLabel class="sr-only">{{ t('dialog.group_post_edit.send_notification') }}</FieldLabel>
<FieldContent>
<label class="inline-flex items-center gap-2">
<Checkbox v-model="groupPostEditDialog.sendNotification" />
<span>{{ t('dialog.group_post_edit.send_notification') }}</span>
</label>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('dialog.group_post_edit.post_visibility') }}</FieldLabel>
<FieldContent>
<RadioGroup v-model="groupPostEditDialog.visibility" class="flex items-center gap-4">
<div class="flex items-center space-x-2">
<RadioGroupItem id="groupPostVisibility-public" value="public" />
<label for="groupPostVisibility-public">
{{ t('dialog.group_post_edit.visibility_public') }}
</label>
</div>
<div class="flex items-center space-x-2">
<RadioGroupItem id="groupPostVisibility-group" value="group" />
<label for="groupPostVisibility-group">
{{ t('dialog.group_post_edit.visibility_group') }}
</label>
</div>
</RadioGroup>
</FieldContent>
</Field>
<Field v-if="groupPostEditDialog.visibility === 'group'">
<FieldLabel>{{ t('dialog.new_instance.roles') }}</FieldLabel>
<FieldContent>
<Select
multiple
:model-value="
Array.isArray(groupPostEditDialog.roleIds) ? groupPostEditDialog.roleIds : []
"
@update:modelValue="handleRoleIdsChange">
<SelectTrigger size="sm" class="w-full">
<SelectValue>
<span class="truncate">
{{ selectedRoleSummary || t('dialog.new_instance.role_placeholder') }}
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem
v-for="role in groupPostEditDialog.groupRef?.roles ?? []"
:key="role.id"
:value="role.id">
{{ role.name }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('dialog.group_post_edit.image') }}</FieldLabel>
<FieldContent>
<template v-if="gallerySelectDialog.selectedFileId">
<div class="mr-1.5" style="display: inline-block; flex: none">
<img
:src="gallerySelectDialog.selectedImageUrl"
style="
flex: none;
width: 60px;
height: 60px;
border-radius: var(--radius-md);
object-fit: cover;
"
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)"
loading="lazy" />
<Button
size="sm"
variant="outline"
style="vertical-align: top"
@click="clearImageGallerySelect">
{{ t('dialog.invite_message.clear_selected_image') }}
</Button>
</div>
</template>
<template v-else>
<Button size="sm" variant="outline" @click="showGallerySelectDialog">
{{ t('dialog.invite_message.select_image') }}
</Button>
</template>
</FieldContent>
</Field>
</FieldGroup>
</div>
<DialogFooter>
<div class="flex gap-2">
<Button variant="secondary" @click="groupPostEditDialog.visible = false">
{{ t('dialog.group_post_edit.cancel') }}
</Button>
<Button v-if="groupPostEditDialog.postId" @click="editGroupPost">
{{ t('dialog.group_post_edit.edit_post') }}
</Button>
<Button v-else @click="createGroupPost">
{{ t('dialog.group_post_edit.create_post') }}
</Button>
</div>
</DialogFooter>
<GallerySelectDialog
:gallery-select-dialog="gallerySelectDialog"
:gallery-table="galleryTable"
@refresh-gallery-table="refreshGalleryTable" />
</DialogContent>
</Dialog>
</template>
<script setup>
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
import { InputGroupField, InputGroupTextareaField } from '@/components/ui/input-group';
import { computed, ref } from 'vue';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
import { RadioGroup, RadioGroupItem } from '../../ui/radio-group';
import { groupRequest, vrcPlusIconRequest } from '../../../api';
import { useGalleryStore, useGroupStore } from '../../../stores';
import GallerySelectDialog from './GallerySelectDialog.vue';
const props = defineProps({
dialogData: {
type: Object,
required: true
},
selectedGalleryFile: { type: Object, default: () => ({}) }
});
const emit = defineEmits(['update:dialogData']);
const { t } = useI18n();
const { showFullscreenImageDialog, handleFilesList } = useGalleryStore();
const { handleGroupPost } = useGroupStore();
const gallerySelectDialog = ref({
visible: false,
selectedFileId: '',
selectedImageUrl: ''
});
const galleryTable = ref([]);
const groupPostEditDialog = computed({
get() {
return props.dialogData;
},
set(value) {
emit('update:dialogData', value);
}
});
const selectedRoleSummary = computed(() => {
const ids = groupPostEditDialog.value.roleIds ?? [];
if (!Array.isArray(ids) || ids.length === 0) {
return '';
}
const roleById = new Map((groupPostEditDialog.value.groupRef?.roles ?? []).map((r) => [r.id, r.name]));
const names = ids.map((id) => roleById.get(id) ?? String(id));
return names.slice(0, 3).join(', ') + (names.length > 3 ? ` +${names.length - 3}` : '');
});
/**
*
* @param value
*/
function handleRoleIdsChange(value) {
const next = Array.isArray(value) ? value.map((v) => String(v ?? '')).filter(Boolean) : [];
groupPostEditDialog.value.roleIds = next;
}
/**
*
*/
function showGallerySelectDialog() {
const D = gallerySelectDialog.value;
D.visible = true;
refreshGalleryTable();
}
/**
*
*/
async function refreshGalleryTable() {
const params = {
n: 100,
tag: 'gallery'
};
const args = await vrcPlusIconRequest.getFileList(params);
handleFilesList(args);
if (args.params.tag === 'gallery') {
galleryTable.value = args.json.reverse();
}
}
/**
*
*/
function editGroupPost() {
const D = groupPostEditDialog.value;
if (!D.groupId || !D.postId) {
return;
}
if (!D.title || !D.text) {
toast.warning('Title and text are required');
return;
}
const params = {
groupId: D.groupId,
postId: D.postId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
imageId: null
};
if (gallerySelectDialog.value.selectedFileId) {
params.imageId = gallerySelectDialog.value.selectedFileId;
}
groupRequest.editGroupPost(params).then((args) => {
handleGroupPost(args);
toast.success('Group post edited');
});
D.visible = false;
}
/**
*
*/
function createGroupPost() {
const D = groupPostEditDialog.value;
if (!D.title || !D.text) {
toast.warning('Title and text are required');
return;
}
const params = {
groupId: D.groupId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
sendNotification: D.sendNotification,
imageId: null
};
if (gallerySelectDialog.value.selectedFileId) {
params.imageId = gallerySelectDialog.value.selectedFileId;
}
groupRequest.createGroupPost(params).then((args) => {
handleGroupPost(args);
toast.success('Group post created');
});
D.visible = false;
}
/**
*
*/
function clearImageGallerySelect() {
const D = gallerySelectDialog.value;
D.selectedFileId = '';
D.selectedImageUrl = '';
}
</script>