feat: fixes and formatter
This commit is contained in:
@@ -1,32 +1,32 @@
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
TouchableOpacity,
|
||||
} from "react-native";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { View, Text, ScrollView, StyleSheet, Dimensions, TouchableOpacity } from 'react-native'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import Animated, {
|
||||
runOnJS,
|
||||
useAnimatedGestureHandler,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withSpring,
|
||||
} from "react-native-reanimated";
|
||||
import { PanGestureHandler } from "react-native-gesture-handler";
|
||||
} from 'react-native-reanimated'
|
||||
import { PanGestureHandler } from 'react-native-gesture-handler'
|
||||
|
||||
const { width } = Dimensions.get("window");
|
||||
const { width } = Dimensions.get('window')
|
||||
|
||||
// Child component to respect Rules of Hooks (no hooks in loops)
|
||||
const DraggableItem = ({ item, gestureHandler, draggingItem, translateX, translateY, onActivate }) => {
|
||||
const DraggableItem = ({
|
||||
item,
|
||||
gestureHandler,
|
||||
draggingItem,
|
||||
translateX,
|
||||
translateY,
|
||||
onActivate,
|
||||
}) => {
|
||||
const style = useAnimatedStyle(() => ({
|
||||
transform: [
|
||||
{ translateX: draggingItem.value === item.id ? translateX.value : 0 },
|
||||
{ translateY: draggingItem.value === item.id ? translateY.value : 0 },
|
||||
],
|
||||
zIndex: draggingItem.value === item.id ? 10 : 0,
|
||||
}));
|
||||
}))
|
||||
|
||||
return (
|
||||
<PanGestureHandler
|
||||
@@ -37,8 +37,8 @@ const DraggableItem = ({ item, gestureHandler, draggingItem, translateX, transla
|
||||
<Text style={styles.text}>{item.label ?? String(item)}</Text>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
// Props:
|
||||
// - sourceItems: array of { id, label, value } or strings
|
||||
@@ -47,88 +47,83 @@ const DraggableItem = ({ item, gestureHandler, draggingItem, translateX, transla
|
||||
const SongStructureDragDrop = ({ sourceItems, initialSelected, onChange }) => {
|
||||
const normalize = (arr = []) =>
|
||||
arr.map((item, idx) =>
|
||||
typeof item === "string"
|
||||
? { id: `${item}-${idx}`, label: item, value: item }
|
||||
: item,
|
||||
);
|
||||
typeof item === 'string' ? { id: `${item}-${idx}`, label: item, value: item } : item
|
||||
)
|
||||
|
||||
const normalizedSource = useMemo(
|
||||
() => normalize(sourceItems || []),
|
||||
[sourceItems],
|
||||
);
|
||||
const normalizedSource = useMemo(() => normalize(sourceItems || []), [sourceItems])
|
||||
|
||||
const [rightItems, setRightItems] = useState([]);
|
||||
const [rightItems, setRightItems] = useState([])
|
||||
|
||||
// Compare arrays by item id and order to avoid unnecessary state churn
|
||||
const sameById = (a = [], b = []) => {
|
||||
if (a === b) return true;
|
||||
if (!a || !b) return false;
|
||||
if (a.length !== b.length) return false;
|
||||
if (a === b) return true
|
||||
if (!a || !b) return false
|
||||
if (a.length !== b.length) return false
|
||||
for (let idx = 0; idx < a.length; idx++) {
|
||||
if (a[idx]?.id !== b[idx]?.id) return false;
|
||||
if (a[idx]?.id !== b[idx]?.id) return false
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return true
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const selected = Array.isArray(initialSelected) ? initialSelected : [];
|
||||
const selectedSet = new Set(selected);
|
||||
const nextRight = normalizedSource.filter((i) => selectedSet.has(i.id));
|
||||
setRightItems((prev) => (sameById(prev, nextRight) ? prev : nextRight));
|
||||
}, [normalizedSource, initialSelected]);
|
||||
const selected = Array.isArray(initialSelected) ? initialSelected : []
|
||||
const selectedSet = new Set(selected)
|
||||
const nextRight = normalizedSource.filter((i) => selectedSet.has(i.id))
|
||||
setRightItems((prev) => (sameById(prev, nextRight) ? prev : nextRight))
|
||||
}, [normalizedSource, initialSelected])
|
||||
|
||||
const leftItems = useMemo(() => {
|
||||
if (!rightItems || rightItems.length === 0) return normalizedSource;
|
||||
const rightIds = new Set(rightItems.map((item) => item.id));
|
||||
return normalizedSource.filter((item) => !rightIds.has(item.id));
|
||||
}, [normalizedSource, rightItems]);
|
||||
if (!rightItems || rightItems.length === 0) return normalizedSource
|
||||
const rightIds = new Set(rightItems.map((item) => item.id))
|
||||
return normalizedSource.filter((item) => !rightIds.has(item.id))
|
||||
}, [normalizedSource, rightItems])
|
||||
|
||||
const draggingItem = useSharedValue(null);
|
||||
const translateX = useSharedValue(0);
|
||||
const translateY = useSharedValue(0);
|
||||
const draggingItem = useSharedValue(null)
|
||||
const translateX = useSharedValue(0)
|
||||
const translateY = useSharedValue(0)
|
||||
|
||||
const onActivate = (id) => {
|
||||
draggingItem.value = id;
|
||||
};
|
||||
draggingItem.value = id
|
||||
}
|
||||
|
||||
const moveItem = (itemId) => {
|
||||
const item = normalizedSource.find((i) => i.id === itemId);
|
||||
if (!item) return;
|
||||
const item = normalizedSource.find((i) => i.id === itemId)
|
||||
if (!item) return
|
||||
setRightItems((current) => {
|
||||
if (current.some((i) => i.id === itemId)) return current;
|
||||
const next = [...current, item];
|
||||
onChange?.(next.map((i) => i.value ?? i.label));
|
||||
return next;
|
||||
});
|
||||
};
|
||||
if (current.some((i) => i.id === itemId)) return current
|
||||
const next = [...current, item]
|
||||
onChange?.(next.map((i) => i.value ?? i.label))
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const removeItem = (itemId) => {
|
||||
setRightItems((current) => {
|
||||
if (!current.some((i) => i.id === itemId)) return current;
|
||||
const next = current.filter((i) => i.id !== itemId);
|
||||
onChange?.(next.map((i) => i.value ?? i.label));
|
||||
return next;
|
||||
});
|
||||
};
|
||||
if (!current.some((i) => i.id === itemId)) return current
|
||||
const next = current.filter((i) => i.id !== itemId)
|
||||
onChange?.(next.map((i) => i.value ?? i.label))
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const gestureHandler = useAnimatedGestureHandler({
|
||||
onStart: (_, ctx) => {
|
||||
ctx.startX = translateX.value;
|
||||
ctx.startY = translateY.value;
|
||||
ctx.startX = translateX.value
|
||||
ctx.startY = translateY.value
|
||||
},
|
||||
onActive: (event, ctx) => {
|
||||
translateX.value = ctx.startX + event.translationX;
|
||||
translateY.value = ctx.startY + event.translationY;
|
||||
translateX.value = ctx.startX + event.translationX
|
||||
translateY.value = ctx.startY + event.translationY
|
||||
},
|
||||
onEnd: () => {
|
||||
if (translateX.value > width / 2) {
|
||||
runOnJS(moveItem)(draggingItem.value);
|
||||
runOnJS(moveItem)(draggingItem.value)
|
||||
}
|
||||
translateX.value = withSpring(0);
|
||||
translateY.value = withSpring(0);
|
||||
draggingItem.value = null;
|
||||
translateX.value = withSpring(0)
|
||||
translateY.value = withSpring(0)
|
||||
draggingItem.value = null
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
return (
|
||||
<View style={styles.scrollWrapper}>
|
||||
@@ -165,55 +160,55 @@ const SongStructureDragDrop = ({ sourceItems, initialSelected, onChange }) => {
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SongStructureDragDrop;
|
||||
export default SongStructureDragDrop
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollWrapper: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-around",
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
},
|
||||
scroll: {
|
||||
width: width / 2.2,
|
||||
height: "90%",
|
||||
backgroundColor: "#f0f0f0",
|
||||
height: '90%',
|
||||
backgroundColor: '#f0f0f0',
|
||||
borderRadius: 10,
|
||||
margin: 5,
|
||||
padding: 10,
|
||||
},
|
||||
title: {
|
||||
fontWeight: "bold",
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
marginBottom: 10,
|
||||
},
|
||||
item: {
|
||||
backgroundColor: "#aaf",
|
||||
backgroundColor: '#aaf',
|
||||
padding: 15,
|
||||
marginVertical: 5,
|
||||
borderRadius: 10,
|
||||
},
|
||||
selectedItem: {
|
||||
backgroundColor: "#bdf",
|
||||
backgroundColor: '#bdf',
|
||||
padding: 15,
|
||||
marginVertical: 5,
|
||||
borderRadius: 10,
|
||||
alignItems: "center",
|
||||
alignItems: 'center',
|
||||
},
|
||||
helpText: {
|
||||
fontSize: 12,
|
||||
color: "#666",
|
||||
color: '#666',
|
||||
marginBottom: 8,
|
||||
textAlign: "center",
|
||||
textAlign: 'center',
|
||||
},
|
||||
removeHint: {
|
||||
fontSize: 12,
|
||||
color: "#555",
|
||||
color: '#555',
|
||||
marginTop: 4,
|
||||
},
|
||||
text: {
|
||||
color: "#333",
|
||||
textAlign: "center",
|
||||
color: '#333',
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user