import React, {useState, useEffect} from 'react'; import {useNavigate} from 'react-router-dom'; import FormatCard from './FormatCard'; import FormatModal from './FormatModal'; import {getGitStatus} from '@api/api'; import {CustomFormats} from '@api/data'; import {Loader} from 'lucide-react'; import Alert from '@ui/Alert'; import {useFormatModal} from '@hooks/useFormatModal'; import {useMassSelection} from '@hooks/useMassSelection'; import {useKeyboardShortcut} from '@hooks/useKeyboardShortcut'; import MassActionsBar from '@ui/MassActionsBar'; import ImportModal from '@ui/ImportModal'; import {importFormats} from '@api/import'; import DataBar from '@ui/DataBar/DataBar'; import useSearch from '@hooks/useSearch'; const loadingMessages = [ 'Formatting the formatters...', 'Teaching formats to behave...', 'Convincing formats to follow rules...', 'Organizing chaos into patterns...', 'Making formats look pretty...', 'Polishing the quality filters...' ]; const LoadingState = () => (

{ loadingMessages[ Math.floor(Math.random() * loadingMessages.length) ] }

); const ConflictState = ({onNavigateSettings}) => (

Merge Conflicts Detected

What Happened?

This page is locked because there are unresolved merge conflicts. You need to address these conflicts in the settings page before continuing.

); function FormatPage() { const [formats, setFormats] = useState([]); const [isModalOpen, setIsModalOpen] = useState(false); const [selectedFormat, setSelectedFormat] = useState(null); const [allTags, setAllTags] = useState([]); const [isCloning, setIsCloning] = useState(false); const [isLoading, setIsLoading] = useState(true); const [mergeConflicts, setMergeConflicts] = useState([]); const [isImportModalOpen, setIsImportModalOpen] = useState(false); const [willBeSelected, setWillBeSelected] = useState([]); const navigate = useNavigate(); // Initialize useSearch hook const { searchTerms, currentInput, setCurrentInput, addSearchTerm, removeSearchTerm, clearSearchTerms, filterType, setFilterType, filterValue, setFilterValue, sortBy, setSortBy, items: filteredFormats } = useSearch(formats, { searchableFields: ['content.name', 'content.tags'], initialSortBy: 'name', sortOptions: { name: (a, b) => a.content.name.localeCompare(b.content.name), dateModified: (a, b) => new Date(b.modified_date) - new Date(a.modified_date) } }); const { selectedItems, isSelectionMode, toggleSelectionMode, handleSelect, clearSelection, lastSelectedIndex } = useMassSelection(); useKeyboardShortcut('m', toggleSelectionMode, {ctrl: true}); const { name, description, tags, conditions, tests, error, activeTab, isDeleting, isRunningTests, includeInRename, setName, setDescription, setTags, setConditions, setTests, setActiveTab, setIsDeleting, setIncludeInRename, initializeForm, handleSave, handleRunTests, handleDelete } = useFormatModal(selectedFormat, () => { fetchFormats(); handleCloseModal(); }); useEffect(() => { fetchGitStatus(); }, []); useEffect(() => { const handleKeyDown = e => { if (e.key === 'Shift' && lastSelectedIndex !== null) { const element = document.elementFromPoint( window.mouseX, window.mouseY ); if (element) { const card = element.closest('[data-format-index]'); if (card) { const index = parseInt(card.dataset.formatIndex); handleMouseEnter(index, true); } } } }; const handleKeyUp = e => { if (e.key === 'Shift') { setWillBeSelected([]); } }; const handleMouseMove = e => { window.mouseX = e.clientX; window.mouseY = e.clientY; }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); window.addEventListener('mousemove', handleMouseMove); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); window.removeEventListener('mousemove', handleMouseMove); }; }, [lastSelectedIndex]); const fetchGitStatus = async () => { try { const result = await getGitStatus(); if (result.success) { setMergeConflicts(result.data.merge_conflicts || []); if (result.data.merge_conflicts.length === 0) { fetchFormats(); } else { setIsLoading(false); } } } catch (error) { console.error('Error fetching Git status:', error); Alert.error('Failed to check repository status'); setIsLoading(false); } }; const fetchFormats = async () => { try { const response = await CustomFormats.getAll(); const formatsData = response.map(item => ({ file_name: item.file_name, modified_date: item.modified_date, content: { ...item.content } })); setFormats(formatsData); const tags = new Set( formatsData.flatMap(format => format.content.tags || []) ); setAllTags(Array.from(tags)); } catch (error) { console.error('Error fetching formats:', error); Alert.error('Failed to load custom formats'); } finally { setIsLoading(false); } }; const handleOpenModal = (format = null) => { if (isSelectionMode) return; setSelectedFormat(format); setIsModalOpen(true); setIsCloning(false); initializeForm(format?.content, false); }; const handleCloseModal = () => { setSelectedFormat(null); setIsModalOpen(false); setIsCloning(false); }; const handleCloneFormat = format => { if (isSelectionMode) return; const clonedFormat = { ...format, content: { ...format.content, name: `${format.content.name} [COPY]` } }; setSelectedFormat(clonedFormat); setIsModalOpen(true); setIsCloning(true); initializeForm(clonedFormat.content, true); }; const handleMassDelete = async () => { try { const selectedFormats = Array.from(selectedItems).map( index => filteredFormats[index] ); for (const format of selectedFormats) { await CustomFormats.delete( format.file_name.replace('.yml', '') ); } Alert.success('Selected formats deleted successfully'); } catch (error) { console.error('Error deleting formats:', error); Alert.error( error.response?.data?.error || 'Failed to delete selected formats' ); } finally { fetchFormats(); toggleSelectionMode(); clearSelection(); } }; const handleMassImport = async arrID => { try { const selectedFormats = Array.from(selectedItems).map( index => filteredFormats[index] ); const formatNames = selectedFormats.map(format => format.file_name); const result = await importFormats(arrID, formatNames); if (result.status === 'partial') { const { added, updated, failed } = result; Alert.partial( `Import partially successful:\n- ${added} added\n- ${updated} updated\n- ${failed} failed` ); } else { Alert.success('Formats imported successfully'); } toggleSelectionMode(); } catch (error) { console.error('Error importing formats:', error); Alert.error('Failed to import formats'); throw error; } }; const handleFormatSelect = (formatName, index, e) => { if (e.shiftKey) { handleMouseEnter(index, true); } handleSelect(formatName, index, e, filteredFormats); }; const handleMouseEnter = (index, isShiftKey) => { if (isShiftKey && lastSelectedIndex !== null) { const start = Math.min(lastSelectedIndex, index); const end = Math.max(lastSelectedIndex, index); const potentialSelection = filteredFormats .slice(start, end + 1) .map((format, idx) => idx + start); setWillBeSelected(potentialSelection); } }; if (isLoading) { return ; } if (mergeConflicts.length > 0) { return ( navigate('/settings')} /> ); } return (
handleOpenModal()} addButtonLabel='Add New Format' />
{filteredFormats.map((format, index) => (
handleMouseEnter(index, window.event?.shiftKey) } onMouseLeave={() => setWillBeSelected([])}> handleOpenModal(format)} onClone={handleCloneFormat} sortBy={sortBy} isSelectionMode={isSelectionMode} isSelected={selectedItems.has(index)} willBeSelected={willBeSelected.includes(index)} onSelect={e => handleFormatSelect( format.content.name, index, e ) } />
))}
{isSelectionMode && ( { toggleSelectionMode(); clearSelection(); }} onDelete={handleMassDelete} onImport={() => setIsImportModalOpen(true)} /> )} setIsImportModalOpen(false)} onImport={handleMassImport} type='Formats' />
); } export default FormatPage;