3 changed files with 180 additions and 212 deletions
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:provider/provider.dart'; |
||||
import 'package:syncfusion_flutter_datagrid/datagrid.dart'; |
||||
import 'package:win_text_editor/modules/template_parser/controllers/template_parser_controller.dart'; |
||||
import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; |
||||
|
||||
class TemplateGridView extends StatelessWidget { |
||||
final TemplateParserController controller; |
||||
|
||||
const TemplateGridView({super.key, required this.controller}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Consumer<TemplateParserController>( |
||||
builder: (context, controller, _) { |
||||
return _buildGridView(controller.templateItems, controller); |
||||
}, |
||||
); |
||||
} |
||||
|
||||
Widget _buildGridView(List<TemplateItem> items, TemplateParserController controller) { |
||||
final filteredItems = |
||||
controller.selectedNode != null |
||||
? items.where((item) => item.matchesPath(controller.selectedNode!.path)).toList() |
||||
: items; |
||||
|
||||
final dataSource = _TemplateItemDataSource( |
||||
items: filteredItems, |
||||
selectedNode: controller.selectedNode, |
||||
); |
||||
|
||||
return SfDataGrid( |
||||
source: dataSource, |
||||
columns: [ |
||||
GridColumn( |
||||
columnName: 'index', |
||||
width: 60, |
||||
label: Container( |
||||
padding: const EdgeInsets.all(8.0), |
||||
color: Colors.grey[200], |
||||
alignment: Alignment.center, |
||||
child: const Text('序号'), |
||||
), |
||||
), |
||||
GridColumn( |
||||
columnName: 'content', |
||||
label: Container( |
||||
padding: const EdgeInsets.all(8.0), |
||||
alignment: Alignment.center, |
||||
color: Colors.grey[200], |
||||
child: const Text('内容'), |
||||
), |
||||
), |
||||
], |
||||
gridLinesVisibility: GridLinesVisibility.both, |
||||
headerGridLinesVisibility: GridLinesVisibility.both, |
||||
columnWidthMode: ColumnWidthMode.fill, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class _TemplateItemDataSource extends DataGridSource { |
||||
final List<TemplateItem> items; |
||||
final TemplateNode? selectedNode; |
||||
|
||||
_TemplateItemDataSource({required this.items, required this.selectedNode}); |
||||
|
||||
@override |
||||
List<DataGridRow> get rows => |
||||
items.map((item) { |
||||
return DataGridRow( |
||||
cells: [ |
||||
DataGridCell<int>(columnName: 'index', value: items.indexOf(item) + 1), |
||||
DataGridCell<String>(columnName: 'content', value: item.value), |
||||
], |
||||
); |
||||
}).toList(); |
||||
|
||||
@override |
||||
DataGridRowAdapter? buildRow(DataGridRow row) { |
||||
return DataGridRowAdapter( |
||||
cells: |
||||
row.getCells().map<Widget>((dataGridCell) { |
||||
return Container( |
||||
padding: const EdgeInsets.all(8.0), |
||||
alignment: |
||||
dataGridCell.columnName == 'index' ? Alignment.center : Alignment.centerLeft, |
||||
child: Text(dataGridCell.value.toString()), |
||||
); |
||||
}).toList(), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,83 @@
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:provider/provider.dart'; |
||||
import 'package:win_text_editor/modules/template_parser/controllers/template_parser_controller.dart'; |
||||
import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; |
||||
import 'package:win_text_editor/shared/components/tree_view.dart'; |
||||
|
||||
class TemplateTreeView extends StatelessWidget { |
||||
final TemplateParserController controller; |
||||
|
||||
const TemplateTreeView({super.key, required this.controller}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Consumer<TemplateParserController>( |
||||
builder: (context, controller, _) { |
||||
if (controller.treeNodes.isEmpty) { |
||||
return const Center(child: Text('No XML data available')); |
||||
} |
||||
|
||||
return TreeView( |
||||
nodes: _processXmlNodes(controller.treeNodes), |
||||
config: const TreeViewConfig( |
||||
showIcons: true, |
||||
singleSelect: true, |
||||
selectedColor: Colors.lightBlueAccent, |
||||
icons: {'element': Icons.label_outline, 'attribute': Icons.code}, |
||||
), |
||||
onNodeTap: (node) { |
||||
final templateNode = node as TemplateNode; |
||||
controller.selectTreeNode(templateNode); |
||||
}, |
||||
nodeBuilder: (context, node, isSelected, onTap) { |
||||
return _buildTreeNode(node, isSelected, onTap, controller); |
||||
}, |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
|
||||
List<TreeNode> _processXmlNodes(List<TemplateNode> nodes) { |
||||
return nodes.cast<TreeNode>(); |
||||
} |
||||
|
||||
Widget _buildTreeNode( |
||||
TreeNode node, |
||||
bool isSelected, |
||||
VoidCallback onTap, |
||||
TemplateParserController controller, |
||||
) { |
||||
final templateNode = node as TemplateNode; |
||||
final isAttribute = node.isAttribute; |
||||
final isActuallySelected = controller.selectedNode?.id == templateNode.id; |
||||
|
||||
return Container( |
||||
color: isActuallySelected ? Colors.lightBlueAccent.withOpacity(0.2) : Colors.transparent, |
||||
child: Padding( |
||||
padding: EdgeInsets.only(left: 12.0 * node.depth), |
||||
child: ListTile( |
||||
dense: true, |
||||
leading: |
||||
isAttribute |
||||
? const Icon(Icons.code, size: 16, color: Colors.grey) |
||||
: const Icon(Icons.label_outline, size: 18, color: Colors.blue), |
||||
title: Text( |
||||
isAttribute ? templateNode.name.substring(1) : templateNode.name, |
||||
style: TextStyle( |
||||
color: isAttribute ? Colors.grey[600] : Colors.black, |
||||
fontWeight: isAttribute ? FontWeight.normal : FontWeight.w500, |
||||
), |
||||
), |
||||
trailing: |
||||
templateNode.isRepeated |
||||
? Text( |
||||
"(${templateNode.repreatCount.toString()})", |
||||
style: const TextStyle(color: Colors.grey), |
||||
) |
||||
: null, |
||||
onTap: onTap, |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
Loading…
Reference in new issue