Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/releases/v1.10.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## 修复

- 修复约 10 MB、十万级节点 JSON 在格式化时长时间阻塞界面的问题,将解析、格式化和压缩移至后台 isolate。
- 修复树形视图在节点折叠时仍一次性创建全部子节点的问题,改为按展开状态懒加载,并以每页 100 项渐进展示。
- 修复超大 JSON 自动写入本地偏好设置、导致应用下次启动再次读取并卡顿的问题;超过 1 MB 的临时内容不再持久化。

## 优化

- 保留原文件的 CRLF 换行风格;已经符合四空格格式的超大文件不再触发无意义的整段文本替换和重排。
- 大文件粘贴、输入解析和启动恢复统一使用可取消的后台任务,新的输入会使旧结果失效,避免异步结果覆盖当前内容。
- 树形视图搜索导航可直接补充显示当前命中路径,不需要提前渲染该分支的全部兄弟节点。

## 验证

- 使用实际 10,745,012 字符、122,985 条记录的 JSON 验证:后台格式化约 268 ms,主事件循环保持响应,输出与已格式化原文一致。
- 使用真实 macOS Release 应用验收:粘贴进入大文件模式约 743 ms,格式化完成约 1.24 秒,压缩完成约 927 ms,搜索返回约 704 ms;复制回读的 10,745,012 字符与原文完全一致。
- 已通过 Flutter 静态分析、Lab 自定义规范检查以及服务、模型、工具、ViewModel、Widget 自动化测试。
- 已新增十万级 JSON 后台转换回归测试和万级折叠分支懒加载 Widget 测试。
- 已通过全新 macOS 1.10.2 Release 构建、严格代码签名校验和真实 worker IPC `hello → accepted → status → stopped` 冒烟验证。
- GitHub Actions 将在 Ubuntu 执行完整测试和 golden 视觉回归,并在 Windows、macOS、Linux 完成 Release 构建与真实工作进程 IPC 冒烟验证。

**完整更新日志:** [v1.10.1...v1.10.2](https://github.com/bcblr1993/iot_devkit_flutter/compare/v1.10.1...v1.10.2)
15 changes: 15 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,21 @@
"inputLabel": "Input",
"treeViewLabel": "Tree View",
"itemsLabel": "items",
"loadMoreJsonItems": "Load more ({visible}/{total})",
"@loadMoreJsonItems": {
"placeholders": {
"visible": {"type": "int"},
"total": {"type": "int"}
}
},
"largeJsonModeTitle": "Large document mode",
"largeJsonModeDescription": "Full content is {size}. Showing a bounded preview of up to {lines} lines; copy, format, minify, search, and tree view still use the complete document.",
"@largeJsonModeDescription": {
"placeholders": {
"size": {"type": "String"},
"lines": {"type": "int"}
}
},
"enterJsonHint": "Enter JSON to view tree",
"invalidJson": "Invalid JSON",
"exportConfig": "Export Config",
Expand Down
15 changes: 15 additions & 0 deletions lib/l10n/app_zh.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@
"inputLabel": "输入",
"treeViewLabel": "树形视图",
"itemsLabel": "项",
"loadMoreJsonItems": "加载更多({visible}/{total})",
"@loadMoreJsonItems": {
"placeholders": {
"visible": {"type": "int"},
"total": {"type": "int"}
}
},
"largeJsonModeTitle": "大文件模式",
"largeJsonModeDescription": "完整内容大小为 {size},当前显示最多前 {lines} 行的限量预览;复制、格式化、压缩、搜索和树形视图仍使用完整文档。",
"@largeJsonModeDescription": {
"placeholders": {
"size": {"type": "String"},
"lines": {"type": "int"}
}
},
"enterJsonHint": "输入 JSON 以查看树形结构",
"invalidJson": "无效的 JSON",
"exportConfig": "导出配置",
Expand Down
18 changes: 18 additions & 0 deletions lib/l10n/generated/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,24 @@ abstract class AppLocalizations {
/// **'items'**
String get itemsLabel;

/// No description provided for @loadMoreJsonItems.
///
/// In en, this message translates to:
/// **'Load more ({visible}/{total})'**
String loadMoreJsonItems(int visible, int total);

/// No description provided for @largeJsonModeTitle.
///
/// In en, this message translates to:
/// **'Large document mode'**
String get largeJsonModeTitle;

/// No description provided for @largeJsonModeDescription.
///
/// In en, this message translates to:
/// **'Full content is {size}. Showing a bounded preview of up to {lines} lines; copy, format, minify, search, and tree view still use the complete document.'**
String largeJsonModeDescription(String size, int lines);

/// No description provided for @enterJsonHint.
///
/// In en, this message translates to:
Expand Down
13 changes: 13 additions & 0 deletions lib/l10n/generated/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,19 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get itemsLabel => 'items';

@override
String loadMoreJsonItems(int visible, int total) {
return 'Load more ($visible/$total)';
}

@override
String get largeJsonModeTitle => 'Large document mode';

@override
String largeJsonModeDescription(String size, int lines) {
return 'Full content is $size. Showing a bounded preview of up to $lines lines; copy, format, minify, search, and tree view still use the complete document.';
}

@override
String get enterJsonHint => 'Enter JSON to view tree';

Expand Down
13 changes: 13 additions & 0 deletions lib/l10n/generated/app_localizations_zh.dart
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,19 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get itemsLabel => '项';

@override
String loadMoreJsonItems(int visible, int total) {
return '加载更多($visible/$total)';
}

@override
String get largeJsonModeTitle => '大文件模式';

@override
String largeJsonModeDescription(String size, int lines) {
return '完整内容大小为 $size,当前显示最多前 $lines 行的限量预览;复制、格式化、压缩、搜索和树形视图仍使用完整文档。';
}

@override
String get enterJsonHint => '输入 JSON 以查看树形结构';

Expand Down
63 changes: 63 additions & 0 deletions lib/services/json_formatter_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'dart:convert';
import 'dart:isolate';

enum JsonOutputStyle { pretty, compact }

class JsonFormatResult {
const JsonFormatResult({
required this.output,
required this.data,
});

final String output;
final dynamic data;
}

class JsonFormatterService {
const JsonFormatterService._();

static Future<JsonFormatResult> transform(
String source,
JsonOutputStyle style,
) {
return Isolate.run(() => transformSync(source, style));
}

static Future<dynamic> parse(String source) {
return Isolate.run(() => jsonDecode(source));
}

static JsonFormatResult transformSync(
String source,
JsonOutputStyle style,
) {
final data = jsonDecode(source);
final encoder = style == JsonOutputStyle.pretty
? const JsonEncoder.withIndent(' ')
: const JsonEncoder();
var output = encoder.convert(data);

// Avoid replacing an already-formatted large document solely because the
// encoder uses LF while the source file uses CRLF.
if (style == JsonOutputStyle.pretty && _usesOnlyCrLf(source)) {
output = output.replaceAll('\n', '\r\n');
}

return JsonFormatResult(output: output, data: data);
}

static bool _usesOnlyCrLf(String source) {
final firstNewline = source.indexOf('\n');
if (firstNewline < 0) {
return false;
}

for (var index = firstNewline; index < source.length; index++) {
if (source.codeUnitAt(index) == 0x0A &&
(index == 0 || source.codeUnitAt(index - 1) != 0x0D)) {
return false;
}
}
return true;
}
}
Loading
Loading