| | |
| | |
| | |
| | |
| | |
| |
|
| | 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> { |
| | 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('导出文件失败:', error); |
| | return false; |
| | } |
| | } |
| | } |
| |
|
| |
|