298 lines
7.7 KiB
JavaScript
298 lines
7.7 KiB
JavaScript
import { ref, computed, watch } from 'vue';
|
|
import { perfMonitor } from '@/utilities/performance-utils.js';
|
|
import {
|
|
extractJsonPaths,
|
|
extractJsonPathsLazy
|
|
} from '@/utilities/json-utils.js';
|
|
import { useLocalStorage } from '@/composables/useLocalStorage.js';
|
|
|
|
/**
|
|
* useGamemasterFiles - Composable for managing gamemaster file operations
|
|
*
|
|
* Handles:
|
|
* - File selection and loading
|
|
* - File parsing and content management
|
|
* - JSON path extraction for filtering
|
|
* - Display line management (with pagination for large files)
|
|
* - File size validation
|
|
* - Preference persistence
|
|
*
|
|
* @param {Object} client - GamemasterClient instance
|
|
* @returns {Object} Files composable API
|
|
*/
|
|
export function useGamemasterFiles(client) {
|
|
// File state
|
|
const selectedFile = ref('');
|
|
const fileContent = ref('');
|
|
const fileLines = ref([]);
|
|
const displayLines = ref([]);
|
|
const jsonPaths = ref([]);
|
|
const isLoading = ref(false);
|
|
const fileError = ref(null);
|
|
|
|
// Status state
|
|
const status = ref({});
|
|
|
|
// Preferences (persisted)
|
|
const preferences = useLocalStorage('gamemaster-explorer-prefs', {
|
|
darkMode: false,
|
|
lineWrap: false,
|
|
showLineNumbers: true,
|
|
performanceMode: 'auto',
|
|
lastFile: ''
|
|
});
|
|
|
|
// Display configuration
|
|
const LINES_TO_DISPLAY = 10000; // Initially display 10K lines
|
|
const MAX_RAW_FILE_SIZE = 50 * 1024 * 1024; // 50MB
|
|
|
|
/**
|
|
* Get available files from status
|
|
*/
|
|
const availableFiles = computed(() => status.value.available || []);
|
|
|
|
/**
|
|
* Get unique file list, sorted by type
|
|
*/
|
|
const uniqueFiles = computed(() => {
|
|
const seen = new Set();
|
|
const order = { pokemon: 1, allForms: 2, moves: 3, raw: 4 };
|
|
|
|
return availableFiles.value
|
|
.filter(file => {
|
|
const type = getFileType(file.filename);
|
|
if (seen.has(type)) return false;
|
|
seen.add(type);
|
|
return true;
|
|
})
|
|
.sort((a, b) => {
|
|
const typeA = getFileType(a.filename);
|
|
const typeB = getFileType(b.filename);
|
|
return (order[typeA] ?? 999) - (order[typeB] ?? 999);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Check if there are any files available
|
|
*/
|
|
const hasFiles = computed(() => availableFiles.value.length > 0);
|
|
|
|
/**
|
|
* Check if currently displayed file exceeds line limit
|
|
*/
|
|
const fileTooLarge = computed(() => {
|
|
return fileLines.value.length > LINES_TO_DISPLAY;
|
|
});
|
|
|
|
/**
|
|
* Get file size in human-readable format
|
|
*/
|
|
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];
|
|
}
|
|
|
|
/**
|
|
* Determine file type from filename
|
|
*/
|
|
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 '';
|
|
}
|
|
|
|
/**
|
|
* Format filename for display
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Load server status (file list)
|
|
*/
|
|
async function loadStatus() {
|
|
try {
|
|
isLoading.value = true;
|
|
fileError.value = null;
|
|
|
|
status.value = await perfMonitor('Load Status', async () => {
|
|
return await client.getStatus();
|
|
});
|
|
|
|
// Auto-load last file if set
|
|
if (preferences.value.lastFile && !selectedFile.value) {
|
|
selectedFile.value = preferences.value.lastFile;
|
|
await loadFile();
|
|
}
|
|
} catch (err) {
|
|
fileError.value = err.message;
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load selected file from server
|
|
*/
|
|
async function loadFile() {
|
|
if (!selectedFile.value) return;
|
|
|
|
try {
|
|
isLoading.value = true;
|
|
fileError.value = null;
|
|
|
|
// Validate raw file size
|
|
if (selectedFile.value === 'raw') {
|
|
const rawFile = status.value.available?.find(f =>
|
|
f.filename.includes('raw')
|
|
);
|
|
if (rawFile && rawFile.size > MAX_RAW_FILE_SIZE) {
|
|
fileError.value =
|
|
'⚠️ Raw gamemaster file is very large (' +
|
|
formatSize(rawFile.size) +
|
|
'). It may be slow to load. Try a specific file type instead (Pokemon, All Forms, or Moves).';
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Fetch file content based on type
|
|
let data;
|
|
switch (selectedFile.value) {
|
|
case 'pokemon':
|
|
data = await perfMonitor('Load Pokemon', () => client.getPokemon());
|
|
break;
|
|
case 'allForms':
|
|
data = await perfMonitor('Load All Forms', () =>
|
|
client.getAllForms()
|
|
);
|
|
break;
|
|
case 'moves':
|
|
data = await perfMonitor('Load Moves', () => client.getMoves());
|
|
break;
|
|
case 'raw':
|
|
data = await perfMonitor('Load Raw', () => client.getRaw());
|
|
break;
|
|
default:
|
|
throw new Error('Unknown file type');
|
|
}
|
|
|
|
// Process file content
|
|
fileContent.value = JSON.stringify(data, null, 2);
|
|
fileLines.value = fileContent.value.split('\n');
|
|
|
|
// Display limited lines initially (10K) but keep full content for searching
|
|
const linesToDisplay = fileLines.value.slice(0, LINES_TO_DISPLAY);
|
|
displayLines.value = linesToDisplay.map((content, index) => ({
|
|
lineNumber: index + 1,
|
|
content,
|
|
hasMatch: false
|
|
}));
|
|
|
|
// Extract JSON paths for filtering (in background)
|
|
jsonPaths.value = extractJsonPaths(data);
|
|
extractJsonPathsLazy(data, paths => {
|
|
jsonPaths.value = paths;
|
|
});
|
|
|
|
// Save preference
|
|
preferences.value.lastFile = selectedFile.value;
|
|
|
|
isLoading.value = false;
|
|
} catch (err) {
|
|
fileError.value = err.message;
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear file selection and related state
|
|
*/
|
|
function clearFileSelection() {
|
|
selectedFile.value = '';
|
|
fileContent.value = '';
|
|
fileLines.value = [];
|
|
displayLines.value = [];
|
|
jsonPaths.value = [];
|
|
fileError.value = null;
|
|
}
|
|
|
|
/**
|
|
* Update displayed lines (for pagination/virtual scrolling)
|
|
*/
|
|
function updateDisplayLines(startIndex = 0, endIndex = LINES_TO_DISPLAY) {
|
|
const linesToDisplay = fileLines.value.slice(startIndex, endIndex);
|
|
displayLines.value = linesToDisplay.map((content, index) => ({
|
|
lineNumber: startIndex + index + 1,
|
|
content,
|
|
hasMatch: false
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Expand display lines to include a specific line number
|
|
*/
|
|
function expandDisplayLinesToInclude(lineNumber) {
|
|
if (lineNumber <= displayLines.value.length) {
|
|
return; // Already visible
|
|
}
|
|
|
|
const newEndIndex = Math.min(lineNumber + 1000, fileLines.value.length);
|
|
updateDisplayLines(0, newEndIndex);
|
|
}
|
|
|
|
/**
|
|
* Watch for file selection changes
|
|
*/
|
|
watch(selectedFile, async newFile => {
|
|
if (newFile) {
|
|
await loadFile();
|
|
}
|
|
});
|
|
|
|
return {
|
|
// State
|
|
selectedFile,
|
|
fileContent,
|
|
fileLines,
|
|
displayLines,
|
|
jsonPaths,
|
|
isLoading,
|
|
fileError,
|
|
status,
|
|
preferences,
|
|
|
|
// Computed
|
|
availableFiles,
|
|
uniqueFiles,
|
|
hasFiles,
|
|
fileTooLarge,
|
|
|
|
// Constants
|
|
LINES_TO_DISPLAY,
|
|
MAX_RAW_FILE_SIZE,
|
|
|
|
// Methods
|
|
loadStatus,
|
|
loadFile,
|
|
clearFileSelection,
|
|
updateDisplayLines,
|
|
expandDisplayLinesToInclude,
|
|
formatSize,
|
|
formatFileName,
|
|
getFileType
|
|
};
|
|
}
|