同步设置
This commit is contained in:
1 parent
832261739f
commit
92aaf6fbb8
28 files changed
+705
-227
No files matched your search
@@ -74,16 +74,7 @@ function fileIcon(item: FileItem): string {
|
||||
<p class="truncate text-xs text-muted">
|
||||
{{ uploadedLabel }} · {{ formatSize(item.size) }}
|
||||
</p>
|
||||
<div
|
||||
v-if="item.tags?.length"
|
||||
class="flex flex-wrap gap-1"
|
||||
>
|
||||
<TagBadge
|
||||
v-for="tag in item.tags"
|
||||
:key="tag.id"
|
||||
:tag="tag"
|
||||
/>
|
||||
</div>
|
||||
<TagBadgeList :tags="item.tags" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-around border-t border-default px-2 py-2">
|
||||
|
||||
@@ -633,20 +633,11 @@ defineExpose({ refresh })
|
||||
{{ uploadedLabel(item) }}
|
||||
</td>
|
||||
<td class="hidden px-3 py-2 md:table-cell">
|
||||
<div
|
||||
v-if="item.tags?.length"
|
||||
class="flex flex-wrap gap-1"
|
||||
>
|
||||
<TagBadge
|
||||
v-for="tag in item.tags"
|
||||
:key="tag.id"
|
||||
:tag="tag"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
v-else
|
||||
class="text-muted"
|
||||
>{{ t('tags.unlabeled') }}</span>
|
||||
<TagBadgeList
|
||||
:tags="item.tags"
|
||||
compact
|
||||
:empty-label="t('tags.unlabeled')"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
|
||||
@@ -32,24 +32,28 @@ onMounted(() => {
|
||||
const text = computed(() => {
|
||||
const allowedVideos = uploadPolicy.value?.allowedVideoExtensions ?? []
|
||||
const forbiddenFiles = uploadPolicy.value?.forbiddenFileExtensions ?? []
|
||||
const videoMaxFileSizeMb = uploadPolicy.value?.videoMaxFileSizeMb ?? 500
|
||||
const fileMaxFileSizeMb = uploadPolicy.value?.fileMaxFileSizeMb ?? 100
|
||||
return props.kind === 'video'
|
||||
? {
|
||||
uploadTitle: t('library.video.uploadTitle'),
|
||||
uploadHint: t('library.video.uploadHint'),
|
||||
uploadFormats: allowedVideos.length
|
||||
? t('library.video.uploadFormatsAllowed', {
|
||||
formats: allowedVideos.map(item => item.toUpperCase()).join(' / ')
|
||||
formats: allowedVideos.map(item => item.toUpperCase()).join(' / '),
|
||||
size: videoMaxFileSizeMb
|
||||
})
|
||||
: t('library.video.uploadFormats')
|
||||
: t('library.video.uploadFormats', { size: videoMaxFileSizeMb })
|
||||
}
|
||||
: {
|
||||
uploadTitle: t('library.file.uploadTitle'),
|
||||
uploadHint: t('library.file.uploadHint'),
|
||||
uploadFormats: forbiddenFiles.length
|
||||
? t('library.file.uploadFormatsBlocked', {
|
||||
formats: forbiddenFiles.map(item => item.toUpperCase()).join(' / ')
|
||||
formats: forbiddenFiles.map(item => item.toUpperCase()).join(' / '),
|
||||
size: fileMaxFileSizeMb
|
||||
})
|
||||
: t('library.file.uploadFormatsUnrestricted')
|
||||
: t('library.file.uploadFormatsUnrestricted', { size: fileMaxFileSizeMb })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ const emit = defineEmits<{
|
||||
'created': [tag: ImageTag]
|
||||
}>()
|
||||
|
||||
const COMMON_LIMIT = 12
|
||||
const COMMON_LIMIT = 10
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
const { createTag } = useTags()
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import type { ImageTag } from '~/types/image'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
tags?: ImageTag[]
|
||||
max?: number
|
||||
compact?: boolean
|
||||
emptyLabel?: string
|
||||
}>(), {
|
||||
tags: () => [],
|
||||
max: 3,
|
||||
compact: false,
|
||||
emptyLabel: ''
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'tag-click': [tagId: number]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const visibleTags = computed(() => props.tags.slice(0, props.max))
|
||||
const hiddenTags = computed(() => props.tags.slice(props.max))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="tags.length"
|
||||
class="flex flex-wrap items-center"
|
||||
:class="compact ? 'gap-1' : 'gap-1.5'"
|
||||
>
|
||||
<TagBadge
|
||||
v-for="tag in visibleTags"
|
||||
:key="tag.id"
|
||||
:tag="tag"
|
||||
@click="emit('tag-click', tag.id)"
|
||||
/>
|
||||
<UPopover
|
||||
v-if="hiddenTags.length"
|
||||
:content="{ side: 'bottom', align: 'start' }"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex rounded-md border border-default text-muted transition-colors hover:border-primary/40 hover:text-primary"
|
||||
:class="compact ? 'px-1.5 py-0.5 text-[11px]' : 'px-2 py-0.5 text-xs'"
|
||||
:aria-label="t('tags.moreTags', { n: hiddenTags.length })"
|
||||
>
|
||||
+{{ hiddenTags.length }}
|
||||
</button>
|
||||
<template #content>
|
||||
<div
|
||||
class="flex max-w-56 flex-wrap gap-1.5 p-2"
|
||||
>
|
||||
<TagBadge
|
||||
v-for="tag in hiddenTags"
|
||||
:key="tag.id"
|
||||
:tag="tag"
|
||||
@click="emit('tag-click', tag.id)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
<span
|
||||
v-else-if="emptyLabel"
|
||||
class="text-muted"
|
||||
>
|
||||
{{ emptyLabel }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -43,7 +43,7 @@ function onBack() {
|
||||
<template>
|
||||
<div
|
||||
class="upload-card-flip"
|
||||
:class="{ 'is-flipped': flipped }"
|
||||
:class="[`upload-card-flip--${kind}`, { 'is-flipped': flipped }]"
|
||||
>
|
||||
<div class="upload-card-flip-inner">
|
||||
<div class="upload-card-face upload-card-face--front">
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { UploadMediaKind } from '~/composables/useUploadPreferences'
|
||||
import {
|
||||
normalizeUploadTagNames,
|
||||
type UploadMediaKind
|
||||
} from '~/composables/useUploadPreferences'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
/** 每个上传页各自一份偏好 */
|
||||
@@ -17,20 +20,36 @@ const {
|
||||
autoCopyMarkdown,
|
||||
copyFormat,
|
||||
clientWebpQuality,
|
||||
lastTagIds,
|
||||
uploadTagNames,
|
||||
transcodeToHls,
|
||||
loadPreferences
|
||||
} = useUploadPreferences(props.kind)
|
||||
const { items: tagItems, fetchTags } = useTags()
|
||||
const uploadTagDraft = ref('')
|
||||
|
||||
/** 视频床 / 文件床没有图片压缩相关选项 */
|
||||
const isImageKind = computed(() => props.kind === 'image')
|
||||
const isVideoKind = computed(() => props.kind === 'video')
|
||||
const gridClass = computed(() => {
|
||||
if (isImageKind.value) return 'sm:grid-cols-3 sm:gap-6'
|
||||
if (isVideoKind.value) return 'sm:grid-cols-2 sm:gap-6'
|
||||
return 'mx-auto w-full max-w-md'
|
||||
const processingGridClass = computed(() => {
|
||||
if (isImageKind.value || isVideoKind.value) return 'md:col-start-1 md:row-start-1'
|
||||
return ''
|
||||
})
|
||||
const qualityGridClass = computed(() => {
|
||||
if (isImageKind.value) return 'md:col-start-1 md:row-start-2'
|
||||
return ''
|
||||
})
|
||||
const convenienceGridClass = computed(() => {
|
||||
if (isImageKind.value) return 'md:col-start-2 md:row-start-1'
|
||||
if (isVideoKind.value) return 'md:col-start-1 md:row-start-2'
|
||||
return ''
|
||||
})
|
||||
const tagsGridClass = computed(() => {
|
||||
if (isImageKind.value) return 'md:col-start-2 md:row-start-2'
|
||||
if (isVideoKind.value) return 'md:col-start-2 md:row-start-1 md:row-span-2'
|
||||
return ''
|
||||
})
|
||||
const tagsLayoutClass = computed(() => [
|
||||
tagsGridClass.value
|
||||
])
|
||||
|
||||
const { t } = useI18n()
|
||||
const {
|
||||
@@ -74,11 +93,39 @@ function onClientQualityInput(event: Event) {
|
||||
clientWebpQuality.value = Number.isFinite(value) ? value : 80
|
||||
}
|
||||
|
||||
function commitUploadTags() {
|
||||
const names = normalizeUploadTagNames(uploadTagDraft.value)
|
||||
if (!names.length) return
|
||||
uploadTagNames.value = normalizeUploadTagNames([
|
||||
...uploadTagNames.value,
|
||||
...names
|
||||
])
|
||||
uploadTagDraft.value = ''
|
||||
}
|
||||
|
||||
function onUploadTagKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' || event.key === ',' || event.key === ',') {
|
||||
event.preventDefault()
|
||||
commitUploadTags()
|
||||
return
|
||||
}
|
||||
if (
|
||||
event.key === 'Backspace'
|
||||
&& !uploadTagDraft.value
|
||||
&& uploadTagNames.value.length
|
||||
) {
|
||||
uploadTagNames.value = uploadTagNames.value.slice(0, -1)
|
||||
}
|
||||
}
|
||||
|
||||
function removeUploadTag(name: string) {
|
||||
uploadTagNames.value = uploadTagNames.value.filter(item => item !== name)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadPreferences()
|
||||
void loadUserSettings()
|
||||
void loadTranscodeSettings()
|
||||
void fetchTags()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -113,12 +160,12 @@ onMounted(() => {
|
||||
|
||||
<div class="flex flex-1 flex-col justify-start overflow-y-auto p-5 sm:p-6">
|
||||
<div
|
||||
class="grid gap-5"
|
||||
:class="gridClass"
|
||||
class="grid gap-5 md:grid-cols-2 md:gap-6"
|
||||
>
|
||||
<div
|
||||
v-if="isImageKind"
|
||||
class="space-y-4"
|
||||
class="space-y-4 rounded-xl border border-default/70 bg-muted/10 p-4"
|
||||
:class="processingGridClass"
|
||||
>
|
||||
<h3 class="text-sm font-semibold">
|
||||
{{ t('preferences.processing') }}
|
||||
@@ -179,7 +226,8 @@ onMounted(() => {
|
||||
|
||||
<div
|
||||
v-if="isVideoKind"
|
||||
class="space-y-4"
|
||||
class="space-y-4 rounded-xl border border-default/70 bg-muted/10 p-4"
|
||||
:class="processingGridClass"
|
||||
>
|
||||
<h3 class="text-sm font-semibold">
|
||||
{{ t('preferences.videoProcessing') }}
|
||||
@@ -217,7 +265,8 @@ onMounted(() => {
|
||||
|
||||
<div
|
||||
v-if="isImageKind"
|
||||
class="space-y-4"
|
||||
class="space-y-4 rounded-xl border border-default/70 bg-muted/10 p-4"
|
||||
:class="qualityGridClass"
|
||||
>
|
||||
<h3 class="text-sm font-semibold">
|
||||
{{ t('preferences.webpQuality') }}
|
||||
@@ -239,7 +288,10 @@ onMounted(() => {
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
class="space-y-4 rounded-xl border border-default/70 bg-muted/10 p-4"
|
||||
:class="convenienceGridClass"
|
||||
>
|
||||
<h3 class="text-sm font-semibold">
|
||||
{{ t('preferences.convenience') }}
|
||||
</h3>
|
||||
@@ -271,19 +323,57 @@ onMounted(() => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 space-y-3 border-t border-default pt-5">
|
||||
<h3 class="text-sm font-semibold">
|
||||
{{ t('preferences.uploadTags') }}
|
||||
</h3>
|
||||
<p class="text-xs leading-relaxed text-muted">
|
||||
{{ t('preferences.uploadTagsHint') }}
|
||||
</p>
|
||||
<TagAssignPanel
|
||||
v-model="lastTagIds"
|
||||
:tags="tagItems"
|
||||
/>
|
||||
<div
|
||||
class="space-y-3 rounded-xl border border-default/70 bg-muted/10 p-4"
|
||||
:class="tagsLayoutClass"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<h3 class="text-sm font-semibold">
|
||||
{{ t('preferences.uploadTags') }}
|
||||
</h3>
|
||||
<p class="text-xs leading-relaxed text-muted">
|
||||
{{ t('preferences.uploadTagsHint') }}
|
||||
</p>
|
||||
</div>
|
||||
<UInput
|
||||
v-model="uploadTagDraft"
|
||||
size="md"
|
||||
icon="i-lucide-tags"
|
||||
maxlength="32"
|
||||
:placeholder="t('preferences.uploadTagsPlaceholder')"
|
||||
@keydown="onUploadTagKeydown"
|
||||
@blur="commitUploadTags"
|
||||
/>
|
||||
<div
|
||||
v-if="uploadTagNames.length"
|
||||
class="space-y-2 pt-1"
|
||||
>
|
||||
<p class="text-xs font-medium text-muted">
|
||||
{{ t('preferences.uploadTagsPending', { n: uploadTagNames.length }) }}
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2.5">
|
||||
<span
|
||||
v-for="name in uploadTagNames"
|
||||
:key="name"
|
||||
class="relative inline-flex max-w-full items-center rounded-full border border-primary/35 bg-primary/10 px-3 py-1 text-xs font-medium text-primary"
|
||||
>
|
||||
<span class="truncate">{{ name }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute -top-1.5 -right-1.5 inline-flex size-4 items-center justify-center rounded-full border border-primary/40 bg-elevated text-primary shadow-sm transition-colors hover:bg-primary hover:text-inverted"
|
||||
:aria-label="t('preferences.uploadTagsRemove', { name })"
|
||||
@click="removeUploadTag(name)"
|
||||
>
|
||||
<UIcon
|
||||
name="i-lucide-x"
|
||||
class="size-2.5"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -109,16 +109,7 @@ const uploadedLabel = computed(() => {
|
||||
<p class="truncate text-xs text-muted">
|
||||
{{ uploadedLabel }} · {{ formatFileSize(item.size) }}
|
||||
</p>
|
||||
<div
|
||||
v-if="item.tags?.length"
|
||||
class="flex flex-wrap gap-1"
|
||||
>
|
||||
<TagBadge
|
||||
v-for="tag in item.tags"
|
||||
:key="tag.id"
|
||||
:tag="tag"
|
||||
/>
|
||||
</div>
|
||||
<TagBadgeList :tags="item.tags" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-around border-t border-default px-2 py-2">
|
||||
|
||||
@@ -659,20 +659,11 @@ defineExpose({ refresh })
|
||||
{{ uploadedLabel(item) }}
|
||||
</td>
|
||||
<td class="hidden px-3 py-2 md:table-cell">
|
||||
<div
|
||||
v-if="item.tags?.length"
|
||||
class="flex flex-wrap gap-1"
|
||||
>
|
||||
<TagBadge
|
||||
v-for="tag in item.tags"
|
||||
:key="tag.id"
|
||||
:tag="tag"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
v-else
|
||||
class="text-muted"
|
||||
>{{ t('tags.unlabeled') }}</span>
|
||||
<TagBadgeList
|
||||
:tags="item.tags"
|
||||
compact
|
||||
:empty-label="t('tags.unlabeled')"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
|
||||
@@ -5,7 +5,9 @@ type VideoExtension = 'mp4' | 'm4v' | 'webm' | 'mov' | 'mkv' | 'avi' | 'flv' | '
|
||||
|
||||
const props = defineProps<{
|
||||
part: 'upload-preference' | 'image-processing' | 'login-rate'
|
||||
uploadMaxFileSizeMbSource?: PolicySource
|
||||
uploadImageMaxFileSizeMbSource?: PolicySource
|
||||
uploadVideoMaxFileSizeMbSource?: PolicySource
|
||||
uploadFileMaxFileSizeMbSource?: PolicySource
|
||||
allowedMimeTypesSource?: PolicySource
|
||||
allowedVideoExtensionsSource?: PolicySource
|
||||
forbiddenFileExtensionsSource?: PolicySource
|
||||
@@ -20,7 +22,9 @@ const props = defineProps<{
|
||||
forbiddenFileExtensionOptions?: ReadonlyArray<{ extension: string, label: string }>
|
||||
}>()
|
||||
|
||||
const uploadMaxFileSizeMbDraft = defineModel<number>('uploadMaxFileSizeMb')
|
||||
const uploadImageMaxFileSizeMbDraft = defineModel<number>('uploadImageMaxFileSizeMb')
|
||||
const uploadVideoMaxFileSizeMbDraft = defineModel<number>('uploadVideoMaxFileSizeMb')
|
||||
const uploadFileMaxFileSizeMbDraft = defineModel<number>('uploadFileMaxFileSizeMb')
|
||||
const allowedMimeTypesDraft = defineModel<MimeType[]>('allowedMimeTypes')
|
||||
const allowedVideoExtensionsDraft = defineModel<VideoExtension[]>('allowedVideoExtensions')
|
||||
const forbiddenFileExtensionsDraft = defineModel<string[]>('forbiddenFileExtensions')
|
||||
@@ -125,67 +129,109 @@ function addCustomForbiddenExtension() {
|
||||
customForbiddenExtension.value = ''
|
||||
}
|
||||
|
||||
function clampUploadMaxFileSizeMb(value: unknown): number {
|
||||
const num = Number(value)
|
||||
if (!Number.isFinite(num)) return uploadMaxFileSizeMbDraft.value ?? 10
|
||||
return Math.min(100, Math.max(1, Math.round(num)))
|
||||
type UploadSizeKind = 'image' | 'video' | 'file'
|
||||
|
||||
const uploadSizeFields = computed(() => [
|
||||
{
|
||||
kind: 'image' as const,
|
||||
title: t('settings.uploadImageMaxFileSize'),
|
||||
hint: t('settings.uploadImageMaxFileSizeHint'),
|
||||
source: props.uploadImageMaxFileSizeMbSource,
|
||||
value: uploadImageMaxFileSizeMbDraft.value ?? 10,
|
||||
max: 100
|
||||
},
|
||||
{
|
||||
kind: 'video' as const,
|
||||
title: t('settings.uploadVideoMaxFileSize'),
|
||||
hint: t('settings.uploadVideoMaxFileSizeHint'),
|
||||
source: props.uploadVideoMaxFileSizeMbSource,
|
||||
value: uploadVideoMaxFileSizeMbDraft.value ?? 500,
|
||||
max: 2048
|
||||
},
|
||||
{
|
||||
kind: 'file' as const,
|
||||
title: t('settings.uploadFileMaxFileSize'),
|
||||
hint: t('settings.uploadFileMaxFileSizeHint'),
|
||||
source: props.uploadFileMaxFileSizeMbSource,
|
||||
value: uploadFileMaxFileSizeMbDraft.value ?? 100,
|
||||
max: 1024
|
||||
}
|
||||
])
|
||||
|
||||
function uploadSizeModel(kind: UploadSizeKind) {
|
||||
if (kind === 'video') return uploadVideoMaxFileSizeMbDraft
|
||||
if (kind === 'file') return uploadFileMaxFileSizeMbDraft
|
||||
return uploadImageMaxFileSizeMbDraft
|
||||
}
|
||||
|
||||
function onUploadMaxFileSizeInput(event: Event) {
|
||||
uploadMaxFileSizeMbDraft.value = clampUploadMaxFileSizeMb(
|
||||
function clampUploadMaxFileSizeMb(kind: UploadSizeKind, value: unknown): number {
|
||||
const num = Number(value)
|
||||
const model = uploadSizeModel(kind)
|
||||
const max = kind === 'video' ? 2048 : kind === 'file' ? 1024 : 100
|
||||
if (!Number.isFinite(num)) return model.value ?? (kind === 'video' ? 500 : kind === 'file' ? 100 : 10)
|
||||
return Math.min(max, Math.max(0, Math.round(num)))
|
||||
}
|
||||
|
||||
function onUploadMaxFileSizeInput(kind: UploadSizeKind, event: Event) {
|
||||
uploadSizeModel(kind).value = clampUploadMaxFileSizeMb(kind,
|
||||
(event.target as HTMLInputElement).value
|
||||
)
|
||||
}
|
||||
|
||||
function onUploadMaxFileSizeNumberInput(value: string | number) {
|
||||
uploadMaxFileSizeMbDraft.value = clampUploadMaxFileSizeMb(value)
|
||||
function onUploadMaxFileSizeNumberInput(kind: UploadSizeKind, value: string | number) {
|
||||
uploadSizeModel(kind).value = clampUploadMaxFileSizeMb(kind, value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="part === 'upload-preference'">
|
||||
<div class="py-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<p class="text-sm font-medium text-highlighted">
|
||||
{{ t('settings.uploadMaxFileSize') }}
|
||||
</p>
|
||||
<UBadge
|
||||
v-if="uploadMaxFileSizeMbSource"
|
||||
:color="sourceBadge(uploadMaxFileSizeMbSource).color"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
>
|
||||
{{ sourceBadge(uploadMaxFileSizeMbSource).label }}
|
||||
</UBadge>
|
||||
</div>
|
||||
<p class="mt-1 text-xs leading-relaxed text-muted">
|
||||
{{ t('settings.uploadMaxFileSizeHint') }}
|
||||
</p>
|
||||
<div class="space-y-5 py-4">
|
||||
<div
|
||||
class="mt-3 flex items-center gap-3"
|
||||
:class="uploadMaxFileSizeMbSource === 'env' ? 'pointer-events-none opacity-60' : ''"
|
||||
v-for="field in uploadSizeFields"
|
||||
:key="field.kind"
|
||||
>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
:value="uploadMaxFileSizeMbDraft"
|
||||
class="h-1.5 min-w-0 flex-1 cursor-pointer appearance-none rounded-full bg-muted accent-primary"
|
||||
:disabled="uploadMaxFileSizeMbSource === 'env'"
|
||||
@input="onUploadMaxFileSizeInput"
|
||||
>
|
||||
<div class="flex shrink-0 items-center gap-1.5">
|
||||
<UInput
|
||||
:model-value="uploadMaxFileSizeMbDraft"
|
||||
type="number"
|
||||
:min="1"
|
||||
:max="100"
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<p class="text-sm font-medium text-highlighted">
|
||||
{{ field.title }}
|
||||
</p>
|
||||
<UBadge
|
||||
v-if="field.source"
|
||||
:color="sourceBadge(field.source).color"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
class="w-14 tabular-nums"
|
||||
:disabled="uploadMaxFileSizeMbSource === 'env'"
|
||||
@update:model-value="onUploadMaxFileSizeNumberInput"
|
||||
/>
|
||||
<span class="text-xs text-muted">MB</span>
|
||||
>
|
||||
{{ sourceBadge(field.source).label }}
|
||||
</UBadge>
|
||||
</div>
|
||||
<p class="mt-1 text-xs leading-relaxed text-muted">
|
||||
{{ field.hint }}
|
||||
</p>
|
||||
<div
|
||||
class="mt-3 flex items-center gap-3"
|
||||
:class="field.source === 'env' ? 'pointer-events-none opacity-60' : ''"
|
||||
>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
:max="field.max"
|
||||
:value="field.value"
|
||||
class="h-1.5 min-w-0 flex-1 cursor-pointer appearance-none rounded-full bg-muted accent-primary"
|
||||
:disabled="field.source === 'env'"
|
||||
@input="onUploadMaxFileSizeInput(field.kind, $event)"
|
||||
>
|
||||
<div class="flex shrink-0 items-center gap-1.5">
|
||||
<UInput
|
||||
:model-value="field.value"
|
||||
type="number"
|
||||
:min="0"
|
||||
:max="field.max"
|
||||
size="xs"
|
||||
class="w-16 tabular-nums"
|
||||
:disabled="field.source === 'env'"
|
||||
@update:model-value="onUploadMaxFileSizeNumberInput(field.kind, $event)"
|
||||
/>
|
||||
<span class="text-xs text-muted">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user