Skip to content

Commit

Permalink
feat: add song editor and validator
Browse files Browse the repository at this point in the history
  • Loading branch information
eepson123tw committed Oct 27, 2024
1 parent dba34fc commit dda8da6
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 49 deletions.
52 changes: 7 additions & 45 deletions app/components/editors/SongEditor.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import type { MaruSongDataParsed } from '@marure/schema'
import { useValidation } from '@/composables/validator'
import { parseLrc, secondsToTimestamp, serializeToLrc } from '@marure/parser'
import { inferSongInfoFromVideoTitle } from '@marure/utils'
import { useDebouncedRefHistory } from '@vueuse/core'
Expand Down Expand Up @@ -37,6 +38,8 @@ const {
const router = useRouter()
const route = useRoute()
const { validate, errors } = useValidation(stateRef.value)
const dirty = ref(false)
watch(
Expand Down Expand Up @@ -64,6 +67,9 @@ async function save() {
alert(t('youtube.requireId'))
return
}
if (!validate()) {
return
}
syncLrc()
const copy = { ...toRaw(stateRef.value), lyrics: undefined }
await saveSongsToLocal([copy])
Expand Down Expand Up @@ -228,34 +234,6 @@ function toggleTranslations(lang: string) {
}
}
const artistsString = computed({
get: () => (stateRef.value.artists || []).join(', '),
set: (value: string) => {
stateRef.value.artists = value.split(',').map(v => v.trim())
},
})
const tagsString = computed({
get: () => (stateRef.value.tags || []).join(', '),
set: (value: string) => {
stateRef.value.tags = value.split(',').map(v => v.trim())
},
})
const offsetString = computed({
get: () => String(stateRef.value.offset || ''),
set: (value: string) => {
stateRef.value.offset = Number(value)
},
})
const notesString = computed({
get: () => (stateRef.value.notes || []).join('\n'),
set: (value: string) => {
stateRef.value.notes = value.split('\n')
},
})
const { copied, copy } = useClipboard({ read: false })
const currentTimestamp = computed(() => secondsToTimestamp(controls.current.value - (stateRef.value.offset ?? 0)))
Expand Down Expand Up @@ -334,23 +312,7 @@ onMounted(() => {
</DraggableWindow>
<div mxa max-w-300 flex="~ col gap-3">
<BasicNav />
<div flex="~ col gap-2" max-w-150>
<h1 my4 text-2xl>
{{ $t("lyrics.editLyrics") }}
</h1>
<TextInput :model-value="stateRef.youtube" label="YouTube ID" input-class="font-mono" disabled />
<TextInput v-model="stateRef.title" :label="$t('song.title')" />
<TextInput v-model="artistsString" :label="$t('song.artist')" />
<TextInput v-model="tagsString" :label="$t('song.tags')" />
<TextInput v-model="offsetString" :label="$t('song.offset')" />
<TextInput
v-model="notesString"
:label="$t('common.notes')"
type="textarea"
input-class="h-30"
/>
</div>

<SongForm :state-ref="state" :errors="errors" />
<div flex="~ gap-2 items-center" mt5>
<SimpleButton
:class="showTab === 'lyrics' ? '' : 'op50'"
Expand Down
68 changes: 68 additions & 0 deletions app/components/editors/SongForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script setup lang="ts">
import type { MaruSongDataParsed } from '@marure/schema'
import type { Reactive } from 'vue'
const props = defineProps<{
stateRef: Reactive<MaruSongDataParsed>
errors: Reactive<ValidationErrors>
}>()
const stateRef = toRef(props.stateRef)
const { title, artists, tags } = toRefs(props.errors)
const artistsString = computed({
get: () => (stateRef.value.artists || []).join(', '),
set: (value: string) => {
stateRef.value.artists = value.split(',').map(v => v.trim())
},
})
const tagsString = computed({
get: () => (stateRef.value.tags || []).join(', '),
set: (value: string) => {
stateRef.value.tags = value.split(',').map(v => v.trim())
},
})
const offsetString = computed({
get: () => String(stateRef.value.offset || ''),
set: (value: string) => {
stateRef.value.offset = Number(value)
},
})
const notesString = computed({
get: () => (stateRef.value.notes || []).join('\n'),
set: (value: string) => {
stateRef.value.notes = value.split('\n')
},
})
</script>

<template>
<div flex="~ col gap-2" max-w-150>
<h1 my4 text-2xl>
{{ $t("lyrics.editLyrics") }}
</h1>
<TextInput :model-value="stateRef.youtube" label="YouTube ID" input-class="font-mono" disabled />
<div>
<TextInput v-model="stateRef.title" :label="$t('song.title')" />
<span class="text-sm text-red-500">{{ title }}</span>
</div>
<div>
<TextInput v-model="artistsString" :label="$t('song.artist')" />
<span v-if="artists" class="text-sm text-red-500">{{ artists }}</span>
</div>
<div>
<TextInput v-model="tagsString" :label="$t('song.tags')" />
<span v-if="tags" class="text-sm text-red-500">{{ tags }}</span>
</div>
<TextInput v-model="offsetString" :label="$t('song.offset')" />
<TextInput
v-model="notesString"
:label="$t('common.notes')"
type="textarea"
input-class="h-30"
/>
</div>
</template>
58 changes: 58 additions & 0 deletions app/composables/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { MaruSongDataParsed } from '@marure/schema'
import type { Ref } from 'vue'

Check failure on line 2 in app/composables/validator.ts

View workflow job for this annotation

GitHub Actions / lint

'Ref' is defined but never used
import { useNuxtApp } from 'nuxt/app'
import { reactive } from 'vue'

export interface ValidationErrors {
youtube: string
title: string
artists: string
tags: string
offset?: string
notes?: string
}

export function useValidation(stateRef: MaruSongDataParsed) {
const errors = reactive<ValidationErrors>({
youtube: '',
title: '',
artists: '',
tags: '',
})
const nuxtApp = useNuxtApp()
const { t } = nuxtApp.$i18n

const validate = (): boolean => {
let isValid = true

errors.youtube = ''
errors.title = ''
errors.artists = ''
errors.tags = ''

if (!stateRef.youtube || !stateRef.youtube.trim()) {
errors.youtube = t('editor.validator.youtube')
isValid = false
}

if (!stateRef.title || !stateRef.title.trim()) {
errors.title = t('editor.validator.title')
isValid = false
}
if (!stateRef.artists || stateRef.artists?.includes('')) {
errors.artists = t('editor.validator.artists')
isValid = false
}

if (!stateRef.tags || stateRef.tags?.includes('')) {
errors.tags = t('editor.validator.tags')
isValid = false
}
return isValid
}

return {
errors,
validate,
}
}
8 changes: 7 additions & 1 deletion app/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@
"setCurrentTime": "Apply current playback time and go next",
"setCurrentTimeOnly": "Apply current playback time"
},
"visualization": "Visualization"
"visualization": "Visualization",
"validator": {
"youtube": "YouTube ID is required.",
"title": "Title is required.",
"artists": "At least one artist is required.",
"tags": "At least one tag is required."
}
},
"footer": {
"fileFormat": "File Format",
Expand Down
8 changes: 7 additions & 1 deletion app/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@
"setCurrentTime": "現在の再生時間に設定して次の行へ",
"setCurrentTimeOnly": "現在の再生時間に設定"
},
"visualization": "可視化"
"visualization": "可視化",
"validator": {
"youtube": "YouTube IDは必須です。",
"title": "タイトルは必須です。",
"artists": "少なくとも1人のアーティストが必要です。",
"tags": "少なくとも1つのタグが必要です。"
}
},
"footer": {
"fileFormat": "ファイル形式",
Expand Down
9 changes: 8 additions & 1 deletion app/locales/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@
"setCurrentTime": "设为当前播放时间后跳行",
"setCurrentTimeOnly": "设为当前播放时间"
},
"visualization": "可视化"
"visualization": "可视化",
"validator": {
"youtube": "YouTube ID 是必填的。",
"title": "标题是必填的。",
"artists": "至少需要一个歌手。",
"tags": "至少需要一个标签。"
}

},
"footer": {
"fileFormat": "文件格式",
Expand Down
8 changes: 7 additions & 1 deletion app/locales/zh-Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@
"setCurrentTime": "設為當前播放時間後跳行",
"setCurrentTimeOnly": "設為當前播放時間"
},
"visualization": "可視化"
"visualization": "可視化",
"validator": {
"youtube": "YouTube ID 是必填的。",
"title": "標題是必填的。",
"artists": "至少需要一個歌手。",
"tags": "至少需要一個標籤。"
}
},
"footer": {
"fileFormat": "檔案格式",
Expand Down

0 comments on commit dda8da6

Please sign in to comment.