mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
feat: add loading and conflict states to RegexPage and FormatPage components
This commit is contained in:
@@ -14,6 +14,37 @@ import ImportModal from '@ui/ImportModal';
|
||||
import {importFormats} from '@api/import';
|
||||
import DataBar from '@ui/DataBar/DataBar';
|
||||
|
||||
const LoadingState = () => (
|
||||
<div className='w-full min-h-[70vh] flex flex-col items-center justify-center'>
|
||||
<Loader className='w-8 h-8 animate-spin text-blue-500 mb-4' />
|
||||
<p className='text-lg font-medium text-gray-300'>
|
||||
Loading custom formats...
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ConflictState = ({onNavigateSettings}) => (
|
||||
<div className='w-full'>
|
||||
<div className='mt-8 flex justify-between items-center'>
|
||||
<h4 className='text-xl font-extrabold'>Merge Conflicts Detected</h4>
|
||||
<button
|
||||
onClick={onNavigateSettings}
|
||||
className='bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded transition'>
|
||||
Resolve Conflicts
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 p-4 bg-gray-800 rounded-lg shadow-md'>
|
||||
<h3 className='text-xl font-semibold'>What Happened?</h3>
|
||||
<p className='mt-2 text-gray-300'>
|
||||
This page is locked because there are unresolved merge
|
||||
conflicts. You need to address these conflicts in the settings
|
||||
page before continuing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
function FormatPage() {
|
||||
const [formats, setFormats] = useState([]);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
@@ -31,7 +62,6 @@ function FormatPage() {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Mass selection hook
|
||||
const {
|
||||
selectedItems,
|
||||
isSelectionMode,
|
||||
@@ -41,10 +71,8 @@ function FormatPage() {
|
||||
lastSelectedIndex
|
||||
} = useMassSelection();
|
||||
|
||||
// Setup keyboard shortcut for selection mode (Ctrl+A)
|
||||
useKeyboardShortcut('a', toggleSelectionMode, {ctrl: true});
|
||||
|
||||
// Format modal hook integration
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
@@ -144,7 +172,6 @@ function FormatPage() {
|
||||
}));
|
||||
setFormats(formatsData);
|
||||
|
||||
// Extract all unique tags
|
||||
const tags = new Set(
|
||||
formatsData.flatMap(format => format.content.tags || [])
|
||||
);
|
||||
@@ -207,14 +234,10 @@ function FormatPage() {
|
||||
|
||||
const handleMassImport = async arr => {
|
||||
try {
|
||||
// Get the filtered and sorted formats that were displayed during selection
|
||||
const filteredFormats = getFilteredAndSortedFormats();
|
||||
|
||||
// Convert selected indexes to format file names using the filtered list
|
||||
const selectedFormats = Array.from(selectedItems).map(
|
||||
index => filteredFormats[index]
|
||||
);
|
||||
|
||||
const formatNames = selectedFormats.map(format => format.file_name);
|
||||
|
||||
await importFormats(arr, formatNames);
|
||||
@@ -229,7 +252,6 @@ function FormatPage() {
|
||||
|
||||
const handleFormatSelect = (formatName, index, e) => {
|
||||
if (e.shiftKey) {
|
||||
// Immediately show selection preview
|
||||
handleMouseEnter(index, true);
|
||||
}
|
||||
handleSelect(formatName, index, e, getFilteredAndSortedFormats());
|
||||
@@ -283,46 +305,17 @@ function FormatPage() {
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center h-64'>
|
||||
<Loader className='w-8 h-8 animate-spin text-blue-500 mb-4' />
|
||||
<p className='text-lg font-medium text-gray-700 dark:text-gray-300'>
|
||||
Loading custom formats...
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return <LoadingState />;
|
||||
}
|
||||
|
||||
const hasConflicts = mergeConflicts.length > 0;
|
||||
|
||||
if (hasConflicts) {
|
||||
if (mergeConflicts.length > 0) {
|
||||
return (
|
||||
<div className='text-gray-900 dark:text-white'>
|
||||
<div className='mt-8 flex justify-between items-center'>
|
||||
<h4 className='text-xl font-extrabold'>
|
||||
Merge Conflicts Detected
|
||||
</h4>
|
||||
<button
|
||||
onClick={() => navigate('/settings')}
|
||||
className='bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded transition'>
|
||||
Resolve Conflicts
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg shadow-md'>
|
||||
<h3 className='text-xl font-semibold'>What Happened?</h3>
|
||||
<p className='mt-2 text-gray-600 dark:text-gray-300'>
|
||||
This page is locked because there are unresolved merge
|
||||
conflicts. You need to address these conflicts in the
|
||||
settings page before continuing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ConflictState onNavigateSettings={() => navigate('/settings')} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='w-full min-h-[70vh] space-y-2 flex flex-col'>
|
||||
<DataBar
|
||||
onSearch={setSearchQuery}
|
||||
searchPlaceholder='Search by name or tag...'
|
||||
@@ -339,7 +332,7 @@ function FormatPage() {
|
||||
addButtonLabel='Add New Format'
|
||||
/>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4 h-full'>
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 flex-grow'>
|
||||
{getFilteredAndSortedFormats().map((format, index) => (
|
||||
<div
|
||||
key={format.file_name}
|
||||
|
||||
@@ -13,6 +13,45 @@ import ImportModal from '@ui/ImportModal';
|
||||
import {importProfiles} from '@api/import';
|
||||
import DataBar from '@ui/DataBar/DataBar';
|
||||
|
||||
const LoadingState = () => (
|
||||
<div className='w-full min-h-[70vh] flex flex-col items-center justify-center'>
|
||||
<Loader size={48} className='animate-spin text-blue-500 mb-4' />
|
||||
<p className='text-lg font-medium text-gray-300'>
|
||||
{
|
||||
[
|
||||
'Profiling your media collection...',
|
||||
'Organizing your digital hoard...',
|
||||
'Calibrating the flux capacitor...',
|
||||
'Synchronizing with the movie matrix...',
|
||||
'Optimizing your binge-watching potential...'
|
||||
][Math.floor(Math.random() * 5)]
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ConflictState = ({onNavigateSettings}) => (
|
||||
<div className='w-full'>
|
||||
<div className='mt-8 flex justify-between items-center'>
|
||||
<h4 className='text-xl font-extrabold'>Merge Conflicts Detected</h4>
|
||||
<button
|
||||
onClick={onNavigateSettings}
|
||||
className='bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded transition'>
|
||||
Resolve Conflicts
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 p-4 bg-gray-800 rounded-lg shadow-md'>
|
||||
<h3 className='text-xl font-semibold'>What Happened?</h3>
|
||||
<p className='mt-2 text-gray-300'>
|
||||
This page is locked because there are unresolved merge
|
||||
conflicts. You need to address these conflicts in the settings
|
||||
page before continuing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
function ProfilePage() {
|
||||
const [profiles, setProfiles] = useState([]);
|
||||
const [formats, setFormats] = useState([]);
|
||||
@@ -31,7 +70,6 @@ function ProfilePage() {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Mass selection hook
|
||||
const {
|
||||
selectedItems,
|
||||
isSelectionMode,
|
||||
@@ -41,22 +79,12 @@ function ProfilePage() {
|
||||
lastSelectedIndex
|
||||
} = useMassSelection();
|
||||
|
||||
// Setup keyboard shortcut for selection mode (Ctrl+A)
|
||||
useKeyboardShortcut('a', toggleSelectionMode, {ctrl: true});
|
||||
|
||||
const loadingMessages = [
|
||||
'Profiling your media collection...',
|
||||
'Organizing your digital hoard...',
|
||||
'Calibrating the flux capacitor...',
|
||||
'Synchronizing with the movie matrix...',
|
||||
'Optimizing your binge-watching potential...'
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
fetchGitStatus();
|
||||
}, []);
|
||||
|
||||
// Add shift-key selection handling
|
||||
useEffect(() => {
|
||||
const handleKeyDown = e => {
|
||||
if (e.key === 'Shift' && lastSelectedIndex !== null) {
|
||||
@@ -128,6 +156,7 @@ function ProfilePage() {
|
||||
}
|
||||
}));
|
||||
setProfiles(profilesData);
|
||||
|
||||
const tags = [
|
||||
...new Set(
|
||||
profilesData.flatMap(profile => profile.content.tags || [])
|
||||
@@ -214,10 +243,9 @@ function ProfilePage() {
|
||||
|
||||
const handleMassImport = async arr => {
|
||||
try {
|
||||
// Get array of indexes from selectedItems
|
||||
const selectedProfilesList = Array.from(selectedItems)
|
||||
.map(index => profiles[index])
|
||||
.filter(profile => profile); // Filter out any undefined entries
|
||||
.filter(profile => profile);
|
||||
|
||||
if (selectedProfilesList.length === 0) {
|
||||
Alert.error('No valid profiles selected for import');
|
||||
@@ -239,7 +267,6 @@ function ProfilePage() {
|
||||
|
||||
const handleProfileSelect = (profileName, index, e) => {
|
||||
if (e.shiftKey) {
|
||||
// Immediately show selection preview
|
||||
handleMouseEnter(index, true);
|
||||
}
|
||||
handleSelect(profileName, index, e, getFilteredAndSortedProfiles());
|
||||
@@ -261,7 +288,6 @@ function ProfilePage() {
|
||||
const getFilteredAndSortedProfiles = () => {
|
||||
return profiles
|
||||
.filter(profile => {
|
||||
// Apply search filter
|
||||
if (searchQuery) {
|
||||
return (
|
||||
profile.content.name
|
||||
@@ -275,13 +301,10 @@ function ProfilePage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Apply existing filters
|
||||
if (filterType === 'tag') {
|
||||
return (
|
||||
profile.content.tags &&
|
||||
profile.content.tags.includes(filterValue)
|
||||
);
|
||||
return profile.content.tags?.includes(filterValue);
|
||||
}
|
||||
|
||||
if (filterType === 'date') {
|
||||
const profileDate = new Date(profile.modified_date);
|
||||
const filterDate = new Date(filterValue);
|
||||
@@ -289,6 +312,7 @@ function ProfilePage() {
|
||||
profileDate.toDateString() === filterDate.toDateString()
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
@@ -314,52 +338,19 @@ function ProfilePage() {
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center h-screen'>
|
||||
<Loader size={48} className='animate-spin text-blue-500 mb-4' />
|
||||
<p className='text-lg font-medium text-gray-700 dark:text-gray-300'>
|
||||
{
|
||||
loadingMessages[
|
||||
Math.floor(Math.random() * loadingMessages.length)
|
||||
]
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return <LoadingState />;
|
||||
}
|
||||
|
||||
const hasConflicts = mergeConflicts.length > 0;
|
||||
|
||||
if (hasConflicts) {
|
||||
if (mergeConflicts.length > 0) {
|
||||
return (
|
||||
<div className='bg-gray-900 text-white'>
|
||||
<div className='mt-8 flex justify-between items-center'>
|
||||
<h4 className='text-xl font-extrabold'>
|
||||
Merge Conflicts Detected
|
||||
</h4>
|
||||
<button
|
||||
onClick={() => navigate('/settings')}
|
||||
className='bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded transition'>
|
||||
Resolve Conflicts
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 p-4 bg-gray-800 rounded-lg shadow-md'>
|
||||
<h3 className='text-xl font-semibold'>What Happened?</h3>
|
||||
<p className='mt-2 text-gray-300'>
|
||||
This page is locked because there are unresolved merge
|
||||
conflicts. You need to address these conflicts in the
|
||||
settings page before continuing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ConflictState onNavigateSettings={() => navigate('/settings')} />
|
||||
);
|
||||
}
|
||||
|
||||
const filteredProfiles = getFilteredAndSortedProfiles();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='w-full space-y-2'>
|
||||
<DataBar
|
||||
onSearch={setSearchQuery}
|
||||
searchPlaceholder='Search by name or tag...'
|
||||
@@ -376,7 +367,7 @@ function ProfilePage() {
|
||||
addButtonLabel='Add New Profile'
|
||||
/>
|
||||
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4 h-full'>
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4'>
|
||||
{filteredProfiles.map((profile, index) => (
|
||||
<div
|
||||
key={profile.file_name}
|
||||
|
||||
@@ -9,6 +9,15 @@ import {useKeyboardShortcut} from '@hooks/useKeyboardShortcut';
|
||||
import MassActionsBar from '@ui/MassActionsBar';
|
||||
import DataBar from '@ui/DataBar/DataBar';
|
||||
|
||||
const LoadingState = () => (
|
||||
<div className='w-full min-h-[70vh] flex flex-col items-center justify-center'>
|
||||
<Loader className='w-8 h-8 animate-spin text-blue-500 mb-4' />
|
||||
<p className='text-lg font-medium text-gray-300'>
|
||||
Loading regex patterns...
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
function RegexPage() {
|
||||
const [patterns, setPatterns] = useState([]);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
@@ -22,7 +31,6 @@ function RegexPage() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [willBeSelected, setWillBeSelected] = useState([]);
|
||||
|
||||
// Mass selection hook
|
||||
const {
|
||||
selectedItems,
|
||||
isSelectionMode,
|
||||
@@ -32,7 +40,6 @@ function RegexPage() {
|
||||
lastSelectedIndex
|
||||
} = useMassSelection();
|
||||
|
||||
// Keyboard shortcut for selection mode
|
||||
useKeyboardShortcut('a', toggleSelectionMode, {ctrl: true});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -90,7 +97,6 @@ function RegexPage() {
|
||||
}));
|
||||
setPatterns(patternsData);
|
||||
|
||||
// Extract all unique tags
|
||||
const tags = new Set();
|
||||
patternsData.forEach(pattern => {
|
||||
pattern.tags?.forEach(tag => tags.add(tag));
|
||||
@@ -157,7 +163,6 @@ function RegexPage() {
|
||||
|
||||
const handlePatternSelect = (patternName, index, e) => {
|
||||
if (e.shiftKey) {
|
||||
// Immediately show selection preview
|
||||
handleMouseEnter(index, true);
|
||||
}
|
||||
handleSelect(patternName, index, e, getFilteredAndSortedPatterns());
|
||||
@@ -215,15 +220,11 @@ function RegexPage() {
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='flex justify-center items-center h-64'>
|
||||
<Loader className='w-8 h-8 animate-spin text-blue-500' />
|
||||
</div>
|
||||
);
|
||||
return <LoadingState />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='w-full space-y-2'>
|
||||
<DataBar
|
||||
onSearch={setSearchQuery}
|
||||
searchPlaceholder='Search by name or tag...'
|
||||
@@ -240,7 +241,7 @@ function RegexPage() {
|
||||
addButtonLabel='Add New Pattern'
|
||||
/>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4 h-full'>
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4'>
|
||||
{getFilteredAndSortedPatterns().map((pattern, index) => (
|
||||
<div
|
||||
key={pattern.name}
|
||||
|
||||
Reference in New Issue
Block a user