| |
| |
| |
| |
| |
|
|
| import type { AnalysisData } from '../api/GLTR_API'; |
| import { validateDemoFormat, ensureJsonExtension } from '../utils/localFileUtils'; |
| import { extractErrorMessage } from '../utils/errorUtils'; |
| import { tr } from '../lang/i18n-lite'; |
|
|
| export interface ImportResult { |
| success: boolean; |
| data?: AnalysisData; |
| filename?: string; |
| message?: string; |
| cancelled?: boolean; |
| |
| files?: Array<{ |
| data: AnalysisData; |
| filename: string; |
| }>; |
| } |
|
|
| |
| |
| |
| |
| export class LocalFileIO { |
| |
| |
| |
| |
| |
| async import(multiple: boolean = false): Promise<ImportResult> { |
| return new Promise((resolve) => { |
| const input = document.createElement('input'); |
| input.type = 'file'; |
| input.accept = '.json,application/json'; |
| input.multiple = multiple; |
| input.style.display = 'none'; |
| |
| const cleanup = () => { |
| if (input.parentNode) input.parentNode.removeChild(input); |
| }; |
| |
| input.onchange = async (e: Event) => { |
| const files = (e.target as HTMLInputElement).files; |
| if (!files || files.length === 0) { |
| cleanup(); |
| resolve({ success: false, message: tr('No file selected') }); |
| return; |
| } |
|
|
| try { |
| if (multiple) { |
| |
| |
| const fileResults: Array<{data: AnalysisData; filename: string}> = []; |
| const errors: string[] = []; |
| |
| for (const file of Array.from(files)) { |
| try { |
| const text = await file.text(); |
| const data = JSON.parse(text); |
| validateDemoFormat(data); |
| const cleanFilename = ensureJsonExtension(file.name); |
| fileResults.push({ |
| data: data as AnalysisData, |
| filename: cleanFilename |
| }); |
| } catch (err) { |
| const message = extractErrorMessage(err, tr('Read failed')); |
| errors.push(`${file.name}: ${message}`); |
| } |
| } |
| |
| cleanup(); |
| |
| |
| if (fileResults.length === 0) { |
| resolve({ |
| success: false, |
| message: `${tr('All files failed to read:')}\n${errors.join('\n')}` |
| }); |
| } else { |
| |
| resolve({ |
| success: true, |
| files: fileResults, |
| message: errors.length > 0 ? `${tr('Partial files failed:')}\n${errors.join('\n')}` : undefined |
| }); |
| } |
| } else { |
| |
| const file = files[0]; |
| const text = await file.text(); |
| const data = JSON.parse(text); |
| validateDemoFormat(data); |
| cleanup(); |
| |
| const cleanFilename = ensureJsonExtension(file.name); |
| resolve({ |
| success: true, |
| data: data as AnalysisData, |
| filename: cleanFilename |
| }); |
| } |
| } catch (error) { |
| cleanup(); |
| const message = extractErrorMessage(error, tr('Failed to read file')); |
| resolve({ success: false, message }); |
| } |
| }; |
|
|
| input.oncancel = () => { |
| cleanup(); |
| resolve({ success: false, message: tr('User cancelled file selection'), cancelled: true }); |
| }; |
|
|
| document.body.appendChild(input); |
| input.click(); |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async export(data: AnalysisData, filename: string): Promise<boolean> { |
| return exportJsonFile(data, filename); |
| } |
| } |
|
|
| |
| |
| |
| export async function exportJsonFile(data: unknown, filename: string): Promise<boolean> { |
| try { |
| const jsonString = JSON.stringify(data, null, 2); |
| const blob = new Blob([jsonString], { type: 'application/json' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = ensureJsonExtension(filename); |
| document.body.appendChild(a); |
| a.click(); |
| setTimeout(() => { |
| if (a.parentNode) document.body.removeChild(a); |
| URL.revokeObjectURL(url); |
| }, 100); |
| return true; |
| } catch (error) { |
| console.error('exportJsonFile failed:', error); |
| return false; |
| } |
| } |
|
|
|
|