feat: fixes and formatter
This commit is contained in:
@@ -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',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user