Skip to content

Commit

Permalink
可添加选择框的删除功能
Browse files Browse the repository at this point in the history
  • Loading branch information
xurenda committed Sep 22, 2024
1 parent 6155ff4 commit 0a5bb81
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 89 deletions.
89 changes: 62 additions & 27 deletions src/components/CanAddedSelect.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
<template>
<a-select v-bind="$attrs">
<a-select v-bind="$attrs" @dropdownVisibleChange="dropdownVisibleChange">
<template #dropdownRender="{ menuNode: menu }">
<v-nodes :vNodes="menu" />
<a-divider class="my-2" />
<a-form>
<a-form-item v-bind="validateInfos.newValue">
<a-input v-model:value="data.newValue" :placeholder="t('addItem')" @keyup.enter="addNewValue" />
</a-form-item>
</a-form>
<div class="mt-2 box-border flex gap-y-2 border-t border-color-border p-2">
<a-tag v-for="item in canDelValues" :key="item" closable @close="delValue(item)">
{{ item }}
</a-tag>
<div>
<a-input
v-if="inputVisible"
ref="inputRef"
v-model:value="newValue"
type="text"
size="small"
class="w-20"
@blur="inputVisible = false"
@keyup.enter="addNewValue"
/>
<a-tag v-else class="flex cursor-pointer items-center border-dashed py-[1px]" @click="showInput">
<i class="iconfont icon-add"></i>
<span>{{ t('addItem') }}</span>
</a-tag>
</div>
</div>
</template>
</a-select>
</template>

<script setup lang="ts">
import { Form } from 'ant-design-vue'
import { defineComponent, reactive } from 'vue'
import { computed, defineComponent, nextTick, ref, useAttrs, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const { newValueRules = [] } = defineProps<{
newValueRules?: any[]
const { cannotDel = new Set() } = defineProps<{
cannotDel?: Set<any>
}>()
const emit = defineEmits<{
(e: 'add', value: string): void
(e: 'del', value: any): void
}>()
const VNodes = defineComponent({
Expand All @@ -36,24 +50,45 @@ const VNodes = defineComponent({
return this.vNodes
},
})
const data = reactive({
newValue: '',
})
const rules = reactive({
newValue: newValueRules,
const { t } = useI18n()
const inputRef = useTemplateRef<HTMLInputElement>('inputRef')
const attrs = useAttrs()
const canDelValues = computed(() => {
const options = attrs.options as any[]
if (!options) {
return []
}
return options.filter((option: any) => !cannotDel?.has(option.value)).map((option: any) => option.value)
})
const inputVisible = ref(false)
const newValue = ref('')
const { validate, validateInfos } = Form.useForm(data, rules)
const showInput = (e: Event) => {
e.stopPropagation()
inputVisible.value = true
nextTick(() => {
inputRef.value?.focus()
inputRef.value?.select()
})
}
const addNewValue = () => {
validate()
.then(() => {
emit('add', data.newValue)
data.newValue = ''
})
.catch(() => {
// ignore
})
const val = newValue.value.trim()
if (!val) {
return
}
emit('add', val)
newValue.value = ''
inputVisible.value = false
}
const delValue = (value: any) => {
emit('del', value)
}
const dropdownVisibleChange = (open: boolean) => {
if (open) {
inputVisible.value = false
}
}
</script>
4 changes: 2 additions & 2 deletions src/components/FullScreenDrop.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<div
class="pointer-events-none box-border flex h-full w-full flex-col items-center justify-center border-2 border-dashed border-color-primary bg-white/80 dark:bg-black/80"
>
<div class="mb-2 text-2xl font-bold tracking-widest">松手即可导入</div>
<div class="mb-8 text-color-gray">拖入 JSON 文件,以导入设置、视频源</div>
<div class="mb-2 text-2xl font-bold tracking-widest">{{ t('drop.title') }}</div>
<div class="mb-8 text-color-gray">{{ t('drop.desc') }}</div>
<i class="iconfont icon-json-file text-[50px]"></i>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@
"verticalMirror": "Vertical Mirror",
"rotate": "Rotate"
},
"drop": {
"title": "Release to import",
"desc": "Drag into JSON file to import settings and video sources"
},
"colonSymbol": ": ",
"splittingSymbol": ", ",
"wordSplitSymbol": " "
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/jp.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@
"verticalMirror": "垂直ミラー",
"rotate": "回転させる"
},
"drop": {
"title": "そのままインポートしてください",
"desc": "JSON ファイルをドラッグして設定、ビデオ ソースをインポートします"
},
"colonSymbol": "",
"splittingSymbol": "",
"wordSplitSymbol": ""
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@
"verticalMirror": "垂直镜像",
"rotate": "旋转"
},
"drop": {
"title": "松手即可导入",
"desc": "拖入 JSON 文件,以导入设置、视频源"
},
"colonSymbol": "",
"splittingSymbol": "",
"wordSplitSymbol": ""
Expand Down
145 changes: 87 additions & 58 deletions src/pages/TheSettings/AppSettings/PlayerSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,101 +29,130 @@
<CanAddedSelect
v-model:value="playerSettingsStore.ratio"
:options="playerSettingsStore.ratios.map(i => ({ value: i }))"
:newValueRules="newRatioRules"
:cannot-del="cannotDelRatio"
@add="addNewRatio"
@del="delRatio"
/>
</a-form-item>
<a-form-item :label="t('settings.speed')">
<CanAddedSelect
:value="playerSettingsStore.speed"
:options="playerSettingsStore.speeds.map(i => ({ value: i }))"
mode="tags"
:newValueRules="newSpeedRules"
:cannot-del="cannotDelSpeed"
@add="addNewSpeed"
@change="changeSpeed"
@del="delSpeed"
/>
</a-form-item>
</a-form>
<contextHolder />
</template>

<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import usePlayerSettingsStore, { seekStepRange, volumeStepRange, playModes } from '@/stores/settings/player'
import usePlayerSettingsStore, {
seekStepRange,
volumeStepRange,
playModes,
defaultRatios,
defaultSpeeds,
} from '@/stores/settings/player'
import CanAddedSelect from '@/components/CanAddedSelect.vue'
import { computed } from 'vue'
import { message } from 'ant-design-vue'
const { formColWidth } = defineProps<{ formColWidth: [number, number] }>()
const { t } = useI18n()
const playerSettingsStore = usePlayerSettingsStore()
const [messageApi, contextHolder] = message.useMessage()
const cannotDelRatio = new Set(defaultRatios)
const cannotDelSpeed = new Set(defaultSpeeds)
const checkRatioNum = (num: number) => {
return !Number.isNaN(num) && Number.isInteger(num) && num > 0
}
const newRatioRules = computed(() => [
{
async validator(_: unknown, value: string) {
value = value.trim()
if (!value) {
throw new Error(t('validate.requiredField'))
}
const [w, h] = value.split(':').map(i => +i.trim())
if (!checkRatioNum(w) || !checkRatioNum(h)) {
throw new Error(`${t('validate.invalid')}${t('width')}:${t('height')}`)
}
const ratio = {
const ratioValidator = async (value: string) => {
value = value.trim()
if (!value) {
throw new Error(t('validate.requiredField'))
}
const [w, h] = value.split(':').map(i => +i.trim())
if (!checkRatioNum(w) || !checkRatioNum(h)) {
throw new Error(`${t('validate.invalid')}${t('width')}:${t('height')}`)
}
const ratio = {
text: `${w}:${h}`,
value: w / h,
}
const ratios = playerSettingsStore.ratiosWithValue
const sameRatio = ratios.find(i => i.value === ratio.value)
if (sameRatio) {
throw new Error(t('validate.duplicate', { value: sameRatio.text }))
}
}
const speedValidator = async (value: string) => {
value = value.trim()
if (!value) {
throw new Error(t('validate.requiredField'))
}
const num = +value
if (Number.isNaN(num) || num < 0.1 || num > 16) {
throw new Error(`${t('validate.invalid')}0.1~16`)
}
const speeds = playerSettingsStore.speeds
const sameSpeed = speeds.find(i => i === num)
if (sameSpeed) {
throw new Error(t('validate.duplicate', { value: sameSpeed }))
}
}
const addNewRatio = (newRatio: string) => {
ratioValidator(newRatio)
.then(() => {
const ratios = playerSettingsStore.ratiosWithValue
const [w, h] = newRatio.split(':').map(i => +i.trim())
const n = {
text: `${w}:${h}`,
value: w / h,
}
const ratios = playerSettingsStore.ratiosWithValue
const sameRatio = ratios.find(i => i.text === ratio.text)
if (sameRatio) {
throw new Error(t('validate.duplicate', { value: sameRatio.text }))
}
},
},
])
ratios.push(n)
playerSettingsStore.ratios = ratios.sort((a, b) => b.value - a.value).map(i => i.text)
playerSettingsStore.ratio = n.text
})
.catch(err => {
messageApi.error(err.message)
})
}
const newSpeedRules = computed(() => [
{
async validator(_: unknown, value: string) {
value = value.trim()
if (!value) {
throw new Error(t('validate.requiredField'))
}
const num = +value
if (Number.isNaN(num) || num < 0.1 || num > 16) {
throw new Error(`${t('validate.invalid')}0.1~16`)
}
const addNewSpeed = (newSpeed: string) => {
speedValidator(newSpeed)
.then(() => {
const speeds = playerSettingsStore.speeds
const sameSpeed = speeds.find(i => i === num)
if (sameSpeed) {
throw new Error(t('validate.duplicate', { value: sameSpeed }))
}
},
},
])
const speed = playerSettingsStore.speed
speeds.push(+newSpeed)
speed.push(+newSpeed)
playerSettingsStore.speeds = speeds.sort((a, b) => a - b)
playerSettingsStore.speed = speed.sort((a, b) => a - b)
})
.catch(err => {
messageApi.error(err.message)
})
}
const addNewRatio = (newRatio: string) => {
const ratios = playerSettingsStore.ratiosWithValue
const [w, h] = newRatio.split(':').map(i => +i.trim())
const n = {
text: `${w}:${h}`,
value: w / h,
const delRatio = (ratio: string) => {
playerSettingsStore.ratios = playerSettingsStore.ratios.filter(i => i !== ratio)
if (playerSettingsStore.ratio === ratio) {
playerSettingsStore.ratio = playerSettingsStore.ratios[0]
}
ratios.push(n)
playerSettingsStore.ratios = ratios.sort((a, b) => b.value - a.value).map(i => i.text)
playerSettingsStore.ratio = n.text
}
const addNewSpeed = (newSpeed: string) => {
const speeds = playerSettingsStore.speeds
const speed = playerSettingsStore.speed
speeds.push(+newSpeed)
speed.push(+newSpeed)
playerSettingsStore.speeds = speeds.sort((a, b) => a - b)
playerSettingsStore.speed = speed.sort((a, b) => a - b)
const delSpeed = (speed: number) => {
playerSettingsStore.speeds = playerSettingsStore.speeds.filter(i => i !== speed)
playerSettingsStore.speed = playerSettingsStore.speed.filter(i => i !== speed)
}
const changeSpeed = (newSpeed: number[]) => {
Expand Down
4 changes: 2 additions & 2 deletions src/stores/settings/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export const volumeStepRange: [number, number] = [1, 100]
export const playModes = ['pause', 'loop', 'next'] as const
export type PlayModes = (typeof playModes)[number]

const defaultRatios = ['16:9', '4:3', '1:1', '9:16']
const defaultSpeeds = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4]
export const defaultRatios = ['16:9', '4:3', '1:1', '9:16']
export const defaultSpeeds = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4]

const getRatioWithValue = (i: string) => {
const [w, h] = i.split(':')
Expand Down

0 comments on commit 0a5bb81

Please sign in to comment.