From 070808737caccca21b021ebbb0ddf8b7c6bfcc27 Mon Sep 17 00:00:00 2001 From: Leon Date: Mon, 23 Feb 2026 12:14:51 +0100 Subject: [PATCH] separate structure and option --- src/screens/Writing/CustomizeSongStructure.js | 97 ++++++- .../Writing/CustomizeSongStructure.web.js | 237 ++++++++++++------ 2 files changed, 242 insertions(+), 92 deletions(-) diff --git a/src/screens/Writing/CustomizeSongStructure.js b/src/screens/Writing/CustomizeSongStructure.js index 7b20779..bcfd6dd 100644 --- a/src/screens/Writing/CustomizeSongStructure.js +++ b/src/screens/Writing/CustomizeSongStructure.js @@ -10,7 +10,6 @@ import { arraysAreSame, formatStructureLabel, getSegmentMeta, - OPTIONAL_STRUCTURE_SEGMENTS, sanitizeStructureList, } from '../../utils/songStructure' 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.' const INTRO_ERROR_MESSAGE = "L'introduction musicale doit rester en première position." 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 = []) => sanitizeStructureList(values, SANITIZE_OPTIONS).map((type, index) => @@ -106,14 +131,24 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { type: baseType, value: baseType, label: formatStructureLabel(baseType, nextCount), - sortable: true, + mode: 'structure', + draggable: !isAutoSegmentType(baseType), } }) }, [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) return { type, label: meta?.label || formatStructureLabel(type, 1) } }), @@ -191,7 +226,10 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { const Row = ({ item: rowData, onPress }) => { const baseType = rowData?.type || rowData?.value 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 count = rowData?.count ?? 0 const containerStyle = StyleSheet.flatten([styles.rowContainer]) @@ -212,11 +250,13 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { > {rowData?.label || ''} - {rowData?.sortable ? ( + {showActions ? ( - - - + {canDrag && ( + + + + )} {canRemove && ( handleRemoveItem(rowData.id)} @@ -241,7 +281,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { { /> - Éléments à ajouter + Options automatiques { const count = segmentCounts[item.type] || 0 @@ -289,7 +329,38 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { handleOptionalSegmentPress(item.type)} + /> + + ) + }} + keyExtractor={(item) => item.type} + contentContainerStyle={{ paddingBottom: 24 }} + scrollEnabled={false} + nestedScrollEnabled={false} + /> + + + Options glisser-déposer + + + { + const count = segmentCounts[item.type] || 0 + const selected = count > 0 + const wrapperStyle = index === 0 ? null : styles.optionWrapper + return ( + + { @@ -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 randomSuffix = () => Math.random().toString(36).slice(2, 8) @@ -241,14 +286,29 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { [sortableItems] ) - const paletteSegments = useMemo( + const autoPaletteSegments = useMemo( () => - OPTIONAL_STRUCTURE_SEGMENTS.map((type) => { + AUTO_STRUCTURE_SEGMENTS.map((type) => { const info = getPaletteSegmentInfo(type) return { type, label: info.label, 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] ) + 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 ( + startPaletteDrag(segment.type, event) : undefined} + > + + + + {segment.label} + {description ? ( + {description} + ) : null} + + + 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]) + } + > + + + + {count} + + + + + + ) + } + const StructureRow = ({ item }) => { const baseType = item?.type || item?.value const meta = getSegmentMeta(baseType) const canRemove = meta?.optional === true - const isIntroduction = INTRO_TYPES.has(baseType) + const isAutoSegment = isAutoSegmentType(baseType) const rowRef = useRef(null) const isActive = activeItemId === item.id const containerStyle = StyleSheet.flatten([ @@ -775,7 +903,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { ]) const rowStyle = StyleSheet.flatten([ styles.row, - isIntroduction ? styles.rowDisabled : styles.rowDraggable, + isAutoSegment ? styles.rowDisabled : styles.rowDraggable, isActive ? styles.rowHidden : null, ]) @@ -791,7 +919,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { ref={rowRef} style={rowStyle} onPointerDown={(event) => { - if (isIntroduction) return + if (isAutoSegment) return startItemDrag(item.id, event, rowRef.current) }} > @@ -811,7 +939,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { )} - {!isIntroduction && ( + {!isAutoSegment && ( { @@ -832,7 +960,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { @@ -865,9 +993,6 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { - - Éléments à ajouter - { contentContainerStyle={styles.listContent} showsVerticalScrollIndicator > - - {paletteSegments.map((segment) => { - const count = segmentCounts[segment.type] || 0 - const isActive = activeItemId === `palette-${segment.type}` - const blurStyle = StyleSheet.flatten([ - styles.itemBlur, - styles.paletteItemBlur, - isActive ? styles.activeBlur : null, - ]) - const badgeStyle = StyleSheet.flatten([ - styles.countBadge, - count > 0 ? styles.countBadgeActive : null, - ]) - - return ( - startPaletteDrag(segment.type, event)} - > - - - - {segment.label} - {segment.description ? ( - {segment.description} - ) : null} - - - 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, - ]) - } - > - - - - {count} - - - - - - ) - })} + + + Options automatiques + + + {autoPaletteSegments.map((segment) => renderPaletteSegment(segment))} + + + + + Options glisser-déposer + + + {dragPaletteSegments.map((segment) => renderPaletteSegment(segment))} + @@ -1033,9 +1108,6 @@ const styles = StyleSheet.create({ borderRadius: 999, backgroundColor: 'rgba(255,255,255,0.12)', }, - addSectionTag: { - marginTop: 0, - }, sectionTagText: { fontSize: 12, color: Palette.white, @@ -1145,9 +1217,16 @@ const styles = StyleSheet.create({ paletteList: { gap: 12, }, + paletteSection: { + gap: 16, + marginBottom: 20, + }, paletteCardWrapper: { cursor: 'grab', }, + paletteCardWrapperAuto: { + cursor: 'default', + }, paletteRow: { flexDirection: 'row', alignItems: 'flex-start',