pre-refrain always before refrain

This commit is contained in:
2026-02-12 11:38:43 +01:00
parent 32331d8b2b
commit 8693b96367
+68 -1
View File
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { FlatList, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native' import { FlatList, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import Animated, { useAnimatedRef } from 'react-native-reanimated' import Animated, { useAnimatedRef } from 'react-native-reanimated'
import Sortable from 'react-native-sortables' import Sortable from 'react-native-sortables'
import { useGlobal } from 'reactn'
import { icons } from '../../assets' import { icons } from '../../assets'
import { Palette } from '../../styles' import { Palette } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts' import { FONT_FAMILY } from '../../styles/Fonts'
@@ -21,6 +22,11 @@ const createSortableItem = (type, index = 0) => ({
type, type,
value: type, value: type,
}) })
const PRE_REFRAIN_TYPE = 'pre_refrain_instrumental'
const INTRO_TYPES = new Set(['short_intro', 'long_intro', 'intro', 'introduction'])
const PRE_REFRAIN_ERROR_MESSAGE =
'Impossible de positionner un pré-refrain ici. Place-le juste avant un refrain.'
const INTRO_ERROR_MESSAGE = "L'introduction musicale doit rester en première position."
const SANITIZE_OPTIONS = { autoInjectPreChorus: false } const SANITIZE_OPTIONS = { autoInjectPreChorus: false }
const buildSortableItems = (values = []) => const buildSortableItems = (values = []) =>
@@ -28,8 +34,36 @@ const buildSortableItems = (values = []) =>
createSortableItem(type, index) createSortableItem(type, index)
) )
const validateStructureRules = (values = []) => {
for (let index = 0; index < values.length; index += 1) {
const type = values[index]
if (type === PRE_REFRAIN_TYPE) {
const nextType = values[index + 1]
if (nextType !== 'refrain') {
return {
valid: false,
reason: 'pre_refrain_invalid_position',
message: PRE_REFRAIN_ERROR_MESSAGE,
}
}
}
}
const introIndex = values.findIndex((type) => INTRO_TYPES.has(type))
if (introIndex > 0) {
return {
valid: false,
reason: 'intro_must_be_first',
message: INTRO_ERROR_MESSAGE,
}
}
return { valid: true }
}
const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
const scrollRef = useAnimatedRef() const scrollRef = useAnimatedRef()
const [, setTooltip] = useGlobal('_tooltip')
const sanitizedBase = useMemo( const sanitizedBase = useMemo(
() => sanitizeStructureList(baseStructure, SANITIZE_OPTIONS), () => sanitizeStructureList(baseStructure, SANITIZE_OPTIONS),
[baseStructure] [baseStructure]
@@ -39,6 +73,13 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
[] []
) )
const [sortableItems, setSortableItems] = useState(() => buildSortableItems(sanitizedBase)) const [sortableItems, setSortableItems] = useState(() => buildSortableItems(sanitizedBase))
const showTooltip = useCallback(
(text, type = 'error') => {
if (!text) return
setTooltip({ text, type })
},
[setTooltip]
)
useEffect(() => { useEffect(() => {
setSortableItems((prev) => { setSortableItems((prev) => {
@@ -119,12 +160,32 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
return buildSortableItems(sanitized) return buildSortableItems(sanitized)
} }
if (type === PRE_REFRAIN_TYPE) {
const firstRefrainIndex = nextValues.findIndex((value) => value === 'refrain')
if (firstRefrainIndex === -1) {
showTooltip(PRE_REFRAIN_ERROR_MESSAGE)
return prev
}
nextValues.splice(firstRefrainIndex, 0, type)
} else if (meta?.exclusiveGroup === 'intro') {
nextValues.unshift(type)
} else if (meta?.exclusiveGroup === 'outro') {
nextValues.push(type) nextValues.push(type)
} else {
nextValues.push(type)
}
const validation = validateStructureRules(nextValues)
if (!validation.valid) {
showTooltip(validation.message)
return prev
}
const sanitized = sanitizeStructureList(nextValues, SANITIZE_OPTIONS) const sanitized = sanitizeStructureList(nextValues, SANITIZE_OPTIONS)
return buildSortableItems(sanitized) return buildSortableItems(sanitized)
}) })
}, },
[extractValues] [extractValues, showTooltip]
) )
const Row = ({ item: rowData, onPress }) => { const Row = ({ item: rowData, onPress }) => {
@@ -201,6 +262,12 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
scrollableRef={scrollRef} scrollableRef={scrollRef}
onDragEnd={({ data: newData }) => { onDragEnd={({ data: newData }) => {
const values = extractValues(newData) const values = extractValues(newData)
const validation = validateStructureRules(values)
if (!validation.valid) {
showTooltip(validation.message)
setSortableItems((prev) => buildSortableItems(extractValues(prev)))
return
}
const sanitized = sanitizeStructureList(values, SANITIZE_OPTIONS) const sanitized = sanitizeStructureList(values, SANITIZE_OPTIONS)
setSortableItems(buildSortableItems(sanitized)) setSortableItems(buildSortableItems(sanitized))
}} }}