feat: fixes and formatter

This commit is contained in:
2026-01-12 16:01:32 +01:00
parent 85c6084351
commit 11e632acff
353 changed files with 23315 additions and 27361 deletions
+103 -132
View File
@@ -1,162 +1,144 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import {
FlatList,
Image,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
import Animated, { useAnimatedRef } from "react-native-reanimated";
import Sortable from "react-native-sortables";
import { icons } from "../../assets";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { FlatList, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import Animated, { useAnimatedRef } from 'react-native-reanimated'
import Sortable from 'react-native-sortables'
import { icons } from '../../assets'
import { Palette } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts'
import {
arraysAreSame,
formatStructureLabel,
getSegmentMeta,
OPTIONAL_STRUCTURE_SEGMENTS,
sanitizeStructureList,
} from "../../utils/songStructure";
import ItemContainer from "../../components/ItemContainer/ItemContainer";
import CreateLyricsHeader from "./components/CreateLyricsHeader";
} from '../../utils/songStructure'
import ItemContainer from '../../components/ItemContainer/ItemContainer'
import CreateLyricsHeader from './components/CreateLyricsHeader'
const randomSuffix = () => Math.random().toString(36).slice(2, 8);
const randomSuffix = () => Math.random().toString(36).slice(2, 8)
const createSortableItem = (type, index = 0) => ({
id: `${type}-${index}-${randomSuffix()}`,
type,
value: type,
});
const SANITIZE_OPTIONS = { autoInjectPreChorus: false };
})
const SANITIZE_OPTIONS = { autoInjectPreChorus: false }
const buildSortableItems = (values = []) =>
sanitizeStructureList(values, SANITIZE_OPTIONS).map((type, index) =>
createSortableItem(type, index)
);
)
const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
const scrollRef = useAnimatedRef();
const scrollRef = useAnimatedRef()
const sanitizedBase = useMemo(
() => sanitizeStructureList(baseStructure, SANITIZE_OPTIONS),
[baseStructure]
);
)
const extractValues = useCallback(
(items) =>
(items || []).map((item) => item?.type || item?.value).filter(Boolean),
(items) => (items || []).map((item) => item?.type || item?.value).filter(Boolean),
[]
);
const [sortableItems, setSortableItems] = useState(() =>
buildSortableItems(sanitizedBase)
);
)
const [sortableItems, setSortableItems] = useState(() => buildSortableItems(sanitizedBase))
useEffect(() => {
setSortableItems((prev) => {
const currentValues = extractValues(prev);
const currentValues = extractValues(prev)
if (arraysAreSame(currentValues, sanitizedBase)) {
return prev;
return prev
}
return buildSortableItems(sanitizedBase);
});
}, [sanitizedBase, extractValues]);
return buildSortableItems(sanitizedBase)
})
}, [sanitizedBase, extractValues])
useEffect(() => {
const values = extractValues(sortableItems);
onChange?.(values);
}, [sortableItems, onChange, extractValues]);
const values = extractValues(sortableItems)
onChange?.(values)
}, [sortableItems, onChange, extractValues])
const labeledItems = useMemo(() => {
const counts = {};
const counts = {}
return sortableItems.map((item) => {
const baseType = item.type || item.value;
const nextCount = (counts[baseType] || 0) + 1;
counts[baseType] = nextCount;
const baseType = item.type || item.value
const nextCount = (counts[baseType] || 0) + 1
counts[baseType] = nextCount
return {
...item,
type: baseType,
value: baseType,
label: formatStructureLabel(baseType, nextCount),
sortable: true,
};
});
}, [sortableItems]);
}
})
}, [sortableItems])
const optionalSegments = useMemo(
() =>
OPTIONAL_STRUCTURE_SEGMENTS.map((type) => {
const meta = getSegmentMeta(type);
return { type, label: meta?.label || formatStructureLabel(type, 1) };
const meta = getSegmentMeta(type)
return { type, label: meta?.label || formatStructureLabel(type, 1) }
}),
[]
);
)
const segmentCounts = useMemo(() => {
const counts = {};
const counts = {}
extractValues(sortableItems).forEach((type) => {
counts[type] = (counts[type] || 0) + 1;
});
return counts;
}, [sortableItems, extractValues]);
counts[type] = (counts[type] || 0) + 1
})
return counts
}, [sortableItems, extractValues])
const handleRemoveItem = useCallback(
(itemId) => {
setSortableItems((prev) => {
const remaining = prev.filter((item) => item.id !== itemId);
const sanitized = sanitizeStructureList(
extractValues(remaining),
SANITIZE_OPTIONS
);
return buildSortableItems(sanitized);
});
const remaining = prev.filter((item) => item.id !== itemId)
const sanitized = sanitizeStructureList(extractValues(remaining), SANITIZE_OPTIONS)
return buildSortableItems(sanitized)
})
},
[extractValues]
);
)
const handleOptionalSegmentPress = useCallback(
(type) => {
const meta = getSegmentMeta(type);
const meta = getSegmentMeta(type)
setSortableItems((prev) => {
const currentValues = extractValues(prev);
let nextValues = [...currentValues];
const currentValues = extractValues(prev)
let nextValues = [...currentValues]
if (meta?.exclusiveGroup) {
nextValues = nextValues.filter((value) => {
const itemMeta = getSegmentMeta(value);
return itemMeta?.exclusiveGroup !== meta.exclusiveGroup;
});
const itemMeta = getSegmentMeta(value)
return itemMeta?.exclusiveGroup !== meta.exclusiveGroup
})
}
const allowsMultiple = meta?.allowMultiple !== false;
const allowsMultiple = meta?.allowMultiple !== false
if (!allowsMultiple && nextValues.includes(type)) {
const filtered = nextValues.filter((value) => value !== type);
const sanitized = sanitizeStructureList(filtered, SANITIZE_OPTIONS);
return buildSortableItems(sanitized);
const filtered = nextValues.filter((value) => value !== type)
const sanitized = sanitizeStructureList(filtered, SANITIZE_OPTIONS)
return buildSortableItems(sanitized)
}
nextValues.push(type);
const sanitized = sanitizeStructureList(nextValues, SANITIZE_OPTIONS);
return buildSortableItems(sanitized);
});
nextValues.push(type)
const sanitized = sanitizeStructureList(nextValues, SANITIZE_OPTIONS)
return buildSortableItems(sanitized)
})
},
[extractValues]
);
)
const Row = ({ item: rowData, onPress }) => {
const baseType = rowData?.type || rowData?.value;
const meta = getSegmentMeta(baseType);
const canRemove = rowData?.sortable && meta?.optional === true;
const selected = rowData?.selected;
const count = rowData?.count ?? 0;
const containerStyle = StyleSheet.flatten([
styles.rowContainer,
]);
const blurStyle = StyleSheet.flatten([
styles.rowBlur,
selected ? styles.selectedBlur : null,
]);
const baseType = rowData?.type || rowData?.value
const meta = getSegmentMeta(baseType)
const canRemove = rowData?.sortable && meta?.optional === true
const selected = rowData?.selected
const count = rowData?.count ?? 0
const containerStyle = StyleSheet.flatten([styles.rowContainer])
const blurStyle = StyleSheet.flatten([styles.rowBlur, selected ? styles.selectedBlur : null])
const badgeStyle = StyleSheet.flatten([
styles.countBadge,
count > 0 ? styles.countBadgeActive : null,
]);
])
return (
<CreateLyricsHeader
@@ -168,7 +150,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
blurViewStyle={blurStyle}
>
<View style={styles.rowContent}>
<Text style={styles.rowText}>{rowData?.label || ""}</Text>
<Text style={styles.rowText}>{rowData?.label || ''}</Text>
{rowData?.sortable ? (
<View style={styles.rowActions}>
<Sortable.Handle>
@@ -191,8 +173,8 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
)}
</View>
</CreateLyricsHeader>
);
};
)
}
return (
<View style={{ flex: 1, gap: 10, marginTop: 16 }}>
@@ -209,11 +191,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
<View style={styles.sectionTag}>
<Text style={styles.sectionTagText}>Structure actuelle</Text>
</View>
<ItemContainer
height="auto"
disableKeyboardHeight
style={styles.blurSection}
>
<ItemContainer height="auto" disableKeyboardHeight style={styles.blurSection}>
<Sortable.Grid
columns={1}
rowGap={12}
@@ -222,30 +200,23 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
customHandle
scrollableRef={scrollRef}
onDragEnd={({ data: newData }) => {
const values = extractValues(newData);
const sanitized = sanitizeStructureList(
values,
SANITIZE_OPTIONS
);
setSortableItems(buildSortableItems(sanitized));
const values = extractValues(newData)
const sanitized = sanitizeStructureList(values, SANITIZE_OPTIONS)
setSortableItems(buildSortableItems(sanitized))
}}
/>
</ItemContainer>
<View style={[styles.sectionTag, { marginTop: 24 }]}>
<Text style={styles.sectionTagText}>Éléments à ajouter</Text>
</View>
<ItemContainer
height="auto"
disableKeyboardHeight
style={styles.blurSection}
>
<ItemContainer height="auto" disableKeyboardHeight style={styles.blurSection}>
<FlatList
data={optionalSegments}
gap={12}
renderItem={({ item, index }) => {
const count = segmentCounts[item.type] || 0;
const selected = count > 0;
const wrapperStyle = index === 0 ? null : styles.optionWrapper;
const count = segmentCounts[item.type] || 0
const selected = count > 0
const wrapperStyle = index === 0 ? null : styles.optionWrapper
return (
<View style={wrapperStyle}>
<Row
@@ -258,7 +229,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
onPress={() => handleOptionalSegmentPress(item.type)}
/>
</View>
);
)
}}
keyExtractor={(item) => item.type}
contentContainerStyle={{ paddingBottom: 24 }}
@@ -269,35 +240,35 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
</Animated.ScrollView>
</View>
</View>
);
};
)
}
export default CustomizeSongStructure;
export default CustomizeSongStructure
const styles = StyleSheet.create({
rowContainer: {
borderRadius: 14,
width: "100%",
width: '100%',
},
blurSection: {
marginTop: 12,
width: "100%",
width: '100%',
},
rowBlur: {
minHeight: 54,
paddingHorizontal: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
selectedBlur: {
backgroundColor: Palette.transparentWhite,
},
rowContent: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
rowText: {
fontSize: 16,
@@ -315,9 +286,9 @@ const styles = StyleSheet.create({
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 999,
backgroundColor: "rgba(255,255,255,0.08)",
alignItems: "center",
justifyContent: "center",
backgroundColor: 'rgba(255,255,255,0.08)',
alignItems: 'center',
justifyContent: 'center',
},
countBadgeActive: {
backgroundColor: Palette.transparentWhite,
@@ -328,13 +299,13 @@ const styles = StyleSheet.create({
fontFamily: FONT_FAMILY.InterMedium,
},
rowActions: {
flexDirection: "row",
alignItems: "center",
flexDirection: 'row',
alignItems: 'center',
},
removeButton: {
padding: 6,
borderRadius: 999,
backgroundColor: "rgba(255,255,255,0.08)",
backgroundColor: 'rgba(255,255,255,0.08)',
},
removeIcon: {
width: 16,
@@ -345,11 +316,11 @@ const styles = StyleSheet.create({
marginTop: 12,
},
sectionTag: {
alignSelf: "flex-start",
alignSelf: 'flex-start',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 999,
backgroundColor: "rgba(255,255,255,0.12)",
backgroundColor: 'rgba(255,255,255,0.12)',
marginBottom: 16,
},
sectionTagText: {
@@ -357,6 +328,6 @@ const styles = StyleSheet.create({
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
letterSpacing: 1,
textTransform: "uppercase",
textTransform: 'uppercase',
},
});
})