Browse Source

开搞模板解析器

master
hejl 2 months ago
parent
commit
f8c5661c1d
  1. 5
      win_text_editor/lib/app/menus/app_menu.dart
  2. 12
      win_text_editor/lib/app/menus/menu_actions.dart
  3. 4
      win_text_editor/lib/app/menus/menu_constants.dart
  4. 21
      win_text_editor/lib/app/models/editor_tab.dart
  5. 105
      win_text_editor/lib/app/providers/editor_provider.dart
  6. 148
      win_text_editor/lib/app/widgets/editor_pane.dart
  7. 4
      win_text_editor/lib/app/widgets/file_explorer.dart
  8. 83
      win_text_editor/lib/app/widgets/tab_bar.dart
  9. 113
      win_text_editor/lib/app/widgets/template_parser.dart

5
win_text_editor/lib/app/menus/app_menu.dart

@ -14,6 +14,7 @@ class AppMenu extends StatelessWidget { @@ -14,6 +14,7 @@ class AppMenu extends StatelessWidget {
child: Row(
children: [
_buildMenuButton(context, '文件', _buildFileMenuItems()),
_buildMenuButton(context, '工具', _buildToolsMenuItems()),
_buildMenuButton(context, '编辑', _buildEditMenuItems()),
_buildMenuButton(context, '窗口', _buildWindowMenuItems()),
_buildMenuButton(context, '帮助', _buildHelpMenuItems()),
@ -22,6 +23,10 @@ class AppMenu extends StatelessWidget { @@ -22,6 +23,10 @@ class AppMenu extends StatelessWidget {
);
}
List<PopupMenuEntry<String>> _buildToolsMenuItems() {
return [const PopupMenuItem<String>(value: MenuConstants.templateParser, child: Text('模板解析'))];
}
Widget _buildMenuButton(BuildContext context, String label, List<PopupMenuEntry<String>> items) {
return PopupMenuButton<String>(
offset: const Offset(0, 30),

12
win_text_editor/lib/app/menus/menu_actions.dart

@ -5,12 +5,19 @@ import 'package:win_text_editor/app/menus/menu_constants.dart'; @@ -5,12 +5,19 @@ import 'package:win_text_editor/app/menus/menu_constants.dart';
import 'package:win_text_editor/app/providers/file_provider.dart';
import 'dart:io';
import '../models/editor_tab.dart';
import '../providers/editor_provider.dart';
import '../widgets/template_parser.dart'; // TemplateParser
class MenuActions {
static Future<void> handleMenuAction(String value, BuildContext context) async {
switch (value) {
case MenuConstants.openFolder:
await _openFolder(context);
break;
case MenuConstants.templateParser:
_openTemplateParser(context);
break;
case MenuConstants.exit:
_exitApplication();
break;
@ -27,6 +34,11 @@ class MenuActions { @@ -27,6 +34,11 @@ class MenuActions {
}
}
static void _openTemplateParser(BuildContext context) {
final editorProvider = Provider.of<EditorProvider>(context, listen: false);
editorProvider.addTemplateParserTab();
}
static void _exitApplication() {
exit(0);
}

4
win_text_editor/lib/app/menus/menu_constants.dart

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
class MenuConstants {
//
static const String fileMenu = 'file';
static const String toolsMenu = 'tools';
static const String editMenu = 'edit';
static const String windowMenu = 'window';
static const String helpMenu = 'help';
@ -11,6 +12,9 @@ class MenuConstants { @@ -11,6 +12,9 @@ class MenuConstants {
static const String saveAs = 'save_as';
static const String exit = 'exit';
//
static const String templateParser = 'template_parser';
//
static const String undo = 'undo';
static const String redo = 'redo';

21
win_text_editor/lib/app/models/editor_tab.dart

@ -1,15 +1,16 @@ @@ -1,15 +1,16 @@
// models/editor_tab.dart
enum EditorTabType {
blank, //
template, //
fileEditor, //
//
}
class EditorTab {
final String id;
final String title;
final String path;
String content;
final String fileType;
final EditorTabType type;
final dynamic content; //
EditorTab({
required this.id,
required this.title,
required this.path,
required this.content,
required this.fileType,
});
EditorTab({required this.id, required this.title, required this.type, this.content});
}

105
win_text_editor/lib/app/providers/editor_provider.dart

@ -1,97 +1,40 @@ @@ -1,97 +1,40 @@
import 'package:flutter/material.dart';
import 'package:win_text_editor/app/models/editor_tab.dart';
import 'package:win_text_editor/app/services/file_service.dart';
import 'package:win_text_editor/app/services/syntax_service.dart';
// providers/editor_provider.dart
class EditorProvider with ChangeNotifier {
final List<EditorTab> _openTabs = [];
int _activeTabIndex = 0;
int _currentLayout = 0; // 0=, 1=, 2=
List<EditorTab> _tabs = [];
String? _activeTabId;
List<EditorTab> get openTabs => _openTabs;
int get activeTabIndex => _activeTabIndex;
int get currentLayout => _currentLayout;
List<EditorTab> get tabs => _tabs;
String? get activeTabId => _activeTabId;
Future<void> openFile(String filePath) async {
try {
//
final existingIndex = _openTabs.indexWhere((tab) => tab.path == filePath);
if (existingIndex != -1) {
_activeTabIndex = existingIndex;
notifyListeners();
return;
}
//
final content = await FileService.readFile(filePath);
final fileName = filePath.split('/').last;
final fileType = SyntaxService.detectFileType(fileName);
//
final newTab = EditorTab(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: fileName,
path: filePath,
content: content,
fileType: fileType,
);
_openTabs.add(newTab);
_activeTabIndex = _openTabs.length - 1;
notifyListeners();
} catch (e) {
debugPrint('Error opening file: $e');
}
void addTab(EditorTab tab) {
_tabs.add(tab);
_activeTabId = tab.id;
notifyListeners();
}
void closeTab(String tabId) {
final index = _openTabs.indexWhere((tab) => tab.id == tabId);
if (index != -1) {
_openTabs.removeAt(index);
if (_activeTabIndex >= index && _activeTabIndex > 0) {
_activeTabIndex--;
}
notifyListeners();
_tabs.removeWhere((tab) => tab.id == tabId);
if (_activeTabId == tabId) {
_activeTabId = _tabs.isNotEmpty ? _tabs.last.id : null;
}
notifyListeners();
}
void setActiveTab(int index) {
if (index >= 0 && index < _openTabs.length) {
_activeTabIndex = index;
notifyListeners();
}
}
Future<void> saveFile(String tabId) async {
final index = _openTabs.indexWhere((tab) => tab.id == tabId);
if (index != -1) {
final tab = _openTabs[index];
await FileService.writeFile(tab.path, tab.content);
//
}
void setActiveTab(String tabId) {
_activeTabId = tabId;
notifyListeners();
}
Future<void> copyToClipboard(String tabId) async {
final index = _openTabs.indexWhere((tab) => tab.id == tabId);
if (index != -1) {
final tab = _openTabs[index];
//
// 使 clipboard package
}
}
void changeLayout(int layout) {
if (layout >= 0 && layout <= 2) {
_currentLayout = layout;
notifyListeners();
}
}
void updateTabContent(String tabId, String newContent) {
final index = _openTabs.indexWhere((tab) => tab.id == tabId);
if (index != -1) {
_openTabs[index].content = newContent;
notifyListeners();
}
//
void addTemplateParserTab() {
final tab = EditorTab(
id: 'template-${DateTime.now().millisecondsSinceEpoch}',
title: '模板解析 ${_tabs.where((t) => t.type == EditorTabType.template).length + 1}',
type: EditorTabType.template,
);
addTab(tab);
}
}

148
win_text_editor/lib/app/widgets/editor_pane.dart

@ -1,9 +1,9 @@ @@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:win_text_editor/app/providers/editor_provider.dart';
import 'package:win_text_editor/app/widgets/tab_bar.dart';
import '../models/editor_tab.dart';
import '../providers/editor_provider.dart';
import 'template_parser.dart';
class EditorPane extends StatelessWidget {
const EditorPane({super.key});
@ -12,85 +12,93 @@ class EditorPane extends StatelessWidget { @@ -12,85 +12,93 @@ class EditorPane extends StatelessWidget {
Widget build(BuildContext context) {
final editorProvider = Provider.of<EditorProvider>(context);
return Column(
children: [
const EditorTabBar(),
Expanded(
child: IndexedStack(
index: editorProvider.currentLayout,
children: [
//
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: editorProvider.openTabs.length,
itemBuilder: (context, index) {
return EditorTabContent(tab: editorProvider.openTabs[index]);
},
),
//
Stack(
children:
editorProvider.openTabs.map((tab) {
return Positioned(
top: 20.0 * editorProvider.openTabs.indexOf(tab),
left: 20.0 * editorProvider.openTabs.indexOf(tab),
right: 20.0 * editorProvider.openTabs.indexOf(tab),
bottom: 20.0 * editorProvider.openTabs.indexOf(tab),
child: EditorTabContent(tab: tab),
);
}).toList(),
),
//
if (editorProvider.openTabs.isNotEmpty)
EditorTabContent(tab: editorProvider.openTabs[editorProvider.activeTabIndex]),
],
return Container(
color: Colors.white,
child: Column(
children: [
//
_buildTabBar(context),
//
Expanded(
child:
editorProvider.tabs.isEmpty
? const Center(child: Text('空白区域', style: TextStyle(color: Colors.grey)))
: _buildCurrentTabContent(context),
),
),
],
],
),
);
}
Widget _buildTabBar(BuildContext context) {
final editorProvider = Provider.of<EditorProvider>(context);
return SizedBox(
height: 40,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: editorProvider.tabs.length,
itemBuilder: (ctx, index) {
final tab = editorProvider.tabs[index];
return _TabItem(
tab: tab,
isActive: tab.id == editorProvider.activeTabId,
onClose: () => editorProvider.closeTab(tab.id),
onTap: () => editorProvider.setActiveTab(tab.id),
);
},
),
);
}
Widget _buildCurrentTabContent(BuildContext context) {
final editorProvider = Provider.of<EditorProvider>(context);
final activeTab = editorProvider.tabs.firstWhere(
(tab) => tab.id == editorProvider.activeTabId,
orElse: () => editorProvider.tabs.first,
);
switch (activeTab.type) {
case EditorTabType.template:
return const TemplateParser(); //
default:
return Container(); //
}
}
}
class EditorTabContent extends StatelessWidget {
class _TabItem extends StatelessWidget {
final EditorTab tab;
final bool isActive;
final VoidCallback onClose;
final VoidCallback onTap;
const EditorTabContent({super.key, required this.tab});
const _TabItem({
required this.tab,
required this.isActive,
required this.onClose,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final editorProvider = Provider.of<EditorProvider>(context, listen: false);
return Card(
margin: const EdgeInsets.all(8.0),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Text(tab.title),
const Spacer(),
IconButton(
icon: const Icon(Icons.content_copy),
onPressed: () => editorProvider.copyToClipboard(tab.id),
),
IconButton(
icon: const Icon(Icons.save),
onPressed: () => editorProvider.saveFile(tab.id),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => editorProvider.closeTab(tab.id),
),
],
),
),
Expanded(
child: SingleChildScrollView(
child: Container(padding: const EdgeInsets.all(8.0), child: Text(tab.content)),
),
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: isActive ? Colors.blue[100] : Colors.grey[200],
border: Border(
bottom: BorderSide(color: isActive ? Colors.blue : Colors.transparent, width: 2),
),
],
),
child: Row(
children: [
Text(tab.title),
const SizedBox(width: 8),
IconButton(icon: const Icon(Icons.close, size: 16), onPressed: onClose),
],
),
),
);
}

4
win_text_editor/lib/app/widgets/file_explorer.dart

@ -130,14 +130,14 @@ class _FileNodeWidget extends StatelessWidget { @@ -130,14 +130,14 @@ class _FileNodeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final editorProvider = Provider.of<EditorProvider>(context, listen: false);
//final editorProvider = Provider.of<EditorProvider>(context, listen: false);
return InkWell(
onTap: () {
if (node.isDirectory) {
onTap();
} else {
editorProvider.openFile(node.path);
//editorProvider.openFile(node.path);
}
},
child: Container(

83
win_text_editor/lib/app/widgets/tab_bar.dart

@ -1,83 +0,0 @@ @@ -1,83 +0,0 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:win_text_editor/app/providers/editor_provider.dart';
import '../providers/file_provider.dart';
class EditorTabBar extends StatelessWidget {
const EditorTabBar({super.key});
@override
Widget build(BuildContext context) {
final editorProvider = Provider.of<EditorProvider>(context);
return Container(
height: 40,
color: Colors.grey[300],
child: Row(
children: [
//
PopupMenuButton<int>(
icon: const Icon(Icons.grid_view),
itemBuilder:
(context) => [
const PopupMenuItem(value: 0, child: Text('平铺布局')),
const PopupMenuItem(value: 1, child: Text('层叠布局')),
const PopupMenuItem(value: 2, child: Text('单页布局')),
],
onSelected: (value) => editorProvider.changeLayout(value),
),
//
Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: editorProvider.openTabs.length,
itemBuilder: (context, index) {
final tab = editorProvider.openTabs[index];
return InkWell(
onTap: () => editorProvider.setActiveTab(index),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
decoration: BoxDecoration(
color:
editorProvider.activeTabIndex == index ? Colors.white : Colors.grey[200],
border: Border(
bottom: BorderSide(
color:
editorProvider.activeTabIndex == index
? Colors.blue
: Colors.transparent,
width: 2.0,
),
),
),
child: Center(
child: Row(
children: [
Text(tab.title),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.close, size: 16),
onPressed: () => editorProvider.closeTab(tab.id),
),
],
),
),
),
);
},
),
),
//
IconButton(
icon: const Icon(Icons.add),
onPressed: () async {
final fileProvider = Provider.of<FileProvider>(context, listen: false);
await fileProvider.pickAndOpenFile();
},
),
],
),
);
}
}

113
win_text_editor/lib/app/widgets/template_parser.dart

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
class TemplateParser extends StatelessWidget {
const TemplateParser({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.zero, //
color: Colors.grey[100],
child: Column(
children: [
//
Expanded(
child: GridView.builder(
padding: const EdgeInsets.all(8.0),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.0,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
),
itemCount: 4,
itemBuilder: (context, index) {
return _buildEditorPanel(context, index);
},
),
),
],
),
);
}
Widget _buildEditorPanel(BuildContext context, int index) {
//
final titles = ['输入内容', '输出内容', '参考文件', '模板'];
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//
Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: const BorderRadius.vertical(top: Radius.circular(4.0)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
//
Text(
titles[index],
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
),
//
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.folder_open, size: 18),
onPressed: () {},
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
IconButton(
icon: const Icon(Icons.content_copy, size: 18),
onPressed: () {},
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
IconButton(
icon: const Icon(Icons.save, size: 18),
onPressed: () {},
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
IconButton(
icon: const Icon(Icons.close, size: 18),
onPressed: () {},
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
],
),
),
//
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(4.0)),
),
child: const TextField(
maxLines: null,
expands: true,
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
border: InputBorder.none,
contentPadding: EdgeInsets.all(8.0),
hintText: '输入内容...',
),
),
),
),
],
);
}
}
Loading…
Cancel
Save