Compare commits

...

7 Commits

6 changed files with 245 additions and 257 deletions

View File

@ -1,16 +1,13 @@
// this file should handle most of the API calls
// it also builds some widgets, but it will be modulated later
// it also builds some widgets, but it will be modulated later // chat it did
import 'dart:async';
import 'dart:typed_data';
import 'structs.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiService {
static String ip = "";
static String port = "";
@ -141,7 +138,6 @@ class ApiService {
} catch (e) {
print('_getEmailContent caught error: $e');
}
// return content;
return HTMLofThread;
}
@ -344,75 +340,41 @@ class ApiService {
return AttachmentResponse(name: "error", data: Uint8List(0));
}
//TODO: MOVE THIS INTO WEB
// Future<List<Map<String, dynamic>>> getMarkerPosition() async {
// //this is so we can put a widget right below each email, but the way how the email content is generated
// //leads to problems as for a) the html is added one right after the other in one iframe, b)
// // if it was multiple iframes then the scrolling to jump would not work as expected
Future<List<String>> fetchMarkdownContent(
List<String> IDsString, String emailFolder) async {
List<String> MDofThread = [];
threadAttachments = [];
int counter = 0;
// print("marker called");
// // JavaScript code embedded as a string
// String jsCode = '''
// (async function waitForIframeAndMarkers() {
// try {
// return await new Promise((resolve) => {
// const interval = setInterval(() => {
// console.log("⏳ Checking for iframe...");
// var iframe = document.getElementsByTagName('iframe')[0];
// if (iframe && iframe.contentDocument) {
// console.log("✅ Iframe found!");
// var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
// var markers = iframeDoc.querySelectorAll('[id^="JuanBedarramarker"]');
// if (markers.length > 0) {
// console.log(` Found markers in the iframe.`);
// var positions = [];
// markers.forEach((marker) => {
// var rect = marker.getBoundingClientRect();
// positions.push({
// id: marker.id,
// x: rect.left + window.scrollX,
// y: rect.top + window.scrollY,
// });
// });
// console.log("📌 Marker positions:", positions);
// clearInterval(interval);
// resolve(JSON.stringify(positions)); // Ensure proper JSON string
// } else {
// console.log("❌ No markers found yet.");
// }
// } else {
// console.log("❌ Iframe not found or not loaded yet.");
// }
// }, 200);
// });
// } catch (error) {
// console.error("JS Error:", error);
// throw error; // Propagate error to Dart
// }
// })();
// ''';
try {
//attaches email after email from a thread
for (var id in IDsString) {
var url = Uri.http('$ip:$port', 'email_md', {'id': id});
print(url);
var response = await http.get(url);
currThread.add(id);
if (response.statusCode == 200) {
counter += 1;
Map<String, dynamic> json = jsonDecode(response.body);
// try {
// // Execute the JavaScript code using eval
// // final result = await js.context.callMethod('eval', [jsCode]);
MDofThread.add(json['md'] ?? '');
try {
List<AttachmentInfo> attachments =
await getAttachmentsInfo(emailFolder, id);
for (var attachment in attachments) {
//TODO: for each attachment creaate at the bottom a widget for each individual one
threadAttachments
.add(await getAttachment(emailFolder, id, attachment.name));
}
} catch (innerError) {
print('_getAttachment info caught error $innerError');
}
}
}
} catch (e) {
print('_getMDContent caught error: $e');
}
// if (result != null && result is String) {
// print("Result received: $result");
// // Parse the JSON string returned by JavaScript into a Dart list of maps
// final List<dynamic> parsedResult = jsonDecode(result);
// var positions = List<Map<String, dynamic>>.from(parsedResult);
// print("positions put on");
// print(positions);
// return positions;
// } else {
// print("result is null or not a string");
// }
// } catch (e, stackTrace) {
// print("Error executing JavaScript: $e");
// print(stackTrace);
// }
// return [];
// }
return MDofThread;
}
}

View File

@ -1,16 +1,18 @@
import 'package:crab_ui/api_service.dart';
import 'package:crab_ui/attachmentDownload.dart';
import 'package:crab_ui/collapsableEmails.dart';
import 'package:crab_ui/structs.dart';
import 'package:flutter/material.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'attachmentWidget.dart';
class EmailToolbar extends StatefulWidget {
final Function(String) onJumpToSpan;
final Function(String) onJumpToNumbering;
final Function(String) onViewspecs;
final VoidCallback onButtonPressed;
EmailToolbar(
{Key? key, required this.onButtonPressed, required this.onJumpToSpan})
{Key? key, required this.onButtonPressed, required this.onJumpToNumbering, required this.onViewspecs})
: super(key: key);
@override
@ -19,7 +21,8 @@ class EmailToolbar extends StatefulWidget {
class _DynamicClassesAugment extends State<EmailToolbar> {
String selectedClass = 'Class 1';
// TextEditingController _jumpController = TextEditingController();
TextEditingController _jumpController = TextEditingController();
TextEditingController _viewspecsController = TextEditingController();
// late final FocusNode _JumpItemfocusNode;
// late final FocusNode _viewSpecsfocusNode;
@ -46,7 +49,7 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
void dispose() {
// _JumpItemfocusNode.dispose();
// _viewSpecsfocusNode.dispose();
// _jumpController.dispose();
_jumpController.dispose();
super.dispose();
}
@ -71,20 +74,20 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
child: Text('Attachments'),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: AugmentClasses.handleOpen,
child: Text('Open'),
),
// ElevatedButton(
// onPressed: AugmentClasses.handleOpen,
// child: Text('Open'),
// ),
// SizedBox(width: 8),
ElevatedButton(
onPressed: AugmentClasses.handleFind,
child: Text('Find'),
),
// SizedBox(width: 8),
ElevatedButton(
onPressed: AugmentClasses.handleStop,
child: Text('Stop'),
),
// ElevatedButton(
// onPressed: AugmentClasses.handleStop,
// child: Text('Stop'),
// ),
ElevatedButton(
onPressed: () {
AugmentClasses.handleMove(context);
@ -131,10 +134,10 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
// width: 8,
// ),
Container(
width: 50,
width: 100,
height: 30,
child: TextField(
// controller: _jumpController,
controller: _jumpController,
decoration: InputDecoration(
border: OutlineInputBorder(),
// suffixIcon: Icon(Icons.search)
@ -142,7 +145,7 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
onSubmitted: (value) {
print("onSubmitted");
if (value.isNotEmpty) {
widget.onJumpToSpan(value);
widget.onJumpToNumbering(value);
}
},
),
@ -179,14 +182,18 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
onPressed: () => AugmentClasses.ViewSpecsButton(context),
child: Text('ViewSpecs:')),
Container(
width: 50,
width: 100,
height: 30,
child: TextField(
controller: _viewspecsController,
decoration: InputDecoration(
labelText: '',
border: OutlineInputBorder(),
// suffixIcon: Icon(Icons.style_rounded)
),
onSubmitted: (value) {
widget.onViewspecs(value);
},
),
),
ElevatedButton(
@ -484,30 +491,8 @@ class AugmentClasses {
print("Stop button pressed");
}
static void handleJump(String spanId) {
String js_code = '''
var iframe = document.getElementsByTagName('iframe')[0]; // 0 for the first iframe, 1 for the second, etc.
// Check if the iframe is loaded and has content
if (iframe && iframe.contentDocument) {
// Access the document inside the iframe
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
// Find the element with the specific id inside the iframe
var targetElement = iframeDoc.getElementById("$spanId"); // Replace '36 ' with the actual id of the target element
// If the element exists, scroll to it
if (targetElement) {
targetElement.scrollIntoView();
console.log('Scrolled to element with id "$spanId" inside the iframe.');
} else {
console.log('Element with id "$spanId" not found inside the iframe.');
}
} else {
console.log('Iframe not found or not loaded.');
}
''';
// js.context.callMethod('eval', [js_code]);
static void handleJump(String value) {
print(value);
}
static void invisibility(String htmlClass) {}

View File

@ -3,13 +3,13 @@ import 'package:flutter/material.dart';
class CollapsableEmails extends StatefulWidget {
final List<String> thread; // email id's in the form xyz@gmail.com
final List<String> threadHTML;
final List<String> threadMarkdown;
final String threadIDs;
CollapsableEmails(
{required this.thread,
required this.threadHTML,
required this.threadIDs});
required this.threadMarkdown,
required this.threadIDs, String? targetJumpNumbering, String? targetViewspecs});
@override
State<CollapsableEmails> createState() => _CollapsableEmailsState();

View File

@ -1,3 +1,4 @@
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'api_service.dart';
import 'structs.dart';
@ -7,13 +8,20 @@ import 'package:markdown/markdown.dart' as md;
class CollapsableEmails extends StatefulWidget {
final List<String> thread; // email id's in the form xyz@gmail.com
final List<String> threadHTML;
// final List<String> threadHTML; to be replaced with the MD
final List<String> threadMarkdown;
final String threadIDs;
final String? targetJumpNumbering;
final String? targetViewspecs;
CollapsableEmails(
{required this.thread,
required this.threadHTML,
required this.threadIDs});
const CollapsableEmails({
required this.thread,
// required this.threadHTML,
required this.threadMarkdown,
required this.threadIDs,
this.targetJumpNumbering,
this.targetViewspecs,
});
@override
State<CollapsableEmails> createState() => _CollapsableEmailsState();
@ -49,15 +57,32 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
bool zoomOut = false;
bool zoomIn = true;
late List<AugmentTree> threadNodes = [];
static bool leftNumbering = true;
static bool rightNumbering = true;
bool showWhole = false;
@override
void initState() {
super.initState();
threadNodes = [];
currentZoomTree = [];
_markdownConverter();
// _markdownConverter();
_serializableData(widget.threadIDs); // this
_markdown2Tree(allMarkdown);
_markdown2Tree(widget.threadMarkdown);
}
@override
void didUpdateWidget(covariant CollapsableEmails oldWidget) {
// TODO: implement didUpdateWidget
super.didUpdateWidget(oldWidget);
if (widget.targetJumpNumbering != null &&
widget.targetJumpNumbering != oldWidget.targetJumpNumbering) {
_handleJump(widget.targetJumpNumbering!);
}
if (widget.targetViewspecs != null &&
widget.targetViewspecs != oldWidget.targetViewspecs) {
_handleViewspecs(widget.targetViewspecs!);
}
}
@override
@ -65,12 +90,16 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
super.dispose();
}
void _markdownConverter() async {
for (int email = 0; email < widget.threadHTML.length; email++) {
String markdown = html2md.convert(widget.threadHTML[email]);
allMarkdown.add(markdown);
}
}
// void _markdownConverter() async {
// // to list of markdown
// // for (int email = 0; email < widget.threadHTML.length; email++) {
// // String markdown = html2md.convert(widget.threadHTML[email]);
// // allMarkdown.add(markdown);
// // }
// for (int email = 0; email < widget.threadMarkdown.length; email++) {
// allMarkdown.add(email);
// }
// }
void _add2Tree(AugmentTree tree, md.Element node2add) {
// adds node to its corresponding place
@ -244,6 +273,15 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
],
),
SizedBox(width: 12.0),
if (leftNumbering)
Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 5, 0),
child: Text(
childNode.numbering,
style:
TextStyle(color: Color(Colors.purple[400]!.value)),
),
),
Expanded(
child: MarkdownBlock(
data: childNode.data,
@ -253,10 +291,15 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
.darkConfig, // or lightConfig depending on theme
),
),
Text(
childNode.numbering,
style: TextStyle(color: Color(Colors.purple[400]!.value)),
)
if (rightNumbering)
Padding(
padding: const EdgeInsets.fromLTRB(5, 10, 5, 0),
child: Text(
childNode.numbering,
style:
TextStyle(color: Color(Colors.purple[400]!.value)),
),
),
],
),
),
@ -266,6 +309,101 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
);
}
void _handleJump(String queryNumbering) {
print(queryNumbering);
if (queryNumbering.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please enter a numbering to jump to.')),
);
return;
}
final int targetEmailIndex = _expandedEmails.first;
if (targetEmailIndex >= threadNodes.length) {
// Error handling
return;
}
final AugmentTree rootOfCurrentEmail = threadNodes[targetEmailIndex];
final AugmentTree? foundNode =
_findNodeByNumbering(rootOfCurrentEmail, queryNumbering);
if (foundNode != null) {
setState(() {
currentZoomTree[targetEmailIndex] = foundNode; // Update the state
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Numbering "$queryNumbering" not found.')),
);
}
}
void _handleViewspecs(String viewspecsQuery) {
print(viewspecsQuery);
if (viewspecsQuery.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please enter the viewspecs.')),
);
return;
}
final int targetEmailIndex = _expandedEmails.first;
if (targetEmailIndex >= threadNodes.length) {
// Error handling
return;
}
if (viewspecsQuery.contains('n')) {
setState(() {
leftNumbering = false; // Update the state
rightNumbering = false;
});
}
if (viewspecsQuery.contains('m')) {
setState(() {
rightNumbering = true;
leftNumbering = true;
});
}
if (viewspecsQuery.contains('H')) {
setState(() {
leftNumbering = !leftNumbering;
});
}
if (viewspecsQuery.contains('G')) {
setState(() {
rightNumbering = !rightNumbering;
});
}
if (viewspecsQuery.contains('w')) {
setState(() {
showWhole = true;
});
}
// else {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(content: Text('Numbering "$viewspecsQuery" not found.')),
// );
// }
}
AugmentTree? _findNodeByNumbering(AugmentTree root, String numbering) {
//recursively finds the node you mentioned
// to find the AugmentTree node corresponding to the `numbering`.
if (root.numbering == numbering) {
return root;
}
for (var child in root.children) {
final found = _findNodeByNumbering(child, numbering);
if (found != null) {
return found;
}
}
return null;
}
@override
Widget build(BuildContext context) {
return _isLoaded

View File

@ -140,7 +140,8 @@ class EmailPageState extends State<EmailPage> {
return Scaffold(
body: EmailListScreen(
emails: emails,
getEmailContent: apiService.fetchEmailContent,
// getEmailContent: apiService.fetchEmailContent,
getEmailContent: apiService.fetchMarkdownContent,
folder: widget.selectedFolder, //try to grab from it directly
),
);

View File

@ -2,11 +2,9 @@ import 'package:flutter/material.dart';
import 'dart:ui_web' as ui;
import 'augment.dart';
// import 'dart:js_interop' as js; //eventually for manipulating css
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'collapsableEmails.dart';
import 'api_service.dart';
class EmailView extends StatefulWidget {
final List<String> emailContent;
final String from;
@ -44,6 +42,8 @@ class _EmailViewState extends State<EmailView> {
{'id': 'marker2', 'x': 150, 'y': 200},
{'id': 'marker3', 'x': 250, 'y': 300},
];
String? _targetJumpNumbering;
String? _targetViewspecs;
@override
void initState() {
@ -55,39 +55,26 @@ class _EmailViewState extends State<EmailView> {
// _registerViewFactory(currentContent);
}
// void _registerViewFactory(List<String> currentContent) { // i think this doesnt work anymore
// setState(() { //update to do item per item
// // each item to have itsviewtype ID
// // is this necessarey here??
// //could just move to collapsable
// viewTypeId = 'iframe-${DateTime.now().millisecondsSinceEpoch}';
// final emailHTML = web.document.createElement('div') as web.HTMLDivElement
// ..id = viewTypeId
// ..innerHTML = currentContent[0].toJS; // temporarily index because it has to do all of them
// emailHTML.style
// ..width = '100%'
// ..height = '100%'
// ..overflow = 'auto'
// ..scrollBehavior = 'smooth';
// ui.platformViewRegistry.registerViewFactory(
// viewTypeId,
// (int viewId) => emailHTML,
// );
// });
// }
void _scrollToNumber(String spanId) {
AugmentClasses.handleJump(spanId);
}
void _handleJumpRequest(String numbering) {
setState(() {
_targetJumpNumbering = numbering;
});
}
void _handleViewspecsRequest(String viewspecsCommand) {
setState(() {
_targetViewspecs = viewspecsCommand;
});
}
// TODO: void _invisibility(String ) //to make purple numbers not visible
@override
Widget build(BuildContext context) {
// print("thread id ${widget.id}");
ApiService.currThreadID = widget.id;
return Scaffold(
appBar: AppBar(
@ -98,28 +85,9 @@ class _EmailViewState extends State<EmailView> {
Column(
children: [
EmailToolbar(
onJumpToSpan: _scrollToNumber,
onButtonPressed: () => {},
// AugmentClasses.handleJump(viewTypeId, '1');
// print("button got pressed?");
// _registerViewFactory(r"""
// <h1>Welcome to My Website</h1>
// <p>This is a simple HTML page.</p>
// <h2>What is HTML?</h2>
// <p>HTML (HyperText Markup Language) is the most basic building~ block of the Web. It defines the meaning and structure of web content. Other technologies besides HTML are generally used to describe a web page's appearance/presentation (CSS) or functionality/behavior (JavaScript).</p>
// <h3>Here's a simple list:</h3>
// <ul>
// <li>HTML elements are the building blocks of HTML pages</li>
// <li>HTML uses tags like <code>&lt;tag&gt;</code> to organize and format content</li>
// <li>CSS is used with HTML to style pages</li>
// </ul>
// <p>Copyright © 2023</p>
// """);
// print("change");
// widget.emailContent = r"
//
onJumpToNumbering: _handleJumpRequest,
onViewspecs: _handleViewspecsRequest,
onButtonPressed: () => {print("email tool bar pressed")},
),
Row(
// title of email
@ -159,82 +127,16 @@ class _EmailViewState extends State<EmailView> {
Expanded(
child: CollapsableEmails(
//change here
thread: widget.messages, //this wont work in serializable
threadHTML: widget.emailContent,
thread: widget.messages, //this wont work in serializable
// threadHTML: widget.emailContent, // old html
threadMarkdown: widget.emailContent,
threadIDs: widget.id,
targetJumpNumbering: _targetJumpNumbering,
targetViewspecs: _targetViewspecs,
),
),
// Expanded(
// child: HtmlElementView(
// key: UniqueKey(),
// viewType: viewTypeId,
// ),
// ),
],
),
// Overlay widgets dynamically based on marker positions
// FutureBuilder<List<Map<String, dynamic>>>(
// future: _markerPositionsFuture,
// builder: (context, snapshot) {
// print("FutureBuilder state: ${snapshot.connectionState}");
// if (snapshot.connectionState == ConnectionState.waiting) {
// return Center(child: CircularProgressIndicator());
// }
// if (snapshot.hasError) {
// print("Error in FutureBuilder: ${snapshot.error}");
// return Center(child: Text('error loading markers'));
// }
// if (snapshot.hasData && snapshot.data != null) {
// final markers = snapshot.data!;
// return Stack(
// children: markers.map((marker) {
// return Positioned(
// left: marker['x'].toDouble(),
// top: marker['y'].toDouble(),
// child: GestureDetector(
// onTap: () {
// print('Tapped on ${marker['id']}');
// },
// child: Container(
// width: 50,
// height: 50,
// color: Colors.red,
// child: Center(
// child: Text(
// marker['id'],
// style: TextStyle(color: Colors.white),
// ),
// ),
// ),
// ),
// );
// }).toList(),
// );
// }
// return SizedBox.shrink(); // No markers found
// },
// ),
// Red widget overlay
// Positioned(
// left: 8, // Adjust based on your desired position
// top: 100 + 44 + 5, // Adjust based on your desired position
// child: IgnorePointer(
// ignoring: true, // Ensures the iframe remains interactive
// child: Container(
// color: Colors.red,
// width: 100,
// height: 50,
// child: Center(
// child: Text(
// 'Overlay',
// style: TextStyle(color: Colors.white),
// ),
// ),
// ),
// ),
// ),
],
));
}