File size: 6,517 Bytes
494c9e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
 * 本地文件 I/O 工具
 * 负责与用户硬盘的交互:导入(读取)和导出(下载)文件
 * 不负责状态管理和持久化
 */

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;
    }>;
}

/**
 * 本地文件 I/O 工具类
 * 提供文件的导入(从硬盘读取)和导出(下载到硬盘)功能
 */
export class LocalFileIO {
    /**
     * 导入文件(弹出文件选择框)
     * @param multiple 是否支持多选,默认 false(单选)
     * @returns 单选模式返回 data 和 filename,多选模式返回 files 数组
     */
    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) {
                        // 多选模式:处理所有文件(即使只有1个文件也返回 files 数组格式)
                        // 逐个处理文件,收集成功和失败的结果
                        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 {
                            // 部分或全部成功,返回成功结果(如果有错误信息,可以包含在message中)
                            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();
                        // 确保文件名以 .json 结尾(统一入口处理)
                        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();
        });
    }

    /**
     * 导出文件(触发浏览器下载)
     * @param data 要导出的数据
     * @param filename 文件名
     * @returns 是否成功
     */
    async export(data: AnalysisData, filename: string): Promise<boolean> {
        return exportJsonFile(data, filename);
    }
}

/**
 * 下载任意可 JSON 序列化对象(如 gen_attribute 打包 demo 的 payload),与 {@link LocalFileIO.export} 同形。
 */
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;
    }
}