replace some el-select with Select component

This commit is contained in:
pa
2026-01-10 20:13:08 +09:00
committed by Natsumi
parent 6e0a3ffc7d
commit fbc3c8d55a
14 changed files with 566 additions and 325 deletions

View File

@@ -3,58 +3,73 @@
ref="setAvatarStylesDialog"
class="x-dialog"
:model-value="setAvatarStylesDialog.visible"
@close="closeSetAvatarStylesDialog"
:title="t('dialog.set_avatar_styles.header')"
width="400px"
append-to-body>
append-to-body
@close="closeSetAvatarStylesDialog">
<template v-if="setAvatarStylesDialog.visible">
<div>
<span>{{ t('dialog.set_avatar_styles.primary_style') }}</span>
<el-select
v-model="setAvatarStylesDialog.primaryStyle"
:placeholder="t('dialog.set_avatar_styles.select_style')"
size="small"
clearable
style="display: inline-block">
<el-option
v-for="(style, index) in setAvatarStylesDialog.availableAvatarStyles"
:key="index"
:label="style"
:value="style"></el-option>
</el-select>
<Select
:model-value="setAvatarStylesDialog.primaryStyle"
@update:modelValue="(v) => updateDialog({ primaryStyle: v === SELECT_CLEAR_VALUE ? '' : v })">
<SelectTrigger size="sm" style="display: inline-flex">
<SelectValue :placeholder="t('dialog.set_avatar_styles.select_style')" />
</SelectTrigger>
<SelectContent>
<SelectItem :value="SELECT_CLEAR_VALUE">{{ t('dialog.gallery_select.none') }}</SelectItem>
<SelectItem
v-for="(style, index) in setAvatarStylesDialog.availableAvatarStyles"
:key="index"
:value="style">
{{ style }}
</SelectItem>
</SelectContent>
</Select>
</div>
<br />
<div>
<span>{{ t('dialog.set_avatar_styles.secondary_style') }}</span>
<el-select
v-model="setAvatarStylesDialog.secondaryStyle"
:placeholder="t('dialog.set_avatar_styles.select_style')"
size="small"
clearable
style="display: inline-block">
<el-option
v-for="(style, index) in setAvatarStylesDialog.availableAvatarStyles"
:key="index"
:label="style"
:value="style"></el-option>
</el-select>
<Select
:model-value="setAvatarStylesDialog.secondaryStyle"
@update:modelValue="(v) => updateDialog({ secondaryStyle: v === SELECT_CLEAR_VALUE ? '' : v })">
<SelectTrigger size="sm" style="display: inline-flex">
<SelectValue :placeholder="t('dialog.set_avatar_styles.select_style')" />
</SelectTrigger>
<SelectContent>
<SelectItem :value="SELECT_CLEAR_VALUE">{{ t('dialog.gallery_select.none') }}</SelectItem>
<SelectItem
v-for="(style, index) in setAvatarStylesDialog.availableAvatarStyles"
:key="index"
:value="style">
{{ style }}
</SelectItem>
</SelectContent>
</Select>
</div>
<br />
<div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.author_tags') }}<br /></div>
<el-input
v-model="setAvatarStylesDialog.authorTags"
:model-value="setAvatarStylesDialog.authorTags"
type="textarea"
size="small"
show-word-limit
:autosize="{ minRows: 2, maxRows: 5 }"
placeholder=""
style="margin-top: 10px"></el-input>
style="margin-top: 10px"
@update:modelValue="(v) => updateDialog({ authorTags: v })" />
</template>
<template #footer>
<el-button @click="closeSetAvatarStylesDialog">{{ t('dialog.set_avatar_styles.cancel') }}</el-button>
<el-button type="primary" @click="saveSetAvatarStylesDialog">{{
t('dialog.set_avatar_styles.save')
}}</el-button>
<el-button type="primary" @click="saveSetAvatarStylesDialog">
{{ t('dialog.set_avatar_styles.save') }}
</el-button>
</template>
</el-dialog>
</template>
@@ -64,13 +79,11 @@
import { useI18n } from 'vue-i18n';
import { watch } from 'vue';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
import { arraysMatch } from '../../../shared/utils';
import { avatarRequest } from '../../../api';
import { useAvatarStore } from '../../../stores';
const { t } = useI18n();
const { applyAvatar } = useAvatarStore();
const props = defineProps({
setAvatarStylesDialog: {
type: Object,
@@ -80,6 +93,11 @@
const emit = defineEmits(['update:setAvatarStylesDialog']);
const { t } = useI18n();
const { applyAvatar } = useAvatarStore();
const SELECT_CLEAR_VALUE = '__clear__';
watch(
() => props.setAvatarStylesDialog.visible,
(newVal) => {
@@ -89,23 +107,34 @@
}
);
function updateDialog(patch) {
emit('update:setAvatarStylesDialog', {
...props.setAvatarStylesDialog,
...patch
});
}
async function getAvatarStyles() {
const ref = await avatarRequest.getAvailableAvatarStyles();
const styles = [];
const stylesMap = new Map();
for (const style of ref.json) {
styles.push(style.styleName);
stylesMap.set(style.styleName, style.id);
try {
const ref = await avatarRequest.getAvailableAvatarStyles();
const styles = [];
const stylesMap = new Map();
for (const style of ref.json) {
styles.push(style.styleName);
stylesMap.set(style.styleName, style.id);
}
updateDialog({
availableAvatarStyles: styles,
availableAvatarStylesMap: stylesMap
});
} catch (error) {
console.error('Error loading avatar styles:', error);
}
props.setAvatarStylesDialog.availableAvatarStyles = styles;
props.setAvatarStylesDialog.availableAvatarStylesMap = stylesMap;
}
function closeSetAvatarStylesDialog() {
emit('update:setAvatarStylesDialog', {
...props.setAvatarStylesDialog,
visible: false
});
updateDialog({ visible: false });
}
function saveSetAvatarStylesDialog() {
@@ -114,18 +143,19 @@
const secondaryStyleId =
props.setAvatarStylesDialog.availableAvatarStylesMap.get(props.setAvatarStylesDialog.secondaryStyle) || '';
let tags = [];
const tags = [];
for (const tag of props.setAvatarStylesDialog.initialTags) {
if (!tag.startsWith('author_tag_')) {
tags.push(tag);
}
}
const authorTagsArray = props.setAvatarStylesDialog.authorTags.split(',');
for (const tag of authorTagsArray) {
if (!tag.trim()) {
continue;
}
let tagName = `author_tag_${tag}`;
const tagName = `author_tag_${tag}`;
if (!tags.includes(tagName)) {
tags.push(tagName);
}
@@ -146,6 +176,7 @@
secondaryStyle: secondaryStyleId,
tags
};
avatarRequest
.saveAvatar(params)
.then((args) => {

View File

@@ -623,25 +623,19 @@
<br />
<div style="display: flex; justify-content: space-between; align-items: center">
<div>
<el-select
v-model="selectedAuditLogTypes"
multiple
collapse-tags
:placeholder="t('dialog.group_member_moderation.filter_type')"
style="margin: 10px 0; width: 250px">
<el-option-group :label="t('dialog.group_member_moderation.select_type')">
<el-option
<Select v-model="selectedAuditLogTypes" multiple>
<SelectTrigger style="margin: 10px 0; width: 250px">
<SelectValue :placeholder="t('dialog.group_member_moderation.filter_type')" />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="type in groupMemberModeration.auditLogTypes"
:key="type"
class="x-friend-item"
:label="getAuditLogTypeName(type)"
:value="type">
<div class="detail">
<span class="name" v-text="getAuditLogTypeName(type)"></span>
</div>
</el-option>
</el-option-group>
</el-select>
{{ getAuditLogTypeName(type) }}
</SelectItem>
</SelectContent>
</Select>
<el-input
v-model="groupLogsModerationTable.filters[0].value"
:placeholder="t('dialog.group_member_moderation.search_placeholder')"
@@ -781,27 +775,16 @@
<br />
<span class="name">{{ t('dialog.group_member_moderation.selected_roles') }}</span>
<br />
<el-select
v-model="selectedRoles"
clearable
multiple
:placeholder="t('dialog.group_member_moderation.choose_roles_placeholder')"
filterable
style="margin-top: 5px">
<el-option-group :label="t('dialog.group_member_moderation.roles')">
<el-option
v-for="role in groupMemberModeration.groupRef.roles"
:key="role.id"
class="x-friend-item"
:label="role.name"
:value="role.id"
style="height: auto">
<div class="detail">
<span class="name" v-text="role.name"></span>
</div>
</el-option>
</el-option-group>
</el-select>
<Select v-model="selectedRoles" multiple>
<SelectTrigger style="margin-top: 5px">
<SelectValue :placeholder="t('dialog.group_member_moderation.choose_roles_placeholder')" />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="role in groupMemberModeration.groupRef.roles" :key="role.id" :value="role.id">
{{ role.name }}
</SelectItem>
</SelectContent>
</Select>
<br />
<br />
<span class="name">{{ t('dialog.group_member_moderation.actions') }}</span>
@@ -881,6 +864,7 @@
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
import { debounce, formatDateFilter, hasGroupPermission, userImage, userImageFull } from '../../../shared/utils';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../ui/dropdown-menu';
import { useAppearanceSettingsStore, useGalleryStore, useGroupStore, useUserStore } from '../../../stores';

View File

@@ -17,20 +17,33 @@
<br />
<span>{{ t('dialog.vrcx_updater.ready_for_update') }}</span>
</div>
<el-select
v-model="branch"
style="display: inline-block; width: 150px; margin-right: 15px"
@change="loadBranchVersions">
<el-option v-for="b in branches" :key="b.name" :label="b.name" :value="b.name"> </el-option>
</el-select>
<el-select v-model="VRCXUpdateDialog.release" style="display: inline-block; width: 150px">
<el-option
v-for="item in VRCXUpdateDialog.releases"
:key="item.name"
:label="item.tag_name"
:value="item.name">
</el-option>
</el-select>
<Select
:model-value="branch"
@update:modelValue="
(v) => {
branch = v;
loadBranchVersions();
}
">
<SelectTrigger style="display: inline-flex; width: 150px; margin-right: 15px">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="b in branches" :key="b.name" :value="b.name">{{ b.name }}</SelectItem>
</SelectContent>
</Select>
<Select
:model-value="VRCXUpdateDialog.release"
@update:modelValue="(v) => (VRCXUpdateDialog.release = v)">
<SelectTrigger style="display: inline-flex; width: 150px">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="item in VRCXUpdateDialog.releases" :key="item.name" :value="item.name">
{{ item.tag_name }}
</SelectItem>
</SelectContent>
</Select>
<div
v-if="!VRCXUpdateDialog.updatePending && VRCXUpdateDialog.release === appVersion"
style="margin-top: 15px">
@@ -62,6 +75,7 @@
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
import { branches } from '../../shared/constants';
import { getNextDialogIndex } from '../../shared/utils/base/ui';
import { useVRCXUpdaterStore } from '../../stores';

View File

@@ -0,0 +1,47 @@
<script setup>
import { TagsInputRoot, useForwardPropsEmits } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
modelValue: { type: [Array, null], required: false },
defaultValue: { type: Array, required: false },
addOnPaste: { type: Boolean, required: false },
addOnTab: { type: Boolean, required: false },
addOnBlur: { type: Boolean, required: false },
duplicate: { type: Boolean, required: false },
disabled: { type: Boolean, required: false },
delimiter: { type: null, required: false },
dir: { type: String, required: false },
max: { type: Number, required: false },
id: { type: String, required: false },
convertValue: { type: Function, required: false },
displayValue: { type: Function, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
name: { type: String, required: false },
required: { type: Boolean, required: false },
class: { type: null, required: false }
});
const emits = defineEmits(['update:modelValue', 'invalid', 'addTag', 'removeTag']);
const delegatedProps = reactiveOmit(props, 'class');
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<TagsInputRoot
v-slot="slotProps"
v-bind="forwarded"
:class="
cn(
'flex flex-wrap gap-2 items-center rounded-md border border-input bg-background px-2 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none',
'focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
props.class
)
">
<slot v-bind="slotProps" />
</TagsInputRoot>
</template>

View File

@@ -0,0 +1,24 @@
<script setup>
import { TagsInputInput, useForwardProps } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
placeholder: { type: String, required: false },
autoFocus: { type: Boolean, required: false },
maxLength: { type: Number, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<TagsInputInput
v-bind="forwardedProps"
:class="cn('text-sm min-h-5 focus:outline-none flex-1 bg-transparent px-1', props.class)" />
</template>

View File

@@ -0,0 +1,30 @@
<script setup>
import { TagsInputItem, useForwardProps } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
value: { type: null, required: true },
disabled: { type: Boolean, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<TagsInputItem
v-bind="forwardedProps"
:class="
cn(
'flex h-5 items-center rounded-md bg-secondary data-[state=active]:ring-ring data-[state=active]:ring-2 data-[state=active]:ring-offset-2 ring-offset-background',
props.class
)
">
<slot />
</TagsInputItem>
</template>

View File

@@ -0,0 +1,24 @@
<script setup>
import { TagsInputItemDelete, useForwardProps } from 'reka-ui';
import { X } from 'lucide-vue-next';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<TagsInputItemDelete v-bind="forwardedProps" :class="cn('flex rounded bg-transparent mr-1', props.class)">
<slot>
<X class="w-4 h-4" />
</slot>
</TagsInputItemDelete>
</template>

View File

@@ -0,0 +1,19 @@
<script setup>
import { TagsInputItemText, useForwardProps } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<TagsInputItemText v-bind="forwardedProps" :class="cn('py-0.5 px-2 text-sm rounded bg-transparent', props.class)" />
</template>

View File

@@ -0,0 +1,5 @@
export { default as TagsInput } from './TagsInput.vue';
export { default as TagsInputInput } from './TagsInputInput.vue';
export { default as TagsInputItem } from './TagsInputItem.vue';
export { default as TagsInputItemDelete } from './TagsInputItemDelete.vue';
export { default as TagsInputItemText } from './TagsInputItemText.vue';