feat: add loading and conflict states to RegexPage and FormatPage components

This commit is contained in:
Sam Chau
2025-01-09 17:28:35 +10:30
parent 92336276a0
commit 38a978146f
3 changed files with 97 additions and 112 deletions

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}