separate structure and option
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
|||||||
arraysAreSame,
|
arraysAreSame,
|
||||||
formatStructureLabel,
|
formatStructureLabel,
|
||||||
getSegmentMeta,
|
getSegmentMeta,
|
||||||
OPTIONAL_STRUCTURE_SEGMENTS,
|
|
||||||
sanitizeStructureList,
|
sanitizeStructureList,
|
||||||
} from '../../utils/songStructure'
|
} from '../../utils/songStructure'
|
||||||
import ItemContainer from '../../components/ItemContainer/ItemContainer'
|
import ItemContainer from '../../components/ItemContainer/ItemContainer'
|
||||||
@@ -28,6 +27,32 @@ const PRE_REFRAIN_ERROR_MESSAGE =
|
|||||||
'Impossible de positionner un pré-refrain ici. Place-le juste avant un refrain.'
|
'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 INTRO_ERROR_MESSAGE = "L'introduction musicale doit rester en première position."
|
||||||
const SANITIZE_OPTIONS = { autoInjectPreChorus: false }
|
const SANITIZE_OPTIONS = { autoInjectPreChorus: false }
|
||||||
|
const AUTO_STRUCTURE_SEGMENTS = [
|
||||||
|
'short_intro',
|
||||||
|
'long_intro',
|
||||||
|
PRE_REFRAIN_TYPE,
|
||||||
|
'final_apogee',
|
||||||
|
'arret_net',
|
||||||
|
'fade_out',
|
||||||
|
'transition_douce',
|
||||||
|
]
|
||||||
|
const DRAG_STRUCTURE_SEGMENTS = [
|
||||||
|
'solo_de_guitare',
|
||||||
|
'solo_de_guitare_electrique',
|
||||||
|
'solo_de_batterie',
|
||||||
|
'solo_de_saxophone',
|
||||||
|
'solo_de_violon',
|
||||||
|
'break',
|
||||||
|
'pont',
|
||||||
|
]
|
||||||
|
|
||||||
|
const isAutoSegmentType = (type) => {
|
||||||
|
if (!type) return false
|
||||||
|
if (INTRO_TYPES.has(type)) return true
|
||||||
|
if (type === PRE_REFRAIN_TYPE) return true
|
||||||
|
const meta = getSegmentMeta(type)
|
||||||
|
return meta?.exclusiveGroup === 'outro'
|
||||||
|
}
|
||||||
|
|
||||||
const buildSortableItems = (values = []) =>
|
const buildSortableItems = (values = []) =>
|
||||||
sanitizeStructureList(values, SANITIZE_OPTIONS).map((type, index) =>
|
sanitizeStructureList(values, SANITIZE_OPTIONS).map((type, index) =>
|
||||||
@@ -106,14 +131,24 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
type: baseType,
|
type: baseType,
|
||||||
value: baseType,
|
value: baseType,
|
||||||
label: formatStructureLabel(baseType, nextCount),
|
label: formatStructureLabel(baseType, nextCount),
|
||||||
sortable: true,
|
mode: 'structure',
|
||||||
|
draggable: !isAutoSegmentType(baseType),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [sortableItems])
|
}, [sortableItems])
|
||||||
|
|
||||||
const optionalSegments = useMemo(
|
const autoSegments = useMemo(
|
||||||
() =>
|
() =>
|
||||||
OPTIONAL_STRUCTURE_SEGMENTS.map((type) => {
|
AUTO_STRUCTURE_SEGMENTS.map((type) => {
|
||||||
|
const meta = getSegmentMeta(type)
|
||||||
|
return { type, label: meta?.label || formatStructureLabel(type, 1) }
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const dragSegments = useMemo(
|
||||||
|
() =>
|
||||||
|
DRAG_STRUCTURE_SEGMENTS.map((type) => {
|
||||||
const meta = getSegmentMeta(type)
|
const meta = getSegmentMeta(type)
|
||||||
return { type, label: meta?.label || formatStructureLabel(type, 1) }
|
return { type, label: meta?.label || formatStructureLabel(type, 1) }
|
||||||
}),
|
}),
|
||||||
@@ -191,7 +226,10 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
const Row = ({ item: rowData, onPress }) => {
|
const Row = ({ item: rowData, onPress }) => {
|
||||||
const baseType = rowData?.type || rowData?.value
|
const baseType = rowData?.type || rowData?.value
|
||||||
const meta = getSegmentMeta(baseType)
|
const meta = getSegmentMeta(baseType)
|
||||||
const canRemove = rowData?.sortable && meta?.optional === true
|
const isOptionRow = rowData?.mode === 'option'
|
||||||
|
const showActions = !isOptionRow
|
||||||
|
const canRemove = showActions && meta?.optional === true
|
||||||
|
const canDrag = showActions && rowData?.draggable !== false
|
||||||
const selected = rowData?.selected
|
const selected = rowData?.selected
|
||||||
const count = rowData?.count ?? 0
|
const count = rowData?.count ?? 0
|
||||||
const containerStyle = StyleSheet.flatten([styles.rowContainer])
|
const containerStyle = StyleSheet.flatten([styles.rowContainer])
|
||||||
@@ -212,11 +250,13 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
>
|
>
|
||||||
<View style={styles.rowContent}>
|
<View style={styles.rowContent}>
|
||||||
<Text style={styles.rowText}>{rowData?.label || ''}</Text>
|
<Text style={styles.rowText}>{rowData?.label || ''}</Text>
|
||||||
{rowData?.sortable ? (
|
{showActions ? (
|
||||||
<View style={styles.rowActions}>
|
<View style={styles.rowActions}>
|
||||||
<Sortable.Handle>
|
{canDrag && (
|
||||||
<Image source={icons.dragDots} style={styles.handleIcon} />
|
<Sortable.Handle>
|
||||||
</Sortable.Handle>
|
<Image source={icons.dragDots} style={styles.handleIcon} />
|
||||||
|
</Sortable.Handle>
|
||||||
|
)}
|
||||||
{canRemove && (
|
{canRemove && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => handleRemoveItem(rowData.id)}
|
onPress={() => handleRemoveItem(rowData.id)}
|
||||||
@@ -241,7 +281,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
<View style={{ flex: 1, gap: 10, marginTop: 16 }}>
|
<View style={{ flex: 1, gap: 10, marginTop: 16 }}>
|
||||||
<CreateLyricsHeader
|
<CreateLyricsHeader
|
||||||
title="Personnalise la structure de ta chanson"
|
title="Personnalise la structure de ta chanson"
|
||||||
subTitle="Réorganise par glisser-déposer"
|
subTitle="Active les options automatiques et réorganise par glisser-déposer"
|
||||||
/>
|
/>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<Animated.ScrollView
|
<Animated.ScrollView
|
||||||
@@ -274,11 +314,11 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
/>
|
/>
|
||||||
</ItemContainer>
|
</ItemContainer>
|
||||||
<View style={[styles.sectionTag, { marginTop: 24 }]}>
|
<View style={[styles.sectionTag, { marginTop: 24 }]}>
|
||||||
<Text style={styles.sectionTagText}>Éléments à ajouter</Text>
|
<Text style={styles.sectionTagText}>Options automatiques</Text>
|
||||||
</View>
|
</View>
|
||||||
<ItemContainer height="auto" disableKeyboardHeight style={styles.blurSection}>
|
<ItemContainer height="auto" disableKeyboardHeight style={styles.blurSection}>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={optionalSegments}
|
data={autoSegments}
|
||||||
gap={12}
|
gap={12}
|
||||||
renderItem={({ item, index }) => {
|
renderItem={({ item, index }) => {
|
||||||
const count = segmentCounts[item.type] || 0
|
const count = segmentCounts[item.type] || 0
|
||||||
@@ -289,7 +329,38 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
<Row
|
<Row
|
||||||
item={{
|
item={{
|
||||||
...item,
|
...item,
|
||||||
sortable: false,
|
mode: 'option',
|
||||||
|
selected,
|
||||||
|
count,
|
||||||
|
}}
|
||||||
|
onPress={() => handleOptionalSegmentPress(item.type)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
keyExtractor={(item) => item.type}
|
||||||
|
contentContainerStyle={{ paddingBottom: 24 }}
|
||||||
|
scrollEnabled={false}
|
||||||
|
nestedScrollEnabled={false}
|
||||||
|
/>
|
||||||
|
</ItemContainer>
|
||||||
|
<View style={[styles.sectionTag, { marginTop: 24 }]}>
|
||||||
|
<Text style={styles.sectionTagText}>Options glisser-déposer</Text>
|
||||||
|
</View>
|
||||||
|
<ItemContainer height="auto" disableKeyboardHeight style={styles.blurSection}>
|
||||||
|
<FlatList
|
||||||
|
data={dragSegments}
|
||||||
|
gap={12}
|
||||||
|
renderItem={({ item, index }) => {
|
||||||
|
const count = segmentCounts[item.type] || 0
|
||||||
|
const selected = count > 0
|
||||||
|
const wrapperStyle = index === 0 ? null : styles.optionWrapper
|
||||||
|
return (
|
||||||
|
<View style={wrapperStyle}>
|
||||||
|
<Row
|
||||||
|
item={{
|
||||||
|
...item,
|
||||||
|
mode: 'option',
|
||||||
selected,
|
selected,
|
||||||
count,
|
count,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
arraysAreSame,
|
arraysAreSame,
|
||||||
formatStructureLabel,
|
formatStructureLabel,
|
||||||
getSegmentMeta,
|
getSegmentMeta,
|
||||||
OPTIONAL_STRUCTURE_SEGMENTS,
|
|
||||||
sanitizeStructureList,
|
sanitizeStructureList,
|
||||||
} from '../../utils/songStructure'
|
} from '../../utils/songStructure'
|
||||||
import CreateLyricsHeader from './components/CreateLyricsHeader'
|
import CreateLyricsHeader from './components/CreateLyricsHeader'
|
||||||
@@ -29,6 +28,24 @@ const PRE_REFRAIN_ERROR_MESSAGE =
|
|||||||
'Impossible de positionner un pré-refrain ici. Place-le juste avant un refrain.'
|
'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 INTRO_ERROR_MESSAGE = "L'introduction musicale doit rester en première position."
|
||||||
const SANITIZE_OPTIONS = { autoInjectPreChorus: false }
|
const SANITIZE_OPTIONS = { autoInjectPreChorus: false }
|
||||||
|
const AUTO_STRUCTURE_SEGMENTS = [
|
||||||
|
'short_intro',
|
||||||
|
'long_intro',
|
||||||
|
PRE_REFRAIN_TYPE,
|
||||||
|
'final_apogee',
|
||||||
|
'arret_net',
|
||||||
|
'fade_out',
|
||||||
|
'transition_douce',
|
||||||
|
]
|
||||||
|
const DRAG_STRUCTURE_SEGMENTS = [
|
||||||
|
'solo_de_guitare',
|
||||||
|
'solo_de_guitare_electrique',
|
||||||
|
'solo_de_batterie',
|
||||||
|
'solo_de_saxophone',
|
||||||
|
'solo_de_violon',
|
||||||
|
'break',
|
||||||
|
'pont',
|
||||||
|
]
|
||||||
|
|
||||||
const PALETTE_SEGMENT_OVERRIDES = [
|
const PALETTE_SEGMENT_OVERRIDES = [
|
||||||
{
|
{
|
||||||
@@ -36,6 +53,18 @@ const PALETTE_SEGMENT_OVERRIDES = [
|
|||||||
description:
|
description:
|
||||||
'Transition contrastée qui relance l’énergie avant d’entrer dans la dernière partie.',
|
'Transition contrastée qui relance l’énergie avant d’entrer dans la dernière partie.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'short_intro',
|
||||||
|
description: 'Introduction courte placée en ouverture du morceau.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'long_intro',
|
||||||
|
description: 'Introduction longue pour installer l’ambiance dès le début.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'pre_refrain_instrumental',
|
||||||
|
description: 'Se place automatiquement juste avant un refrain.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'interlude_melodique',
|
type: 'interlude_melodique',
|
||||||
description: 'Respiration instrumentale qui met la mélodie à l’honneur sans paroles.',
|
description: 'Respiration instrumentale qui met la mélodie à l’honneur sans paroles.',
|
||||||
@@ -44,10 +73,18 @@ const PALETTE_SEGMENT_OVERRIDES = [
|
|||||||
type: 'final_apogee',
|
type: 'final_apogee',
|
||||||
description: 'Clôture apothéose qui amplifie l’émotion pour un final impactant.',
|
description: 'Clôture apothéose qui amplifie l’émotion pour un final impactant.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'arret_net',
|
||||||
|
description: 'Final abrupt qui coupe net la musique.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'fade_out',
|
type: 'fade_out',
|
||||||
description: 'Sortie progressive où le morceau disparaît doucement dans le mix.',
|
description: 'Sortie progressive où le morceau disparaît doucement dans le mix.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'transition_douce',
|
||||||
|
description: 'Fin en douceur qui glisse vers le silence.',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const getPaletteSegmentInfo = (type) => {
|
const getPaletteSegmentInfo = (type) => {
|
||||||
@@ -59,6 +96,14 @@ const getPaletteSegmentInfo = (type) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isAutoSegmentType = (type) => {
|
||||||
|
if (!type) return false
|
||||||
|
if (INTRO_TYPES.has(type)) return true
|
||||||
|
if (type === PRE_REFRAIN_TYPE) return true
|
||||||
|
const meta = getSegmentMeta(type)
|
||||||
|
return meta?.exclusiveGroup === 'outro'
|
||||||
|
}
|
||||||
|
|
||||||
const getScrollY = () => (typeof window !== 'undefined' ? window.scrollY : 0)
|
const getScrollY = () => (typeof window !== 'undefined' ? window.scrollY : 0)
|
||||||
|
|
||||||
const randomSuffix = () => Math.random().toString(36).slice(2, 8)
|
const randomSuffix = () => Math.random().toString(36).slice(2, 8)
|
||||||
@@ -241,14 +286,29 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
[sortableItems]
|
[sortableItems]
|
||||||
)
|
)
|
||||||
|
|
||||||
const paletteSegments = useMemo(
|
const autoPaletteSegments = useMemo(
|
||||||
() =>
|
() =>
|
||||||
OPTIONAL_STRUCTURE_SEGMENTS.map((type) => {
|
AUTO_STRUCTURE_SEGMENTS.map((type) => {
|
||||||
const info = getPaletteSegmentInfo(type)
|
const info = getPaletteSegmentInfo(type)
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
label: info.label,
|
label: info.label,
|
||||||
description: info.description,
|
description: info.description,
|
||||||
|
placement: 'auto',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const dragPaletteSegments = useMemo(
|
||||||
|
() =>
|
||||||
|
DRAG_STRUCTURE_SEGMENTS.map((type) => {
|
||||||
|
const info = getPaletteSegmentInfo(type)
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
label: info.label,
|
||||||
|
description: info.description,
|
||||||
|
placement: 'drag',
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
@@ -756,11 +816,79 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
[attachGlobalListeners]
|
[attachGlobalListeners]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const renderPaletteSegment = (segment) => {
|
||||||
|
const count = segmentCounts[segment.type] || 0
|
||||||
|
const isActive = activeItemId === `palette-${segment.type}`
|
||||||
|
const isDraggable = segment.placement === 'drag'
|
||||||
|
const description =
|
||||||
|
segment.description || (segment.placement === 'auto' ? 'Placement automatique.' : '')
|
||||||
|
const blurStyle = StyleSheet.flatten([
|
||||||
|
styles.itemBlur,
|
||||||
|
styles.paletteItemBlur,
|
||||||
|
isActive ? styles.activeBlur : null,
|
||||||
|
])
|
||||||
|
const badgeStyle = StyleSheet.flatten([
|
||||||
|
styles.countBadge,
|
||||||
|
count > 0 ? styles.countBadgeActive : null,
|
||||||
|
])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={segment.type}
|
||||||
|
style={StyleSheet.flatten([
|
||||||
|
styles.paletteCardWrapper,
|
||||||
|
!isDraggable ? styles.paletteCardWrapperAuto : null,
|
||||||
|
])}
|
||||||
|
onPointerDown={isDraggable ? (event) => startPaletteDrag(segment.type, event) : undefined}
|
||||||
|
>
|
||||||
|
<CreateLyricsHeader
|
||||||
|
tint="dark"
|
||||||
|
intensity={40}
|
||||||
|
showBorder={false}
|
||||||
|
containerStyle={styles.rowContainer}
|
||||||
|
blurViewStyle={blurStyle}
|
||||||
|
>
|
||||||
|
<View style={styles.paletteRow}>
|
||||||
|
<View style={styles.paletteTextWrapper}>
|
||||||
|
<Text style={styles.rowText}>{segment.label}</Text>
|
||||||
|
{description ? (
|
||||||
|
<Text style={styles.paletteDescription}>{description}</Text>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
<View style={styles.paletteActions}>
|
||||||
|
<Pressable
|
||||||
|
onPointerDown={(ev) => ev?.stopPropagation?.()}
|
||||||
|
onPressIn={(ev) => ev?.stopPropagation?.()}
|
||||||
|
onPress={(ev) => {
|
||||||
|
ev?.stopPropagation?.()
|
||||||
|
const result = addOptionalSegment(segment.type)
|
||||||
|
if (result?.success) {
|
||||||
|
setInsertPreviewIndex(null)
|
||||||
|
setActiveItemId(null)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
|
||||||
|
style={({ pressed }) =>
|
||||||
|
StyleSheet.flatten([styles.addButton, pressed ? styles.addButtonPressed : null])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Image source={icons.add} style={styles.addIcon} />
|
||||||
|
</Pressable>
|
||||||
|
<View style={badgeStyle}>
|
||||||
|
<Text style={styles.countBadgeText}>{count}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</CreateLyricsHeader>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const StructureRow = ({ item }) => {
|
const StructureRow = ({ item }) => {
|
||||||
const baseType = item?.type || item?.value
|
const baseType = item?.type || item?.value
|
||||||
const meta = getSegmentMeta(baseType)
|
const meta = getSegmentMeta(baseType)
|
||||||
const canRemove = meta?.optional === true
|
const canRemove = meta?.optional === true
|
||||||
const isIntroduction = INTRO_TYPES.has(baseType)
|
const isAutoSegment = isAutoSegmentType(baseType)
|
||||||
const rowRef = useRef(null)
|
const rowRef = useRef(null)
|
||||||
const isActive = activeItemId === item.id
|
const isActive = activeItemId === item.id
|
||||||
const containerStyle = StyleSheet.flatten([
|
const containerStyle = StyleSheet.flatten([
|
||||||
@@ -775,7 +903,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
])
|
])
|
||||||
const rowStyle = StyleSheet.flatten([
|
const rowStyle = StyleSheet.flatten([
|
||||||
styles.row,
|
styles.row,
|
||||||
isIntroduction ? styles.rowDisabled : styles.rowDraggable,
|
isAutoSegment ? styles.rowDisabled : styles.rowDraggable,
|
||||||
isActive ? styles.rowHidden : null,
|
isActive ? styles.rowHidden : null,
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -791,7 +919,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
ref={rowRef}
|
ref={rowRef}
|
||||||
style={rowStyle}
|
style={rowStyle}
|
||||||
onPointerDown={(event) => {
|
onPointerDown={(event) => {
|
||||||
if (isIntroduction) return
|
if (isAutoSegment) return
|
||||||
startItemDrag(item.id, event, rowRef.current)
|
startItemDrag(item.id, event, rowRef.current)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -811,7 +939,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
<Image source={icons.close} style={styles.removeIcon} />
|
<Image source={icons.close} style={styles.removeIcon} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
{!isIntroduction && (
|
{!isAutoSegment && (
|
||||||
<View
|
<View
|
||||||
style={styles.handleWrapper}
|
style={styles.handleWrapper}
|
||||||
onPointerDown={(event) => {
|
onPointerDown={(event) => {
|
||||||
@@ -832,7 +960,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<CreateLyricsHeader
|
<CreateLyricsHeader
|
||||||
title="Personnalise la structure de ta chanson"
|
title="Personnalise la structure de ta chanson"
|
||||||
subTitle="Réorganise par glisser-déposer"
|
subTitle="Active les options automatiques et réorganise par glisser-déposer"
|
||||||
/>
|
/>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<View style={styles.columns}>
|
<View style={styles.columns}>
|
||||||
@@ -865,9 +993,6 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
</ItemContainer>
|
</ItemContainer>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.column, styles.paletteColumn]}>
|
<View style={[styles.column, styles.paletteColumn]}>
|
||||||
<View style={[styles.sectionTag, styles.addSectionTag]}>
|
|
||||||
<Text style={styles.sectionTagText}>Éléments à ajouter</Text>
|
|
||||||
</View>
|
|
||||||
<ItemContainer
|
<ItemContainer
|
||||||
height="auto"
|
height="auto"
|
||||||
disableKeyboardHeight
|
disableKeyboardHeight
|
||||||
@@ -878,71 +1003,21 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
contentContainerStyle={styles.listContent}
|
contentContainerStyle={styles.listContent}
|
||||||
showsVerticalScrollIndicator
|
showsVerticalScrollIndicator
|
||||||
>
|
>
|
||||||
<View style={styles.paletteList}>
|
<View style={styles.paletteSection}>
|
||||||
{paletteSegments.map((segment) => {
|
<View style={styles.sectionTag}>
|
||||||
const count = segmentCounts[segment.type] || 0
|
<Text style={styles.sectionTagText}>Options automatiques</Text>
|
||||||
const isActive = activeItemId === `palette-${segment.type}`
|
</View>
|
||||||
const blurStyle = StyleSheet.flatten([
|
<View style={styles.paletteList}>
|
||||||
styles.itemBlur,
|
{autoPaletteSegments.map((segment) => renderPaletteSegment(segment))}
|
||||||
styles.paletteItemBlur,
|
</View>
|
||||||
isActive ? styles.activeBlur : null,
|
</View>
|
||||||
])
|
<View style={styles.paletteSection}>
|
||||||
const badgeStyle = StyleSheet.flatten([
|
<View style={styles.sectionTag}>
|
||||||
styles.countBadge,
|
<Text style={styles.sectionTagText}>Options glisser-déposer</Text>
|
||||||
count > 0 ? styles.countBadgeActive : null,
|
</View>
|
||||||
])
|
<View style={styles.paletteList}>
|
||||||
|
{dragPaletteSegments.map((segment) => renderPaletteSegment(segment))}
|
||||||
return (
|
</View>
|
||||||
<View
|
|
||||||
key={segment.type}
|
|
||||||
style={styles.paletteCardWrapper}
|
|
||||||
onPointerDown={(event) => startPaletteDrag(segment.type, event)}
|
|
||||||
>
|
|
||||||
<CreateLyricsHeader
|
|
||||||
tint="dark"
|
|
||||||
intensity={40}
|
|
||||||
showBorder={false}
|
|
||||||
containerStyle={styles.rowContainer}
|
|
||||||
blurViewStyle={blurStyle}
|
|
||||||
>
|
|
||||||
<View style={styles.paletteRow}>
|
|
||||||
<View style={styles.paletteTextWrapper}>
|
|
||||||
<Text style={styles.rowText}>{segment.label}</Text>
|
|
||||||
{segment.description ? (
|
|
||||||
<Text style={styles.paletteDescription}>{segment.description}</Text>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
<View style={styles.paletteActions}>
|
|
||||||
<Pressable
|
|
||||||
onPointerDown={(ev) => ev?.stopPropagation?.()}
|
|
||||||
onPressIn={(ev) => ev?.stopPropagation?.()}
|
|
||||||
onPress={(ev) => {
|
|
||||||
ev?.stopPropagation?.()
|
|
||||||
const result = addOptionalSegment(segment.type)
|
|
||||||
if (result?.success) {
|
|
||||||
setInsertPreviewIndex(null)
|
|
||||||
setActiveItemId(null)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
|
|
||||||
style={({ pressed }) =>
|
|
||||||
StyleSheet.flatten([
|
|
||||||
styles.addButton,
|
|
||||||
pressed ? styles.addButtonPressed : null,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Image source={icons.add} style={styles.addIcon} />
|
|
||||||
</Pressable>
|
|
||||||
<View style={badgeStyle}>
|
|
||||||
<Text style={styles.countBadgeText}>{count}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</CreateLyricsHeader>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</ItemContainer>
|
</ItemContainer>
|
||||||
@@ -1033,9 +1108,6 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 999,
|
borderRadius: 999,
|
||||||
backgroundColor: 'rgba(255,255,255,0.12)',
|
backgroundColor: 'rgba(255,255,255,0.12)',
|
||||||
},
|
},
|
||||||
addSectionTag: {
|
|
||||||
marginTop: 0,
|
|
||||||
},
|
|
||||||
sectionTagText: {
|
sectionTagText: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Palette.white,
|
color: Palette.white,
|
||||||
@@ -1145,9 +1217,16 @@ const styles = StyleSheet.create({
|
|||||||
paletteList: {
|
paletteList: {
|
||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
|
paletteSection: {
|
||||||
|
gap: 16,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
paletteCardWrapper: {
|
paletteCardWrapper: {
|
||||||
cursor: 'grab',
|
cursor: 'grab',
|
||||||
},
|
},
|
||||||
|
paletteCardWrapperAuto: {
|
||||||
|
cursor: 'default',
|
||||||
|
},
|
||||||
paletteRow: {
|
paletteRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
|
|||||||
Reference in New Issue
Block a user