634 lines
22 KiB
Dart
634 lines
22 KiB
Dart
import 'package:crab_ui/api_service.dart';
|
|
import 'package:crab_ui/attachmentDownload.dart';
|
|
import 'package:crab_ui/structs.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
|
import 'dart:html' as html;
|
|
import 'dart:js' as js;
|
|
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
|
import 'attachmentWidget.dart';
|
|
|
|
class EmailToolbar extends StatefulWidget {
|
|
final Function(String) onJumpToSpan;
|
|
final VoidCallback onButtonPressed;
|
|
|
|
EmailToolbar(
|
|
{Key? key, required this.onButtonPressed, required this.onJumpToSpan})
|
|
: super(key: key);
|
|
|
|
@override
|
|
_DynamicClassesAugment createState() => _DynamicClassesAugment();
|
|
}
|
|
|
|
class _DynamicClassesAugment extends State<EmailToolbar> {
|
|
String selectedClass = 'Class 1';
|
|
// TextEditingController _jumpController = TextEditingController();
|
|
|
|
// late final FocusNode _JumpItemfocusNode;
|
|
// late final FocusNode _viewSpecsfocusNode;
|
|
|
|
// bool _jumpItemHasFocus = false;
|
|
// bool _viewSpecsHasFocus = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// _JumpItemfocusNode = FocusNode();
|
|
// _viewSpecsfocusNode = FocusNode();
|
|
|
|
// _JumpItemfocusNode.addListener(() {
|
|
// setState(() => _jumpItemHasFocus = _JumpItemfocusNode.hasFocus);
|
|
// });
|
|
|
|
// _viewSpecsfocusNode.addListener(() {
|
|
// setState(() => _viewSpecsHasFocus = _viewSpecsfocusNode.hasFocus);
|
|
// });
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// _JumpItemfocusNode.dispose();
|
|
// _viewSpecsfocusNode.dispose();
|
|
// _jumpController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// const animationDuration = Duration(milliseconds: 250);
|
|
|
|
return Column(children: [
|
|
Row(
|
|
children: [
|
|
ElevatedButton(
|
|
onPressed: () => AugmentClasses.handleHome(context),
|
|
child: Text('Home'),
|
|
),
|
|
SizedBox(width: 8),
|
|
ElevatedButton(
|
|
onPressed: AugmentClasses.handleReload,
|
|
child: Text('Reload'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () => AugmentClasses.handleImages(context),
|
|
child: Text('Attachments'),
|
|
),
|
|
SizedBox(width: 8),
|
|
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'),
|
|
),
|
|
Spacer(),
|
|
PopupMenuButton<String>(
|
|
onSelected: (String value) {
|
|
setState(() {
|
|
selectedClass = value;
|
|
print(selectedClass);
|
|
});
|
|
},
|
|
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
|
const PopupMenuItem<String>(
|
|
value: 'Class 1',
|
|
child: Text('Class 1'),
|
|
),
|
|
const PopupMenuItem<String>(
|
|
value: 'Class 2',
|
|
child: Text('Class 2'),
|
|
),
|
|
const PopupMenuItem<String>(
|
|
value: 'Turbo 3',
|
|
child: Text('Turbo 3'),
|
|
),
|
|
],
|
|
// child: ElevatedButton(
|
|
// onPressed: () {},
|
|
child: Text('Options'),
|
|
),
|
|
],
|
|
),
|
|
if (selectedClass == 'Class 2')
|
|
Stack(children: [
|
|
Row(
|
|
children: [
|
|
ElevatedButton(
|
|
onPressed: () => AugmentClasses.JumpButton(context),
|
|
child: Text('JumpItem:'),
|
|
),
|
|
// SizedBox(
|
|
// width: 8,
|
|
// ),
|
|
Container(
|
|
width: 50,
|
|
height: 30,
|
|
child: TextField(
|
|
// controller: _jumpController,
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(),
|
|
// suffixIcon: Icon(Icons.search)
|
|
),
|
|
onSubmitted: (value) {
|
|
print("onSubmitted");
|
|
if (value.isNotEmpty) {
|
|
widget.onJumpToSpan(value);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
//TODO: Make an animation to make the button a textfield
|
|
// AnimatedSwitcher(
|
|
// duration: animationDuration,
|
|
// transitionBuilder: (Widget child, Animation<double> animation) {
|
|
// return FadeTransition(opacity: animation, child: child);
|
|
// },
|
|
// child: _jumpItemHasFocus
|
|
// ? Container(
|
|
// key: ValueKey('TextField1'),
|
|
// width: 150,
|
|
// child: TextField(
|
|
// focusNode: _JumpItemfocusNode,
|
|
// decoration: InputDecoration(
|
|
// hintText: 'Enter Text',
|
|
// border: OutlineInputBorder(),
|
|
// ),
|
|
// ),
|
|
|
|
// )
|
|
// : Container(
|
|
// key: ValueKey('Button1'),
|
|
// child: ElevatedButton(
|
|
// onPressed: () => _JumpItemfocusNode.requestFocus(),
|
|
// child: Text('Jump Item:'),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
SizedBox(width: 8),
|
|
ElevatedButton(
|
|
onPressed: () => AugmentClasses.ViewSpecsButton(context),
|
|
child: Text('ViewSpecs:')),
|
|
Container(
|
|
width: 50,
|
|
height: 30,
|
|
child: TextField(
|
|
decoration: InputDecoration(
|
|
labelText: '',
|
|
border: OutlineInputBorder(),
|
|
// suffixIcon: Icon(Icons.style_rounded)
|
|
),
|
|
),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () => AugmentClasses.FilterButton(context),
|
|
child: Text('Filter'),
|
|
),
|
|
SizedBox(width: 8),
|
|
ElevatedButton(
|
|
onPressed: AugmentClasses.handleOpen,
|
|
child: Text('Lookup'),
|
|
),
|
|
// SizedBox(width: 8),
|
|
ElevatedButton(
|
|
onPressed: AugmentClasses.handleFind,
|
|
child: Text('Create Link'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: AugmentClasses.handleFind,
|
|
child: Text('Paste Link'),
|
|
),
|
|
],
|
|
)
|
|
])
|
|
]);
|
|
}
|
|
}
|
|
|
|
class AugmentClasses {
|
|
ApiService _apiService = ApiService();
|
|
static OverlayEntry? _overlayEntry;
|
|
static void handleHome(BuildContext context) {
|
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
|
}
|
|
|
|
static void handleReload() {
|
|
print("reload");
|
|
}
|
|
|
|
static void handleImages(BuildContext context) {
|
|
print("Images button pressed");
|
|
final overlay = Overlay.of(context);
|
|
final renderBox = context.findRenderObject() as RenderBox;
|
|
final offset = renderBox.localToGlobal(Offset.zero);
|
|
|
|
_overlayEntry = OverlayEntry(
|
|
builder: (context) => Stack(
|
|
children: [
|
|
// Dimmed background
|
|
Container(
|
|
color: Colors.black54,
|
|
width: MediaQuery.of(context).size.width,
|
|
height: MediaQuery.of(context).size.height,
|
|
),
|
|
// Focused content window
|
|
PointerInterceptor(
|
|
child: Center(
|
|
child: Material(
|
|
elevation: 8,
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(
|
|
maxWidth: 400,
|
|
maxHeight: 500,
|
|
),
|
|
child: Column(
|
|
children: [
|
|
_buildHeader(context),
|
|
const Divider(height: 1),
|
|
Expanded(
|
|
child: ListView(
|
|
children: _buildMenuItem(context),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
if (_overlayEntry != null) {
|
|
overlay.insert(_overlayEntry!);
|
|
}
|
|
}
|
|
|
|
// Add missing widget builder methods
|
|
static Widget _buildHeader(BuildContext context) {
|
|
return Padding(
|
|
padding: EdgeInsets.all(16.0),
|
|
child:
|
|
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
|
Text(
|
|
'Thread Attachments',
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
CloseButton(
|
|
onPressed: () {
|
|
_overlayEntry?.remove();
|
|
},
|
|
),
|
|
]));
|
|
}
|
|
|
|
static List<Widget> _buildMenuItem(BuildContext context) {
|
|
List<Widget> listOfFiles = [];
|
|
for (AttachmentResponse file in ApiService.threadAttachments) {
|
|
listOfFiles.add(
|
|
ListTile (
|
|
leading: Icon(Icons.file_present),
|
|
title: Text(file.name.toString()),
|
|
trailing: GestureDetector(
|
|
child: Icon(Icons.download),
|
|
onTap: () => Attachmentdownload().saveFile(file),
|
|
),
|
|
onTap: () {
|
|
_overlayEntry?.remove();
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => AttachmentWidget(attachment: file)));
|
|
}));
|
|
}
|
|
return listOfFiles;
|
|
}
|
|
|
|
static void handleOpen() {
|
|
print("Open button pressed");
|
|
}
|
|
|
|
static void handleFind() {
|
|
print("Find button pressed");
|
|
}
|
|
|
|
static void handleStop() {
|
|
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 invisibility(String htmlClass) {}
|
|
|
|
static Future<void> JumpButton(BuildContext context) async {
|
|
// FocusNode textFieldFocusNode = FocusNode();
|
|
|
|
AugmentClasses.disableIframePointerEvents();
|
|
await showDialog(
|
|
barrierDismissible: true,
|
|
// barrierColor: Colors.yellow,
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text('Jump Item:'),
|
|
content: Container(
|
|
width: 300,
|
|
height: 170,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text('Jump (to) Item (at)'),
|
|
SizedBox(height: 10),
|
|
TextField(
|
|
autofocus: true,
|
|
decoration: InputDecoration(
|
|
labelText: '...',
|
|
border: OutlineInputBorder(),
|
|
suffixIcon: Icon(Icons.search)),
|
|
onSubmitted: (value) {
|
|
print("onSubmitted: $value");
|
|
if (value.isNotEmpty) {
|
|
handleJump(value);
|
|
Navigator.of(context).pop();
|
|
}
|
|
},
|
|
),
|
|
Spacer(
|
|
flex: 5,
|
|
),
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ElevatedButton(
|
|
onPressed: () => AugmentClasses.ViewSpecsButton(context),
|
|
child: Text("Viewspecs:"),
|
|
),
|
|
SizedBox(
|
|
width: 150,
|
|
child: TextField(
|
|
maxLines: 1,
|
|
decoration: InputDecoration(
|
|
labelText: '',
|
|
border: OutlineInputBorder(),
|
|
suffixIcon: Icon(Icons.search)),
|
|
onSubmitted: (value) {
|
|
print("onSubmitted: $value");
|
|
if (value.isNotEmpty) {
|
|
handleJump(value);
|
|
Navigator.of(context).pop();
|
|
}
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
//TODO: Grab both textfields and call both of the functions handles
|
|
},
|
|
child: Text('OK')),
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
// print('close pressed');
|
|
},
|
|
child: Text('Cancel'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
//TODO: in the ui demo didn't see it
|
|
},
|
|
child: Text('Help'))
|
|
],
|
|
),
|
|
).then((_) {
|
|
AugmentClasses.enableIframePointerEvents();
|
|
});
|
|
}
|
|
|
|
static Future<void> ViewSpecsButton(context) async {
|
|
//TODO: finish it
|
|
|
|
bool blankLines = false;
|
|
bool numbering = false;
|
|
bool statementSignatures = false;
|
|
AugmentClasses.disableIframePointerEvents();
|
|
await showDialog(
|
|
context: context,
|
|
builder: (context) => Container(
|
|
height: 150,
|
|
width: 300,
|
|
child: AlertDialog(
|
|
title: Text('Viewspecs(short)'),
|
|
content: Container(
|
|
width: 400, // Set the width to simulate the Windows style
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
// First section: Checkboxes for "Show"
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Show'),
|
|
Row(
|
|
children: [
|
|
Text("y z"),
|
|
Checkbox(
|
|
value: blankLines,
|
|
onChanged: (bool? value) {
|
|
blankLines = value!;
|
|
},
|
|
),
|
|
Text('Blank lines'),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
Text('m n'),
|
|
Checkbox(
|
|
value: numbering,
|
|
onChanged: (bool? value) {
|
|
numbering = value!;
|
|
},
|
|
),
|
|
Text('Numbering'),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
Text('K L'),
|
|
Checkbox(
|
|
value: statementSignatures,
|
|
onChanged: (bool? value) {
|
|
statementSignatures = value!;
|
|
},
|
|
),
|
|
Text('Statement signatures'),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// Second section: Numeric input for Outline, Levels, and Lines
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Outline'),
|
|
Row(
|
|
children: [
|
|
Text('Levels'),
|
|
SizedBox(width: 10),
|
|
Container(
|
|
width: 40,
|
|
child: TextField(
|
|
decoration: InputDecoration(
|
|
isDense: true,
|
|
border: OutlineInputBorder(),
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
Text('Lines'),
|
|
SizedBox(width: 10),
|
|
Container(
|
|
width: 40,
|
|
child: TextField(
|
|
decoration: InputDecoration(
|
|
isDense: true,
|
|
border: OutlineInputBorder(),
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 20),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
ElevatedButton(onPressed: () {}, child: Text('OK')),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
AugmentClasses.disableIframePointerEvents();
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: Text('Cancel')),
|
|
ElevatedButton(
|
|
onPressed: () {}, child: Text('Reset')),
|
|
ElevatedButton(onPressed: () {}, child: Text('Help')),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
)).then((_) {
|
|
AugmentClasses.enableIframePointerEvents(); // may be useless?
|
|
});
|
|
}
|
|
|
|
void handleFilter() {}
|
|
static Future<void> FilterButton(context) async {
|
|
//this is literally ctrl+F :skull:
|
|
//idea is to search in file, extract the <p> tags that contain these
|
|
//words and highlight, then when zoom, you just jump to that paragraph
|
|
|
|
AugmentClasses.disableIframePointerEvents();
|
|
await showDialog(
|
|
context: context,
|
|
builder: (context) => Container(
|
|
height: 150,
|
|
width: 300,
|
|
child: AlertDialog(
|
|
title: Text('Filter'),
|
|
content: Container(
|
|
width: 400, // Set the width to simulate the Windows style
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Set filter:'),
|
|
SizedBox(
|
|
width: 175,
|
|
child: TextField(
|
|
maxLines: 1,
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
)))));
|
|
}
|
|
|
|
static void disableIframePointerEvents() {
|
|
//pretty sure these dont work
|
|
final iframes = html.document.getElementsByTagName('iframe');
|
|
for (var iframe in iframes) {
|
|
if (iframe is html.Element) {
|
|
iframe.style.pointerEvents = 'none'; // Disable pointer events
|
|
}
|
|
}
|
|
}
|
|
|
|
static void enableIframePointerEvents() {
|
|
final iframes = html.document.getElementsByTagName('iframe');
|
|
for (var iframe in iframes) {
|
|
if (iframe is html.Element) {
|
|
iframe.style.pointerEvents = 'auto'; // Re-enable pointer events
|
|
}
|
|
}
|
|
}
|
|
}
|