如何解决Draft.js:仅在内容或样式更改时才触发onchange吗?
我想在我的React项目中使用一个所见即所得的编辑器。
Draft.js看起来很棒。但是,我想在编辑器的内容或样式更改后3秒钟将编辑器的内容保存到import 'dart:isolate';
import 'dart:ui';
import 'dart:async';
import 'dart:io';
import 'package:Flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:Flutter_downloader/Flutter_downloader.dart';
import 'package:permission_handler/permission_handler.dart';
const debug = true;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterDownloader.initialize(debug: debug);
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final platform = Theme.of(context).platform;
return new MaterialApp(
title: 'Flutter Demo',theme: new ThemeData(
primarySwatch: Colors.blue,),home: new MyHomePage(
title: 'Downloader',platform: platform,);
}
}
class MyHomePage extends StatefulWidget with WidgetsBindingObserver {
final TargetPlatform platform;
MyHomePage({Key key,this.title,this.platform}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
---------------------------------------------------------------------------------------
final _documents = [
{
'name': 'Learning Android Studio','link':
'http://barbra-coco.dyndns.org/student/learning_android_studio.pdf'
},{
'name': 'Android Programming Cookbook','link':
'http://enos.itcollege.ee/~jpoial/allalaadimised/reading/Android-Programming-Cookbook.pdf'
},{
'name': 'iOS Programming Guide','link':
'http://darwinlogic.com/uploads/education/iOS_Programming_Guide.pdf'
},{
'name': 'Objective-C Programming (Pre-Course Workbook','link':
'https://www.bignerdranch.com/documents/objective-c-prereading-assignment.pdf'
},];
final _images = [
{
'name': 'Arches National Park','link':
'https://upload.wikimedia.org/wikipedia/commons/6/60/The_Organ_at_Arches_National_Park_Utah_Corrected.jpg'
},{
'name': 'Canyonlands National Park','link':
'https://upload.wikimedia.org/wikipedia/commons/7/78/Canyonlands_National_Park%E2%80%A6Needles_area_%286294480744%29.jpg'
},{
'name': 'Death Valley National Park','link':
'https://upload.wikimedia.org/wikipedia/commons/b/b2/Sand_Dunes_in_Death_Valley_National_Park.jpg'
},{
'name': 'Gates of the Arctic National Park and Preserve','link':
'https://upload.wikimedia.org/wikipedia/commons/e/e4/GatesofArctic.jpg'
}
];
final _videos = [
{
'name': 'Big Buck Bunny','link':
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
},{
'name': 'Elephant Dream','link':
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4'
}
];
-------------------------------------------------------------------------------------------
List<_TaskInfo> _tasks;
List<_ItemHolder> _items;
bool _isLoading;
bool _permissionReady;
String _localPath;
ReceivePort _port = ReceivePort();
@override
void initState() {
super.initState();
_bindBackgroundisolate();
FlutterDownloader.registerCallback(downloadCallback);
_isLoading = true;
_permissionReady = false;
_prepare();
}
@override
void dispose() {
_unbindBackgroundisolate();
super.dispose();
}
void _bindBackgroundisolate() {
bool isSuccess = IsolateNameServer.registerPortWithName(
_port.sendPort,'downloader_send_port');
if (!isSuccess) {
_unbindBackgroundisolate();
_bindBackgroundisolate();
return;
}
_port.listen((dynamic data) {
if (debug) {
print('UI Isolate Callback: $data');
}
String id = data[0];
DownloadTaskStatus status = data[1];
int progress = data[2];
final task = _tasks?.firstWhere((task) => task.taskId == id);
if (task != null) {
setState(() {
task.status = status;
task.progress = progress;
});
}
});
}
void _unbindBackgroundisolate() {
IsolateNameServer.removePortNameMapping('downloader_send_port');
}
static void downloadCallback(
String id,DownloadTaskStatus status,int progress) {
if (debug) {
print(
'Background Isolate Callback: task ($id) is in status ($status) and process ($progress)');
}
final SendPort send =
IsolateNameServer.lookupPortByName('downloader_send_port');
send.send([id,status,progress]);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),body: Builder(
builder: (context) => _isLoading
? new Center(
child: new CircularProgressIndicator(),)
: _permissionReady
? _buildDownloadList()
: _buildnopermissionWarning()),);
}
Widget _buildDownloadList() => Container(
child: ListView(
padding: const EdgeInsets.symmetric(vertical: 16.0),children: _items
.map((item) => item.task == null
? _buildListSection(item.name)
: DownloadItem(
data: item,onItemClick: (task) {
_openDownloadedFile(task).then((success) {
if (!success) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Cannot open this file')));
}
});
},onAtionClick: (task) {
if (task.status == DownloadTaskStatus.undefined) {
_requestDownload(task);
} else if (task.status == DownloadTaskStatus.running) {
_pauseDownload(task);
} else if (task.status == DownloadTaskStatus.paused) {
_resumeDownload(task);
} else if (task.status == DownloadTaskStatus.complete) {
_delete(task);
} else if (task.status == DownloadTaskStatus.Failed) {
_retryDownload(task);
}
},))
.toList(),);
Widget _buildListSection(String title) => Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0,vertical: 8.0),child: Text(
title,style: TextStyle(
fontWeight: FontWeight.bold,color: Colors.blue,fontSize: 18.0),);
Widget _buildnopermissionWarning() => Container(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,crossAxisAlignment: CrossAxisAlignment.center,children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),child: Text(
'Please grant accessing storage permission to continue -_-',textAlign: TextAlign.center,style: TextStyle(color: Colors.blueGrey,SizedBox(
height: 32.0,FlatButton(
onpressed: () {
_checkPermission().then((hasGranted) {
setState(() {
_permissionReady = hasGranted;
});
});
},child: Text(
'Retry',style: TextStyle(
color: Colors.blue,fontWeight: FontWeight.bold,fontSize: 20.0),))
],);
void _requestDownload(_TaskInfo task) async {
task.taskId = await FlutterDownloader.enqueue(
url: task.link,headers: {"auth": "test_for_sql_encoding"},savedDir: _localPath,showNotification: true,openFileFromNotification: true);
}
void _cancelDownload(_TaskInfo task) async {
await FlutterDownloader.cancel(taskId: task.taskId);
}
void _pauseDownload(_TaskInfo task) async {
await FlutterDownloader.pause(taskId: task.taskId);
}
void _resumeDownload(_TaskInfo task) async {
String newTaskId = await FlutterDownloader.resume(taskId: task.taskId);
task.taskId = newTaskId;
}
void _retryDownload(_TaskInfo task) async {
String newTaskId = await FlutterDownloader.retry(taskId: task.taskId);
task.taskId = newTaskId;
}
Future<bool> _openDownloadedFile(_TaskInfo task) {
return FlutterDownloader.open(taskId: task.taskId);
}
void _delete(_TaskInfo task) async {
await FlutterDownloader.remove(
taskId: task.taskId,shouldDeleteContent: true);
await _prepare();
setState(() {});
}
Future<bool> _checkPermission() async {
if (widget.platform == TargetPlatform.android) {
final status = await Permission.storage.status;
if (status != PermissionStatus.granted) {
final result = await Permission.storage.request();
if (result == PermissionStatus.granted) {
return true;
}
} else {
return true;
}
} else {
return true;
}
return false;
}
Future<Null> _prepare() async {
final tasks = await FlutterDownloader.loadTasks();
int count = 0;
_tasks = [];
_items = [];
_tasks.addAll(_documents.map((document) =>
_TaskInfo(name: document['name'],link: document['link'])));
_items.add(_ItemHolder(name: 'Documents'));
for (int i = count; i < _tasks.length; i++) {
_items.add(_ItemHolder(name: _tasks[i].name,task: _tasks[i]));
count++;
}
_tasks.addAll(_images
.map((image) => _TaskInfo(name: image['name'],link: image['link'])));
_items.add(_ItemHolder(name: 'Images'));
for (int i = count; i < _tasks.length; i++) {
_items.add(_ItemHolder(name: _tasks[i].name,task: _tasks[i]));
count++;
}
_tasks.addAll(_videos
.map((video) => _TaskInfo(name: video['name'],link: video['link'])));
_items.add(_ItemHolder(name: 'Videos'));
for (int i = count; i < _tasks.length; i++) {
_items.add(_ItemHolder(name: _tasks[i].name,task: _tasks[i]));
count++;
}
tasks?.forEach((task) {
for (_TaskInfo info in _tasks) {
if (info.link == task.url) {
info.taskId = task.taskId;
info.status = task.status;
info.progress = task.progress;
}
}
});
_permissionReady = await _checkPermission();
_localPath = (await _findLocalPath()) + Platform.pathSeparator + 'Download';
final savedDir = Directory(_localPath);
bool hasExisted = await savedDir.exists();
if (!hasExisted) {
savedDir.create();
}
setState(() {
_isLoading = false;
});
}
Future<String> _findLocalPath() async {
final directory = widget.platform == TargetPlatform.android
? await getExternalStorageDirectory()
: await getApplicationDocumentsDirectory();
return directory.path;
}
}
class DownloadItem extends StatelessWidget {
final _ItemHolder data;
final Function(_TaskInfo) onItemClick;
final Function(_TaskInfo) onAtionClick;
DownloadItem({this.data,this.onItemClick,this.onAtionClick});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(left: 16.0,right: 8.0),child: InkWell(
onTap: data.task.status == DownloadTaskStatus.complete
? () {
onItemClick(data.task);
}
: null,child: Stack(
children: <Widget>[
Container(
width: double.infinity,height: 64.0,child: Row(
crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[
Expanded(
child: Text(
data.name,maxLines: 1,softWrap: true,overflow: TextOverflow.ellipsis,Padding(
padding: const EdgeInsets.only(left: 8.0),child: _buildActionForTask(data.task),],data.task.status == DownloadTaskStatus.running ||
data.task.status == DownloadTaskStatus.paused
? Positioned(
left: 0.0,right: 0.0,bottom: 0.0,child: LinearProgressIndicator(
value: data.task.progress / 100,)
: Container()
].where((child) => child != null).toList(),);
}
Widget _buildActionForTask(_TaskInfo task) {
if (task.status == DownloadTaskStatus.undefined) {
return RawMaterialButton(
onpressed: () {
onAtionClick(task);
},child: Icon(Icons.file_download),shape: CircleBorder(),constraints: BoxConstraints(minHeight: 32.0,minWidth: 32.0),);
} else if (task.status == DownloadTaskStatus.running) {
return RawMaterialButton(
onpressed: () {
onAtionClick(task);
},child: Icon(
Icons.pause,color: Colors.red,);
} else if (task.status == DownloadTaskStatus.paused) {
return RawMaterialButton(
onpressed: () {
onAtionClick(task);
},child: Icon(
Icons.play_arrow,color: Colors.green,);
} else if (task.status == DownloadTaskStatus.complete) {
return Row(
mainAxisSize: MainAxisSize.min,mainAxisAlignment: MainAxisAlignment.end,children: [
Text(
'Ready',style: TextStyle(color: Colors.green),RawMaterialButton(
onpressed: () {
onAtionClick(task);
},child: Icon(
Icons.delete_forever,)
],);
} else if (task.status == DownloadTaskStatus.canceled) {
return Text('Canceled',style: TextStyle(color: Colors.red));
} else if (task.status == DownloadTaskStatus.Failed) {
return Row(
mainAxisSize: MainAxisSize.min,children: [
Text('Failed',style: TextStyle(color: Colors.red)),child: Icon(
Icons.refresh,);
} else {
return null;
}
}
}
class _TaskInfo {
final String name;
final String link;
String taskId;
int progress = 0;
DownloadTaskStatus status = DownloadTaskStatus.undefined;
_TaskInfo({this.name,this.link});
}
class _ItemHolder {
final String name;
final _TaskInfo task;
_ItemHolder({this.name,this.task});
}
。
但是localStorage
事件会被触发,即使光标或选择内容发生变化,或者聚焦或聚焦也不会触发。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。