diff --git a/code/websites/pokedex.online/src/views/GamemasterExplorer.vue b/code/websites/pokedex.online/src/views/GamemasterExplorer.vue index f8d34fa..1b73887 100644 --- a/code/websites/pokedex.online/src/views/GamemasterExplorer.vue +++ b/code/websites/pokedex.online/src/views/GamemasterExplorer.vue @@ -9,21 +9,20 @@

{{ error }}

- +

No Gamemaster Files Available

Please process gamemaster data first in the Gamemaster Manager.

- Go to Gamemaster Manager + + Go to Gamemaster Manager +
-
← Back Home @@ -48,297 +47,195 @@
-

Keyboard Shortcuts

+
- - 0, - Math.min(lineIndex + 1000, fileLines.value.length) - ); - displayLines.value = newLinesToDisplay.map((content, index) => ({ - lineNumber: index + 1, - content, - hasMatch: searchResults.value.includes(index) - })); +const searchState = useGamemasterSearch(fileLines, displayLines); +const { searchResults, currentResultIndex } = searchState; +const selectionState = useLineSelection(displayLines, fileContent, selectedFile); +const filterState = useJsonFilter(); + +const showHelp = ref(false); +const showSettings = ref(false); +const operationProgress = ref({ + active: false, + percent: 0, + message: '', + complete: false +}); + +const loading = computed(() => isLoading.value); +const error = computed(() => fileError.value); + +const lineHeight = computed(() => (globalThis.innerWidth < 768 ? 24 : 20)); +const highlightConfig = computed(() => ({ + theme: preferences.value.darkMode ? 'github-dark' : 'github', + language: 'json' +})); + +const filterData = computed(() => { + if (!fileContent.value) return []; + try { + const parsed = JSON.parse(fileContent.value); + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; } +}); - // Use virtual scroller API if available (for large files) - if (virtualScroller.value && displayLines.value.length > 1000) { - nextTick(() => { - // Calculate the scroll position to center the item - const scroller = virtualScroller.value; - const itemHeight = lineHeight.value; - const containerHeight = scroller.$el.clientHeight; +useUrlState({ + file: selectedFile, + search: searchState.searchQuery, + filter: filterState.filterProperty, + value: filterState.filterValue +}); - // Calculate scroll position to center the item - const targetScrollTop = - lineIndex * itemHeight - containerHeight / 2 + itemHeight / 2; +const clipboard = useClipboard(); - // Use the scroller's internal scrollTop - scroller.$el.scrollTop = Math.max(0, targetScrollTop); - }); - } else { - // Fallback for non-virtual scrolled content - const attemptScroll = (attempt = 0) => { - const lineElement = document.querySelector(`[data-line="${lineNumber}"]`); - - if (lineElement) { - // Scroll only within the container, not the whole page - const container = lineElement.closest('.scroller, .lines-container'); - if (container) { - // Get element's position relative to the container - const elementOffsetTop = lineElement.offsetTop; - const containerHeight = container.clientHeight; - const elementHeight = lineElement.offsetHeight; - - // Calculate scroll position to center element in container - const scrollTo = - elementOffsetTop - containerHeight / 2 + elementHeight / 2; - container.scrollTo({ top: scrollTo, behavior: 'smooth' }); - } - return true; - } else if (attempt < 3) { - // Virtual scroller may not have rendered yet, try again - setTimeout(() => attemptScroll(attempt + 1), 50); - return false; - } else { - // Fallback: scroll container to approximate position - const container = document.querySelector('.scroller, .lines-container'); - if (container) { - const estimatedScroll = - (lineIndex / fileLines.value.length) * - (container.scrollHeight - container.clientHeight); - container.scrollTop = estimatedScroll; - } - return false; - } - }; - - attemptScroll(); - } -} - -function applyHistoryItem(item) { - searchQuery.value = item; - onSearchInput(); -} - -function onFilterChange() { - // Implement property filtering - // This is complex - needs to parse JSON and filter based on property paths - console.log( - 'Filter change:', - filterProperty.value, - filterValue.value, - filterMode.value - ); -} - -function toggleLineSelection(lineNumber, event) { - if (event.shiftKey && selectedLines.value.size > 0) { - // Range selection - const lastSelected = Math.max(...selectedLines.value); - const start = Math.min(lastSelected, lineNumber); - const end = Math.max(lastSelected, lineNumber); - - for (let i = start; i <= end; i++) { - selectedLines.value.add(i); +useKeyboardShortcuts({ + 'ctrl+f': () => { + showHelp.value = false; + showSettings.value = false; + }, + 'ctrl+c': selectionState.copySelected, + 'ctrl+g': searchState.goToNextResult, + 'shift+ctrl+g': searchState.goToPrevResult, + escape: () => { + if (showHelp.value || showSettings.value) { + showHelp.value = false; + showSettings.value = false; + } else if (selectionState.selectedLines.value.size > 0) { + selectionState.selectedLines.value.clear(); + } else if (searchState.searchQuery.value) { + searchState.clearSearch(); } - } else if (event.ctrlKey || event.metaKey) { - // Toggle individual line - if (selectedLines.value.has(lineNumber)) { - selectedLines.value.delete(lineNumber); - } else { - selectedLines.value.add(lineNumber); - } - } else { - // Single selection - selectedLines.value.clear(); - selectedLines.value.add(lineNumber); } -} +}); -async function copySelected() { - if (selectedLines.value.size === 0) return; - - const lines = [...selectedLines.value].sort((a, b) => a - b); - const content = lines - .map(lineNum => { - return ( - displayLines.value.find(l => l.lineNumber === lineNum)?.content || '' - ); - }) - .join('\n'); - - await clipboard.copyToClipboard(content); -} - -async function copyAll() { - await clipboard.copyToClipboard(fileContent.value); -} - -function exportSelected() { - if (selectedLines.value.size === 0) return; - - const lines = [...selectedLines.value].sort((a, b) => a - b); - const content = lines - .map(lineNum => { - return ( - displayLines.value.find(l => l.lineNumber === lineNum)?.content || '' - ); - }) - .join('\n'); - - downloadFile(content, `${selectedFile.value}-selected-${Date.now()}.json`); -} - -function exportAll() { - downloadFile(fileContent.value, `${selectedFile.value}-${Date.now()}.json`); -} - -function downloadFile(content, filename) { - const blob = new Blob([content], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - a.remove(); - URL.revokeObjectURL(url); -} - -async function shareUrl() { - const url = globalThis.location.href; - await clipboard.copyToClipboard(url); -} - -function formatSize(bytes) { - if (!bytes) return '0 B'; - const k = 1024; - const sizes = ['B', 'KB', 'MB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; -} - -function getFileType(filename) { - if (filename.includes('AllForms') || filename.includes('allForms')) - return 'allForms'; - if (filename.includes('moves')) return 'moves'; - if (filename.includes('pokemon')) return 'pokemon'; - if (filename.includes('raw')) return 'raw'; - return ''; -} - -function formatFileName(filename) { - if (filename.includes('AllForms') || filename.includes('allForms')) - return 'Pokemon All Forms'; - if (filename.includes('moves')) return 'Moves'; - if (filename.includes('pokemon')) return 'Pokemon'; - if (filename.includes('raw')) return 'Raw Gamemaster'; - return filename; -} +watch(selectedFile, () => { + if (selectionState.selectedLines.value.size > 0) { + selectionState.clearSelection(); + } +}); + // Lifecycle onMounted(() => {