feat: drop images into editor (#5813)

This commit is contained in:
Mathias Mogensen 2024-07-26 00:08:55 +02:00 committed by GitHub
parent 23b6f94e82
commit d1af172fb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 125 additions and 21 deletions

View File

@ -6,7 +6,10 @@ import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_notification.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/startup/startup.dart';
@ -16,10 +19,17 @@ import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:desktop_drop/desktop_drop.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
const _excludeFromDropTarget = [
ImageBlockKeys.type,
CustomImageBlockKeys.type,
MultiImageBlockKeys.type,
];
class DocumentPage extends StatefulWidget {
const DocumentPage({
super.key,
@ -125,7 +135,53 @@ class _DocumentPageState extends State<DocumentPage>
},
);
} else {
child = AppFlowyEditorPage(
child = DropTarget(
onDragExited: (_) =>
state.editorState!.selectionService.removeDropTarget(),
onDragUpdated: (details) {
final data = state.editorState!.selectionService
.getDropTargetRenderData(details.globalPosition);
if (data != null &&
data.dropTarget != null &&
// We implement custom Drop logic for image blocks, this is
// how we can exclude them from the Drop Target
!_excludeFromDropTarget.contains(data.cursorNode?.type)) {
// Render the drop target
state.editorState!.selectionService
.renderDropTargetForOffset(details.globalPosition);
} else {
state.editorState!.selectionService.removeDropTarget();
}
},
onDragDone: (details) async {
state.editorState!.selectionService.removeDropTarget();
final data = state.editorState!.selectionService
.getDropTargetRenderData(details.globalPosition);
if (data != null) {
if (data.cursorNode != null) {
if ([
ImageBlockKeys.type,
CustomImageBlockKeys.type,
MultiImageBlockKeys.type,
].contains(data.cursorNode?.type)) {
return;
}
final isLocalMode = context.read<DocumentBloc>().isLocalMode;
await editorState!.dropImages(
data.dropTarget!,
details.files,
widget.view.id,
isLocalMode,
);
}
}
},
child: AppFlowyEditorPage(
editorState: state.editorState!,
styleCustomizer: EditorStyleCustomizer(
context: context,
@ -134,6 +190,7 @@ class _DocumentPageState extends State<DocumentPage>
),
header: _buildCoverAndIcon(context, state),
initialSelection: widget.initialSelection,
),
);
}

View File

@ -353,6 +353,10 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
},
child: VSpace(PlatformExtension.isDesktopOrWeb ? 200 : 400),
),
dropTargetStyle: AppFlowyDropTargetStyle(
color: Theme.of(context).colorScheme.primary.withOpacity(0.8),
margin: const EdgeInsets.only(left: 44),
),
),
);

View File

@ -3,12 +3,16 @@ import 'dart:typed_data';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';
import 'package:appflowy/shared/patterns/common_patterns.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:cross_file/cross_file.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:path/path.dart' as p;
@ -21,6 +25,42 @@ extension PasteFromImage on EditorState {
'gif',
];
Future<void> dropImages(
Node dropNode,
List<XFile> files,
String documentId,
bool isLocalMode,
) async {
final imageFiles = files.where(
(file) =>
file.mimeType?.startsWith('image/') ??
false || imgExtensionRegex.hasMatch(file.name),
);
for (final file in imageFiles) {
String? path;
CustomImageType? type;
if (isLocalMode) {
path = await saveImageToLocalStorage(file.path);
type = CustomImageType.local;
} else {
(path, _) = await saveImageToCloudStorage(file.path, documentId);
type = CustomImageType.internal;
}
if (path == null) {
continue;
}
final t = transaction
..insertNode(
dropNode.path,
customImageNode(url: path, type: type),
);
await apply(t);
}
}
Future<bool> pasteImage(
String format,
Uint8List imageBytes,

View File

@ -53,8 +53,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "61d4363"
resolved-ref: "61d4363b4675f7ef91ef5003f2f88bbcb4d9dfa9"
ref: "268aae9"
resolved-ref: "268aae905b18efc8a3a9c88dc75ebd19b314bd43"
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git
version: "3.1.0"
@ -387,7 +387,7 @@ packages:
source: hosted
version: "1.7.2"
cross_file:
dependency: transitive
dependency: "direct main"
description:
name: cross_file
sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32"
@ -1118,18 +1118,18 @@ packages:
dependency: transitive
description:
name: irondash_engine_context
sha256: "4f5e2629296430cce08cdff42e47cef07b8f74a64fdbdfb0525d147bc1a969a2"
sha256: cd7b769db11a2b5243b037c8a9b1ecaef02e1ae27a2d909ffa78c1dad747bb10
url: "https://pub.dev"
source: hosted
version: "0.5.2"
version: "0.5.4"
irondash_message_channel:
dependency: transitive
description:
name: irondash_message_channel
sha256: dd581214215dca054bd9873209d690ec3609288c28774cb509dbd86b21180cf8
sha256: b4101669776509c76133b8917ab8cfc704d3ad92a8c450b92934dd8884a2f060
url: "https://pub.dev"
source: hosted
version: "0.6.0"
version: "0.7.0"
isolates:
dependency: "direct main"
description:
@ -2053,18 +2053,18 @@ packages:
dependency: "direct main"
description:
name: super_clipboard
sha256: "15d25eb88df8e904e0c2ef77378c6010cc57bbfc0b6f91f2416d08fad5fcca92"
sha256: c72d2ae8c3a66b20a104523add86b7c2813b1d4cced893a9764b84fb97ac8e2a
url: "https://pub.dev"
source: hosted
version: "0.8.5"
version: "0.8.18"
super_native_extensions:
dependency: transitive
description:
name: super_native_extensions
sha256: "530a2118d032483b192713c68ed7105fe64418f22492165f87ed01f9b01d4965"
sha256: b03f19e54744b65940a7c2cb4f93abd4819b5355aa3464d7b3c9a013b6b76db1
url: "https://pub.dev"
source: hosted
version: "0.8.12"
version: "0.8.18"
sync_http:
dependency: transitive
description:

View File

@ -142,10 +142,13 @@ dependencies:
shimmer: ^3.0.0
isolates: ^3.0.3+8
markdown_widget: ^2.3.2+6
desktop_drop: ^0.4.4
markdown:
logger: ^2.4.0
# Desktop Drop uses Cross File (XFile) data type
desktop_drop: ^0.4.4
cross_file: ^0.3.4+1
# Window Manager for MacOS and Linux
window_manager: ^0.3.9
@ -191,7 +194,7 @@ dependency_overrides:
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: "61d4363"
ref: "268aae9"
appflowy_editor_plugins:
git: