mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: init flowy document 2 (#2248)
* feat: init flowy document 2 * feat: convert inner document to document PB * feat: integrate colla document into Flutter * feat: integrate colla document into tauri * fix: cargo clippy
This commit is contained in:
parent
c7eb490db4
commit
ec89e9517b
@ -0,0 +1,24 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy/core/notification_helper.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/notification.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
typedef DocumentNotificationCallback = void Function(
|
||||
DocumentNotification,
|
||||
Either<Uint8List, FlowyError>,
|
||||
);
|
||||
|
||||
class DocumentNotificationParser
|
||||
extends NotificationParser<DocumentNotification, FlowyError> {
|
||||
DocumentNotificationParser({
|
||||
String? id,
|
||||
required DocumentNotificationCallback callback,
|
||||
}) : super(
|
||||
id: id,
|
||||
callback: callback,
|
||||
tyParser: (ty) => DocumentNotification.valueOf(ty),
|
||||
errorParser: (bytes) => FlowyError.fromBuffer(bytes),
|
||||
);
|
||||
}
|
@ -3,6 +3,7 @@ import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_
|
||||
import 'package:appflowy/plugins/trash/application/trash_service.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||
import 'package:appflowy/workspace/application/doc/doc_listener.dart';
|
||||
import 'package:appflowy/plugins/document/application/doc_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
|
||||
@ -17,12 +18,13 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'dart:async';
|
||||
import 'package:appflowy/util/either_extension.dart';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
||||
part 'doc_bloc.freezed.dart';
|
||||
|
||||
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
final ViewPB view;
|
||||
final DocumentService _documentService;
|
||||
final DocumentListener _docListener;
|
||||
|
||||
final ViewListener _listener;
|
||||
final TrashService _trashService;
|
||||
@ -32,12 +34,14 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
DocumentBloc({
|
||||
required this.view,
|
||||
}) : _documentService = DocumentService(),
|
||||
_docListener = DocumentListener(id: view.id),
|
||||
_listener = ViewListener(view: view),
|
||||
_trashService = TrashService(),
|
||||
super(DocumentState.initial()) {
|
||||
on<DocumentEvent>((event, emit) async {
|
||||
await event.map(
|
||||
initial: (Initial value) async {
|
||||
_listenOnDocChange();
|
||||
await _initial(value, emit);
|
||||
_listenOnViewChange();
|
||||
},
|
||||
@ -73,6 +77,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
}
|
||||
|
||||
await _documentService.closeDocument(docId: view.id);
|
||||
await _documentService.closeDocumentV2(view: view);
|
||||
return super.close();
|
||||
}
|
||||
|
||||
@ -88,6 +93,39 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
);
|
||||
}
|
||||
final result = await _documentService.openDocument(view: view);
|
||||
// test code
|
||||
final document = await _documentService.openDocumentV2(view: view);
|
||||
BlockPB? root;
|
||||
document.fold((l) {
|
||||
print('---------<open document v2>-----------');
|
||||
print('page id = ${l.pageId}');
|
||||
l.blocks.blocks.forEach((key, value) {
|
||||
print('-----<block begin>-----');
|
||||
print('block = $value');
|
||||
if (value.ty == 'page') {
|
||||
root = value;
|
||||
}
|
||||
print('-----<block end>-----');
|
||||
});
|
||||
print('---------<open document v2>-----------');
|
||||
}, (r) {});
|
||||
if (root != null) {
|
||||
await _documentService.applyAction(
|
||||
view: view,
|
||||
actions: [
|
||||
BlockActionPB(
|
||||
action: BlockActionTypePB.Insert,
|
||||
payload: BlockActionPayloadPB(
|
||||
block: BlockPB()
|
||||
..id = 'id_0'
|
||||
..ty = 'text'
|
||||
..parentId = root!.id,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return result.fold(
|
||||
(documentData) async {
|
||||
await _initEditorState(documentData).whenComplete(() {
|
||||
@ -126,6 +164,14 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
);
|
||||
}
|
||||
|
||||
void _listenOnDocChange() {
|
||||
_docListener.start(
|
||||
didReceiveUpdate: () {
|
||||
print('---------<receive document update>-----------');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _initEditorState(DocumentDataPB documentData) async {
|
||||
final document = Document.fromJson(jsonDecode(documentData.content));
|
||||
final editorState = EditorState(document: document);
|
||||
|
@ -4,6 +4,7 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
||||
|
||||
class DocumentService {
|
||||
Future<Either<DocumentDataPB, FlowyError>> openDocument({
|
||||
@ -39,4 +40,32 @@ class DocumentService {
|
||||
final payload = ViewIdPB(value: docId);
|
||||
return FolderEventCloseView(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<DocumentDataPB2, FlowyError>> openDocumentV2({
|
||||
required ViewPB view,
|
||||
}) async {
|
||||
await FolderEventSetLatestView(ViewIdPB(value: view.id)).send();
|
||||
|
||||
final payload = OpenDocumentPayloadPBV2()..documentId = view.id;
|
||||
|
||||
return DocumentEvent2OpenDocument(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> closeDocumentV2({
|
||||
required ViewPB view,
|
||||
}) async {
|
||||
final payload = CloseDocumentPayloadPBV2()..documentId = view.id;
|
||||
return DocumentEvent2CloseDocument(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> applyAction({
|
||||
required ViewPB view,
|
||||
required List<BlockActionPB> actions,
|
||||
}) async {
|
||||
final payload = ApplyActionPayloadPBV2(
|
||||
documentId: view.id,
|
||||
actions: actions,
|
||||
);
|
||||
return DocumentEvent2ApplyAction(payload).send();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:appflowy/core/document_notification.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/subject.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/notification.pb.dart';
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
|
||||
class DocumentListener {
|
||||
DocumentListener({
|
||||
required this.id,
|
||||
});
|
||||
|
||||
final String id;
|
||||
|
||||
final _didReceiveUpdate = PublishNotifier();
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
DocumentNotificationParser? _parser;
|
||||
|
||||
Function()? didReceiveUpdate;
|
||||
|
||||
void start({
|
||||
void Function()? didReceiveUpdate,
|
||||
}) {
|
||||
this.didReceiveUpdate = didReceiveUpdate;
|
||||
|
||||
_parser = DocumentNotificationParser(
|
||||
id: id,
|
||||
callback: _callback,
|
||||
);
|
||||
_subscription = RustStreamReceiver.listen(
|
||||
(observable) => _parser?.parse(observable),
|
||||
);
|
||||
}
|
||||
|
||||
void _callback(
|
||||
DocumentNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case DocumentNotification.DidReceiveUpdate:
|
||||
didReceiveUpdate?.call();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _subscription?.cancel();
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/dart-ffi/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart';
|
||||
|
||||
// ignore: unused_import
|
||||
@ -30,6 +31,7 @@ part 'dart_event/flowy-net/dart_event.dart';
|
||||
part 'dart_event/flowy-user/dart_event.dart';
|
||||
part 'dart_event/flowy-database/dart_event.dart';
|
||||
part 'dart_event/flowy-document/dart_event.dart';
|
||||
part 'dart_event/flowy-document2/dart_event.dart';
|
||||
|
||||
enum FFIException {
|
||||
RequestIsEmpty,
|
||||
|
48
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
48
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -564,7 +564,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#9b3f895bb6f8e92830acd90cfb68b69aece83095"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#4ccf3e5cf88ee71f1ce257e587cca54a8173abae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -582,7 +582,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#9b3f895bb6f8e92830acd90cfb68b69aece83095"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#4ccf3e5cf88ee71f1ce257e587cca54a8173abae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -591,10 +591,26 @@ dependencies = [
|
||||
"yrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#4ccf3e5cf88ee71f1ce257e587cca54a8173abae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
"collab-derive",
|
||||
"collab-persistence",
|
||||
"nanoid",
|
||||
"parking_lot 0.12.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#9b3f895bb6f8e92830acd90cfb68b69aece83095"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#4ccf3e5cf88ee71f1ce257e587cca54a8173abae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -612,7 +628,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-persistence"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#9b3f895bb6f8e92830acd90cfb68b69aece83095"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#4ccf3e5cf88ee71f1ce257e587cca54a8173abae"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
@ -1284,6 +1300,7 @@ dependencies = [
|
||||
"flowy-client-ws",
|
||||
"flowy-database",
|
||||
"flowy-document",
|
||||
"flowy-document2",
|
||||
"flowy-error",
|
||||
"flowy-folder",
|
||||
"flowy-folder2",
|
||||
@ -1407,6 +1424,28 @@ dependencies = [
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-document2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"collab",
|
||||
"collab-document",
|
||||
"collab-persistence",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-error",
|
||||
"flowy-notification",
|
||||
"lib-dispatch",
|
||||
"nanoid",
|
||||
"parking_lot 0.12.1",
|
||||
"protobuf",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-error"
|
||||
version = "0.1.0"
|
||||
@ -1511,6 +1550,7 @@ dependencies = [
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-document",
|
||||
"flowy-document2",
|
||||
"flowy-error",
|
||||
"flowy-folder2",
|
||||
"flowy-server-sync",
|
||||
|
@ -36,9 +36,13 @@ custom-protocol = ["tauri/custom-protocol"]
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
|
||||
|
||||
#collab = { path = "../../AppFlowy-Collab/collab" }
|
||||
#collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }
|
||||
#collab-persistence = { path = "../../AppFlowy-Collab/collab-persistence" }
|
||||
#collab-document = { path = "../../AppFlowy-Collab/collab-document" }
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -4,11 +4,22 @@ import {
|
||||
EditPayloadPB,
|
||||
FlowyError,
|
||||
OpenDocumentPayloadPB,
|
||||
DocumentDataPB2,
|
||||
ViewIdPB,
|
||||
OpenDocumentPayloadPBV2,
|
||||
ApplyActionPayloadPBV2,
|
||||
BlockActionTypePB,
|
||||
BlockActionPB,
|
||||
CloseDocumentPayloadPBV2,
|
||||
} from '@/services/backend';
|
||||
import { DocumentEventApplyEdit, DocumentEventGetDocument } from '@/services/backend/events/flowy-document';
|
||||
import { Result } from 'ts-results';
|
||||
import { FolderEventCloseView } from '@/services/backend/events/flowy-folder2';
|
||||
import {
|
||||
DocumentEvent2ApplyAction,
|
||||
DocumentEvent2CloseDocument,
|
||||
DocumentEvent2OpenDocument,
|
||||
} from '@/services/backend/events/flowy-document2';
|
||||
|
||||
export class DocumentBackendService {
|
||||
constructor(public readonly viewId: string) {}
|
||||
@ -27,4 +38,26 @@ export class DocumentBackendService {
|
||||
const payload = ViewIdPB.fromObject({ value: this.viewId });
|
||||
return FolderEventCloseView(payload);
|
||||
};
|
||||
|
||||
openV2 = (): Promise<Result<DocumentDataPB2, FlowyError>> => {
|
||||
const payload = OpenDocumentPayloadPBV2.fromObject({
|
||||
document_id: this.viewId,
|
||||
});
|
||||
return DocumentEvent2OpenDocument(payload);
|
||||
};
|
||||
|
||||
applyActions = (actions: [BlockActionPB]): Promise<Result<void, FlowyError>> => {
|
||||
const payload = ApplyActionPayloadPBV2.fromObject({
|
||||
document_id: this.viewId,
|
||||
actions: actions,
|
||||
});
|
||||
return DocumentEvent2ApplyAction(payload);
|
||||
};
|
||||
|
||||
closeV2 = (): Promise<Result<void, FlowyError>> => {
|
||||
const payload = CloseDocumentPayloadPBV2.fromObject({
|
||||
document_id: this.viewId,
|
||||
});
|
||||
return DocumentEvent2CloseDocument(payload);
|
||||
};
|
||||
}
|
||||
|
@ -2,37 +2,70 @@ import { DocumentData, BlockType, TextDelta } from '@/appflowy_app/interfaces/do
|
||||
import { createContext } from 'react';
|
||||
import { DocumentBackendService } from './document_bd_svc';
|
||||
import { Err } from 'ts-results';
|
||||
import { FlowyError } from '@/services/backend';
|
||||
import { BlockActionPB, BlockActionPayloadPB, BlockActionTypePB, BlockPB, FlowyError } from '@/services/backend';
|
||||
import { DocumentObserver } from './document_observer';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export const DocumentControllerContext = createContext<DocumentController | null>(null);
|
||||
|
||||
export class DocumentController {
|
||||
private readonly backendService: DocumentBackendService;
|
||||
private readonly observer: DocumentObserver;
|
||||
|
||||
constructor(public readonly viewId: string) {
|
||||
this.backendService = new DocumentBackendService(viewId);
|
||||
this.observer = new DocumentObserver(viewId);
|
||||
}
|
||||
|
||||
open = async (): Promise<DocumentData | null> => {
|
||||
// example:
|
||||
await this.observer.subscribe({
|
||||
didReceiveUpdate: () => {
|
||||
console.log('didReceiveUpdate');
|
||||
},
|
||||
});
|
||||
|
||||
const document = await this.backendService.openV2();
|
||||
let root_id = '';
|
||||
if (document.ok) {
|
||||
root_id = document.val.page_id;
|
||||
console.log(document.val.blocks);
|
||||
}
|
||||
await this.backendService.applyActions([
|
||||
BlockActionPB.fromObject({
|
||||
action: BlockActionTypePB.Insert,
|
||||
payload: BlockActionPayloadPB.fromObject({
|
||||
block: BlockPB.fromObject({
|
||||
id: nanoid(10),
|
||||
ty: 'text',
|
||||
parent_id: root_id,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
||||
const openDocumentResult = await this.backendService.open();
|
||||
if (openDocumentResult.ok) {
|
||||
return {
|
||||
rootId: '',
|
||||
blocks: {},
|
||||
ytexts: {},
|
||||
yarrays: {}
|
||||
yarrays: {},
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
insert(node: {
|
||||
id: string,
|
||||
type: BlockType,
|
||||
delta?: TextDelta[]
|
||||
}, parentId: string, prevId: string) {
|
||||
insert(
|
||||
node: {
|
||||
id: string;
|
||||
type: BlockType;
|
||||
delta?: TextDelta[];
|
||||
},
|
||||
parentId: string,
|
||||
prevId: string
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
@ -42,7 +75,7 @@ export class DocumentController {
|
||||
|
||||
yTextApply = (yTextId: string, delta: TextDelta[]) => {
|
||||
//
|
||||
}
|
||||
};
|
||||
|
||||
dispose = async () => {
|
||||
await this.backendService.close();
|
||||
|
@ -0,0 +1,36 @@
|
||||
import { Ok, Result } from 'ts-results';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
import { FolderNotificationObserver } from '../folder/notifications/observer';
|
||||
import { DocumentNotification } from '@/services/backend';
|
||||
import { DocumentNotificationObserver } from './notifications/observer';
|
||||
|
||||
export type DidReceiveUpdateCallback = () => void; // todo: add params
|
||||
|
||||
export class DocumentObserver {
|
||||
private listener?: DocumentNotificationObserver;
|
||||
|
||||
constructor(public readonly workspaceId: string) {}
|
||||
|
||||
subscribe = async (callbacks: { didReceiveUpdate: DidReceiveUpdateCallback }) => {
|
||||
this.listener = new DocumentNotificationObserver({
|
||||
viewId: this.workspaceId,
|
||||
parserHandler: (notification, result) => {
|
||||
switch (notification) {
|
||||
case DocumentNotification.DidReceiveUpdate:
|
||||
callbacks.didReceiveUpdate();
|
||||
// Fixme: ...
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
await this.listener.start();
|
||||
};
|
||||
|
||||
unsubscribe = async () => {
|
||||
this.appListNotifier.unsubscribe();
|
||||
this.workspaceNotifier.unsubscribe();
|
||||
await this.listener?.stop();
|
||||
};
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { OnNotificationError, AFNotificationObserver } from '@/services/backend/notifications';
|
||||
import { DocumentNotificationParser } from './parser';
|
||||
import { FlowyError, DocumentNotification } from '@/services/backend';
|
||||
import { Result } from 'ts-results';
|
||||
|
||||
export type ParserHandler = (notification: DocumentNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
||||
|
||||
export class DocumentNotificationObserver extends AFNotificationObserver<DocumentNotification> {
|
||||
constructor(params: { viewId?: string; parserHandler: ParserHandler; onError?: OnNotificationError }) {
|
||||
const parser = new DocumentNotificationParser({
|
||||
callback: params.parserHandler,
|
||||
id: params.viewId,
|
||||
});
|
||||
super(parser);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { NotificationParser, OnNotificationError } from '@/services/backend/notifications';
|
||||
import { FlowyError, DocumentNotification } from '@/services/backend';
|
||||
import { Result } from 'ts-results';
|
||||
|
||||
declare type DocumentNotificationCallback = (ty: DocumentNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
||||
|
||||
export class DocumentNotificationParser extends NotificationParser<DocumentNotification> {
|
||||
constructor(params: { id?: string; callback: DocumentNotificationCallback; onError?: OnNotificationError }) {
|
||||
super(
|
||||
params.callback,
|
||||
(ty) => {
|
||||
const notification = DocumentNotification[ty];
|
||||
if (isDocumentNotification(notification)) {
|
||||
return DocumentNotification[notification];
|
||||
} else {
|
||||
return DocumentNotification.Unknown;
|
||||
}
|
||||
},
|
||||
params.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const isDocumentNotification = (notification: string): notification is keyof typeof DocumentNotification => {
|
||||
return Object.values(DocumentNotification).indexOf(notification) !== -1;
|
||||
};
|
@ -2,5 +2,6 @@ export * from "./models/flowy-user";
|
||||
export * from "./models/flowy-document";
|
||||
export * from "./models/flowy-database";
|
||||
export * from "./models/flowy-folder2";
|
||||
export * from "./models/flowy-document2";
|
||||
export * from "./models/flowy-net";
|
||||
export * from "./models/flowy-error";
|
||||
|
948
frontend/rust-lib/Cargo.lock
generated
948
frontend/rust-lib/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@ members = [
|
||||
# "flowy-folder",r
|
||||
"flowy-folder2",
|
||||
"flowy-notification",
|
||||
"flowy-document2",
|
||||
"flowy-document",
|
||||
"flowy-error",
|
||||
"flowy-revision",
|
||||
@ -38,9 +39,12 @@ opt-level = 3
|
||||
incremental = false
|
||||
|
||||
[patch.crates-io]
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
|
||||
#collab = { path = "../AppFlowy-Collab/collab" }
|
||||
#collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
|
||||
#collab-persistence = { path = "../AppFlowy-Collab/collab-persistence" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", branch = "main" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", branch = "main" }
|
||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", branch = "main" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", branch = "main" }
|
||||
|
||||
# collab = { path = "../AppFlowy-Collab/collab" }
|
||||
# collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
|
||||
# collab-persistence = { path = "../AppFlowy-Collab/collab-persistence" }
|
||||
# collab-document = { path = "../AppFlowy-Collab/collab-document" }
|
@ -18,6 +18,7 @@ user-model = { path = "../../../shared-lib/user-model" }
|
||||
flowy-client-ws = { path = "../../../shared-lib/flowy-client-ws" }
|
||||
flowy-sqlite = { path = "../flowy-sqlite", optional = true }
|
||||
flowy-document = { path = "../flowy-document" }
|
||||
flowy-document2 = { path = "../flowy-document2" }
|
||||
flowy-revision = { path = "../flowy-revision" }
|
||||
flowy-error = { path = "../flowy-error", features = ["adaptor_ws"] }
|
||||
flowy-task = { path = "../flowy-task" }
|
||||
@ -50,6 +51,7 @@ dart = [
|
||||
"flowy-folder2/dart",
|
||||
"flowy-database/dart",
|
||||
"flowy-document/dart",
|
||||
"flowy-document2/dart",
|
||||
]
|
||||
ts = [
|
||||
"flowy-user/ts",
|
||||
@ -58,6 +60,7 @@ ts = [
|
||||
"flowy-folder2/ts",
|
||||
"flowy-database/ts",
|
||||
"flowy-document/ts",
|
||||
"flowy-document2/ts",
|
||||
]
|
||||
rev-sqlite = [
|
||||
"flowy-sqlite",
|
||||
|
@ -0,0 +1,40 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_persistence::CollabKV;
|
||||
use flowy_database::manager::DatabaseManager;
|
||||
use flowy_document2::manager::{DocumentManager as DocumentManager2, DocumentUser};
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_user::services::UserSession;
|
||||
|
||||
pub struct Document2DepsResolver();
|
||||
impl Document2DepsResolver {
|
||||
pub fn resolve(
|
||||
user_session: Arc<UserSession>,
|
||||
database_manager: &Arc<DatabaseManager>,
|
||||
) -> Arc<DocumentManager2> {
|
||||
let user: Arc<dyn DocumentUser> = Arc::new(DocumentUserImpl(user_session.clone()));
|
||||
|
||||
Arc::new(DocumentManager2::new(user.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
struct DocumentUserImpl(Arc<UserSession>);
|
||||
impl DocumentUser for DocumentUserImpl {
|
||||
fn user_id(&self) -> Result<i64, FlowyError> {
|
||||
self
|
||||
.0
|
||||
.user_id()
|
||||
.map_err(|e| FlowyError::internal().context(e))
|
||||
}
|
||||
|
||||
fn token(&self) -> Result<String, FlowyError> {
|
||||
self
|
||||
.0
|
||||
.token()
|
||||
.map_err(|e| FlowyError::internal().context(e))
|
||||
}
|
||||
|
||||
fn kv_db(&self) -> Result<Arc<CollabKV>, FlowyError> {
|
||||
self.0.get_kv_db()
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
mod document2_deps;
|
||||
mod document_deps;
|
||||
mod folder2_deps;
|
||||
mod grid_deps;
|
||||
mod user_deps;
|
||||
mod util;
|
||||
|
||||
pub use document2_deps::*;
|
||||
pub use document_deps::*;
|
||||
pub use folder2_deps::*;
|
||||
pub use grid_deps::*;
|
||||
|
@ -7,6 +7,7 @@ use flowy_database::entities::DatabaseLayoutPB;
|
||||
use flowy_database::manager::DatabaseManager;
|
||||
use flowy_document::entities::DocumentVersionPB;
|
||||
use flowy_document::{DocumentConfig, DocumentManager};
|
||||
use flowy_document2::manager::DocumentManager as DocumentManager2;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_folder::errors::FlowyError;
|
||||
use flowy_folder2::manager::Folder2Manager;
|
||||
@ -124,6 +125,7 @@ pub struct AppFlowyCore {
|
||||
pub config: AppFlowyCoreConfig,
|
||||
pub user_session: Arc<UserSession>,
|
||||
pub document_manager: Arc<DocumentManager>,
|
||||
pub document_manager2: Arc<DocumentManager2>,
|
||||
pub folder_manager: Arc<Folder2Manager>,
|
||||
pub database_manager: Arc<DatabaseManager>,
|
||||
pub event_dispatcher: Arc<AFPluginDispatcher>,
|
||||
@ -146,40 +148,50 @@ impl AppFlowyCore {
|
||||
runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
|
||||
|
||||
let (local_server, ws_conn) = mk_local_server(&config.server_config);
|
||||
let (user_session, document_manager, folder_manager, local_server, database_manager) = runtime
|
||||
.block_on(async {
|
||||
let user_session = mk_user_session(&config, &local_server, &config.server_config);
|
||||
let document_manager = DocumentDepsResolver::resolve(
|
||||
local_server.clone(),
|
||||
ws_conn.clone(),
|
||||
user_session.clone(),
|
||||
&config.server_config,
|
||||
&config.document,
|
||||
);
|
||||
let (
|
||||
user_session,
|
||||
document_manager,
|
||||
folder_manager,
|
||||
local_server,
|
||||
database_manager,
|
||||
document_manager2,
|
||||
) = runtime.block_on(async {
|
||||
let user_session = mk_user_session(&config, &local_server, &config.server_config);
|
||||
let document_manager = DocumentDepsResolver::resolve(
|
||||
local_server.clone(),
|
||||
ws_conn.clone(),
|
||||
user_session.clone(),
|
||||
&config.server_config,
|
||||
&config.document,
|
||||
);
|
||||
|
||||
let database_manager = DatabaseDepsResolver::resolve(
|
||||
ws_conn.clone(),
|
||||
user_session.clone(),
|
||||
task_dispatcher.clone(),
|
||||
)
|
||||
.await;
|
||||
let database_manager = DatabaseDepsResolver::resolve(
|
||||
ws_conn.clone(),
|
||||
user_session.clone(),
|
||||
task_dispatcher.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let folder_manager =
|
||||
Folder2DepsResolver::resolve(user_session.clone(), &document_manager, &database_manager)
|
||||
.await;
|
||||
let folder_manager =
|
||||
Folder2DepsResolver::resolve(user_session.clone(), &document_manager, &database_manager)
|
||||
.await;
|
||||
|
||||
if let Some(local_server) = local_server.as_ref() {
|
||||
local_server.run();
|
||||
}
|
||||
ws_conn.init().await;
|
||||
(
|
||||
user_session,
|
||||
document_manager,
|
||||
folder_manager,
|
||||
local_server,
|
||||
database_manager,
|
||||
)
|
||||
});
|
||||
let document_manager2 =
|
||||
Document2DepsResolver::resolve(user_session.clone(), &database_manager);
|
||||
|
||||
if let Some(local_server) = local_server.as_ref() {
|
||||
local_server.run();
|
||||
}
|
||||
ws_conn.init().await;
|
||||
(
|
||||
user_session,
|
||||
document_manager,
|
||||
folder_manager,
|
||||
local_server,
|
||||
database_manager,
|
||||
document_manager2,
|
||||
)
|
||||
});
|
||||
|
||||
let user_status_listener = UserStatusListener {
|
||||
document_manager: document_manager.clone(),
|
||||
@ -203,6 +215,7 @@ impl AppFlowyCore {
|
||||
&database_manager,
|
||||
&user_session,
|
||||
&document_manager,
|
||||
&document_manager2,
|
||||
)
|
||||
}));
|
||||
_start_listening(&event_dispatcher, &ws_conn, &folder_manager);
|
||||
@ -211,6 +224,7 @@ impl AppFlowyCore {
|
||||
config,
|
||||
user_session,
|
||||
document_manager,
|
||||
document_manager2,
|
||||
folder_manager,
|
||||
database_manager,
|
||||
event_dispatcher,
|
||||
|
@ -1,6 +1,7 @@
|
||||
use flowy_client_ws::FlowyWebSocketConnect;
|
||||
use flowy_database::manager::DatabaseManager;
|
||||
use flowy_document::DocumentManager;
|
||||
use flowy_document2::manager::DocumentManager as DocumentManager2;
|
||||
|
||||
use flowy_folder2::manager::Folder2Manager;
|
||||
use flowy_user::services::UserSession;
|
||||
@ -13,17 +14,20 @@ pub fn make_plugins(
|
||||
grid_manager: &Arc<DatabaseManager>,
|
||||
user_session: &Arc<UserSession>,
|
||||
document_manager: &Arc<DocumentManager>,
|
||||
document_manager2: &Arc<DocumentManager2>,
|
||||
) -> Vec<AFPlugin> {
|
||||
let user_plugin = flowy_user::event_map::init(user_session.clone());
|
||||
let folder_plugin = flowy_folder2::event_map::init(folder_manager.clone());
|
||||
let network_plugin = flowy_net::event_map::init(ws_conn.clone());
|
||||
let grid_plugin = flowy_database::event_map::init(grid_manager.clone());
|
||||
let document_plugin = flowy_document::event_map::init(document_manager.clone());
|
||||
let document_plugin2 = flowy_document2::event_map::init(document_manager2.clone());
|
||||
vec![
|
||||
user_plugin,
|
||||
folder_plugin,
|
||||
network_plugin,
|
||||
grid_plugin,
|
||||
document_plugin,
|
||||
document_plugin2,
|
||||
]
|
||||
}
|
||||
|
33
frontend/rust-lib/flowy-document2/Cargo.toml
Normal file
33
frontend/rust-lib/flowy-document2/Cargo.toml
Normal file
@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "flowy-document2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
collab = { version = "0.1.0" }
|
||||
collab-persistence = { version = "0.1.0" }
|
||||
collab-document = { version = "0.1.0" }
|
||||
|
||||
flowy-derive = { path = "../flowy-derive" }
|
||||
flowy-notification = { path = "../flowy-notification" }
|
||||
flowy-error = { path = "../flowy-error", features = ["adaptor_sync", "adaptor_ot", "adaptor_serde", "adaptor_database", "adaptor_dispatch"] }
|
||||
|
||||
lib-dispatch = { path = "../lib-dispatch" }
|
||||
|
||||
protobuf = {version = "2.28.0"}
|
||||
bytes = { version = "1.4" }
|
||||
nanoid = "0.4.0"
|
||||
parking_lot = "0.12.1"
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = {version = "1.0"}
|
||||
|
||||
[build-dependencies]
|
||||
flowy-codegen = { path = "../flowy-codegen"}
|
||||
|
||||
[features]
|
||||
dart = ["flowy-codegen/dart", "flowy-notification/dart"]
|
||||
ts = ["flowy-codegen/ts", "flowy-notification/ts"]
|
3
frontend/rust-lib/flowy-document2/Flowy.toml
Normal file
3
frontend/rust-lib/flowy-document2/Flowy.toml
Normal file
@ -0,0 +1,3 @@
|
||||
# Check out the FlowyConfig (located in flowy_toml.rs) for more details.
|
||||
proto_input = ["src/event_map.rs", "src/entities.rs", "src/notification.rs"]
|
||||
event_files = ["src/event_map.rs"]
|
10
frontend/rust-lib/flowy-document2/build.rs
Normal file
10
frontend/rust-lib/flowy-document2/build.rs
Normal file
@ -0,0 +1,10 @@
|
||||
fn main() {
|
||||
let crate_name = env!("CARGO_PKG_NAME");
|
||||
flowy_codegen::protobuf_file::gen(crate_name);
|
||||
|
||||
#[cfg(feature = "dart")]
|
||||
flowy_codegen::dart_event::gen(crate_name);
|
||||
|
||||
#[cfg(feature = "ts")]
|
||||
flowy_codegen::ts_event::gen(crate_name);
|
||||
}
|
148
frontend/rust-lib/flowy-document2/src/document.rs
Normal file
148
frontend/rust-lib/flowy-document2/src/document.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
vec,
|
||||
};
|
||||
|
||||
use collab::preclude::Collab;
|
||||
use collab_document::{
|
||||
blocks::{Block, DocumentData, DocumentMeta},
|
||||
document::Document as InnerDocument,
|
||||
};
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use nanoid::nanoid;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::entities::{BlockMapPB, BlockPB, ChildrenPB, DocumentDataPB2, MetaPB};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Document(Arc<Mutex<InnerDocument>>);
|
||||
|
||||
impl Document {
|
||||
pub fn new(collab: Collab, data: DocumentDataWrapper) -> FlowyResult<Self> {
|
||||
let inner = InnerDocument::create(collab, data.0)
|
||||
.map_err(|_| FlowyError::from(ErrorCode::DocumentDataInvalid))?;
|
||||
Ok(Self(Arc::new(Mutex::new(inner))))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for Document {}
|
||||
unsafe impl Send for Document {}
|
||||
|
||||
impl Deref for Document {
|
||||
type Target = Arc<Mutex<InnerDocument>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Document {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DocumentDataWrapper(pub DocumentData);
|
||||
|
||||
impl Deref for DocumentDataWrapper {
|
||||
type Target = DocumentData;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for DocumentDataWrapper {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DocumentDataWrapper> for DocumentDataPB2 {
|
||||
fn from(data: DocumentDataWrapper) -> Self {
|
||||
let blocks = data
|
||||
.0
|
||||
.blocks
|
||||
.into_iter()
|
||||
.map(|(id, block)| {
|
||||
(
|
||||
id,
|
||||
BlockPB {
|
||||
id: block.id,
|
||||
ty: block.ty,
|
||||
parent_id: block.parent,
|
||||
children_id: block.children,
|
||||
data: serde_json::to_string(&block.data).unwrap(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<String, BlockPB>>();
|
||||
let children_map = data
|
||||
.0
|
||||
.meta
|
||||
.children_map
|
||||
.into_iter()
|
||||
.map(|(id, children)| {
|
||||
(
|
||||
id,
|
||||
ChildrenPB {
|
||||
children: children.into_iter().collect(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<String, ChildrenPB>>();
|
||||
Self {
|
||||
page_id: data.0.page_id,
|
||||
blocks: BlockMapPB { blocks },
|
||||
meta: MetaPB { children_map },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DocumentDataWrapper {
|
||||
fn default() -> Self {
|
||||
let mut blocks: HashMap<String, Block> = HashMap::new();
|
||||
let mut meta: HashMap<String, Vec<String>> = HashMap::new();
|
||||
|
||||
// page block
|
||||
let page_id = nanoid!(10);
|
||||
let children_id = nanoid!(10);
|
||||
let root = Block {
|
||||
id: page_id.clone(),
|
||||
ty: "page".to_string(),
|
||||
parent: "".to_string(),
|
||||
children: children_id.clone(),
|
||||
external_id: None,
|
||||
external_type: None,
|
||||
data: HashMap::new(),
|
||||
};
|
||||
blocks.insert(page_id.clone(), root);
|
||||
|
||||
// text block
|
||||
let text_block_id = nanoid!(10);
|
||||
let text_0_children_id = nanoid!(10);
|
||||
let text_block = Block {
|
||||
id: text_block_id.clone(),
|
||||
ty: "text".to_string(),
|
||||
parent: page_id.clone(),
|
||||
children: text_0_children_id.clone(),
|
||||
external_id: None,
|
||||
external_type: None,
|
||||
data: HashMap::new(),
|
||||
};
|
||||
blocks.insert(text_block_id.clone(), text_block);
|
||||
|
||||
// meta
|
||||
meta.insert(children_id, vec![text_block_id]);
|
||||
meta.insert(text_0_children_id, vec![]);
|
||||
|
||||
Self(DocumentData {
|
||||
page_id,
|
||||
blocks,
|
||||
meta: DocumentMeta { children_map: meta },
|
||||
})
|
||||
}
|
||||
}
|
110
frontend/rust-lib/flowy-document2/src/entities.rs
Normal file
110
frontend/rust-lib/flowy-document2/src/entities.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct OpenDocumentPayloadPBV2 {
|
||||
#[pb(index = 1)]
|
||||
pub document_id: String,
|
||||
// Support customize initial data
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct CloseDocumentPayloadPBV2 {
|
||||
#[pb(index = 1)]
|
||||
pub document_id: String,
|
||||
// Support customize initial data
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct ApplyActionPayloadPBV2 {
|
||||
#[pb(index = 1)]
|
||||
pub document_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub actions: Vec<BlockActionPB>,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct DocumentDataPB2 {
|
||||
#[pb(index = 1)]
|
||||
pub page_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub blocks: BlockMapPB,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub meta: MetaPB,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct BlockMapPB {
|
||||
#[pb(index = 1)]
|
||||
pub blocks: HashMap<String, BlockPB>,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct BlockPB {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub ty: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub data: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub parent_id: String,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub children_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct MetaPB {
|
||||
#[pb(index = 1)]
|
||||
pub children_map: HashMap<String, ChildrenPB>,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct ChildrenPB {
|
||||
#[pb(index = 1)]
|
||||
pub children: Vec<String>,
|
||||
}
|
||||
|
||||
// Actions
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct BlockActionPB {
|
||||
#[pb(index = 1)]
|
||||
pub action: BlockActionTypePB,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub payload: BlockActionPayloadPB,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct BlockActionPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub block: BlockPB,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub prev_id: Option<String>,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub parent_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf_Enum)]
|
||||
pub enum BlockActionTypePB {
|
||||
Insert = 0,
|
||||
Update = 1,
|
||||
Delete = 2,
|
||||
Move = 3,
|
||||
}
|
||||
|
||||
impl Default for BlockActionTypePB {
|
||||
fn default() -> Self {
|
||||
Self::Insert
|
||||
}
|
||||
}
|
98
frontend/rust-lib/flowy-document2/src/event_handler.rs
Normal file
98
frontend/rust-lib/flowy-document2/src/event_handler.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use std::{sync::Arc};
|
||||
|
||||
use crate::{
|
||||
document::DocumentDataWrapper,
|
||||
entities::{
|
||||
ApplyActionPayloadPBV2, BlockActionPB, BlockActionPayloadPB, BlockActionTypePB,
|
||||
BlockPB, CloseDocumentPayloadPBV2, DocumentDataPB2, OpenDocumentPayloadPBV2,
|
||||
},
|
||||
manager::DocumentManager,
|
||||
};
|
||||
|
||||
use collab_document::blocks::{
|
||||
json_str_to_hashmap, Block, BlockAction, BlockActionPayload, BlockActionType,
|
||||
};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||
pub(crate) async fn open_document_handler(
|
||||
data: AFPluginData<OpenDocumentPayloadPBV2>,
|
||||
manager: AFPluginState<Arc<DocumentManager>>,
|
||||
) -> DataResult<DocumentDataPB2, FlowyError> {
|
||||
let context = data.into_inner();
|
||||
let document = manager.open_document(context.document_id)?;
|
||||
let document_data = document
|
||||
.lock()
|
||||
.get_document()
|
||||
.map_err(|err| FlowyError::internal().context(err))?;
|
||||
data_result_ok(DocumentDataPB2::from(DocumentDataWrapper(document_data)))
|
||||
}
|
||||
|
||||
pub(crate) async fn close_document_handler(
|
||||
data: AFPluginData<CloseDocumentPayloadPBV2>,
|
||||
manager: AFPluginState<Arc<DocumentManager>>,
|
||||
) -> FlowyResult<()> {
|
||||
let context = data.into_inner();
|
||||
manager.close_document(context.document_id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_action_handler(
|
||||
data: AFPluginData<ApplyActionPayloadPBV2>,
|
||||
manager: AFPluginState<Arc<DocumentManager>>,
|
||||
) -> FlowyResult<()> {
|
||||
let context = data.into_inner();
|
||||
let doc_id = context.document_id;
|
||||
let actions = context
|
||||
.actions
|
||||
.into_iter()
|
||||
.map(|action| action.into())
|
||||
.collect();
|
||||
let document = manager.open_document(doc_id)?;
|
||||
document.lock().apply_action(actions);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl From<BlockActionPB> for BlockAction {
|
||||
fn from(pb: BlockActionPB) -> Self {
|
||||
Self {
|
||||
action: pb.action.into(),
|
||||
payload: pb.payload.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockActionTypePB> for BlockActionType {
|
||||
fn from(pb: BlockActionTypePB) -> Self {
|
||||
match pb {
|
||||
BlockActionTypePB::Insert => Self::Insert,
|
||||
BlockActionTypePB::Update => Self::Update,
|
||||
BlockActionTypePB::Delete => Self::Delete,
|
||||
BlockActionTypePB::Move => Self::Move,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockActionPayloadPB> for BlockActionPayload {
|
||||
fn from(pb: BlockActionPayloadPB) -> Self {
|
||||
Self {
|
||||
block: pb.block.into(),
|
||||
parent_id: pb.parent_id,
|
||||
prev_id: pb.prev_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockPB> for Block {
|
||||
fn from(pb: BlockPB) -> Self {
|
||||
let data = json_str_to_hashmap(&pb.data).unwrap_or_default();
|
||||
Self {
|
||||
id: pb.id,
|
||||
ty: pb.ty,
|
||||
children: pb.children_id,
|
||||
parent: pb.parent_id,
|
||||
data,
|
||||
external_id: None,
|
||||
external_type: None,
|
||||
}
|
||||
}
|
||||
}
|
35
frontend/rust-lib/flowy-document2/src/event_map.rs
Normal file
35
frontend/rust-lib/flowy-document2/src/event_map.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use std::sync::Arc;
|
||||
use strum_macros::Display;
|
||||
|
||||
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
|
||||
use lib_dispatch::prelude::AFPlugin;
|
||||
|
||||
use crate::{
|
||||
event_handler::{apply_action_handler, close_document_handler, open_document_handler},
|
||||
manager::DocumentManager,
|
||||
};
|
||||
|
||||
pub fn init(document_manager: Arc<DocumentManager>) -> AFPlugin {
|
||||
let mut plugin = AFPlugin::new()
|
||||
.name(env!("CARGO_PKG_NAME"))
|
||||
.state(document_manager);
|
||||
|
||||
plugin = plugin.event(DocumentEvent2::OpenDocument, open_document_handler);
|
||||
plugin = plugin.event(DocumentEvent2::CloseDocument, close_document_handler);
|
||||
plugin = plugin.event(DocumentEvent2::ApplyAction, apply_action_handler);
|
||||
|
||||
plugin
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, ProtoBuf_Enum, Flowy_Event)]
|
||||
#[event_err = "FlowyError"]
|
||||
pub enum DocumentEvent2 {
|
||||
#[event(input = "OpenDocumentPayloadPBV2", output = "DocumentDataPB2")]
|
||||
OpenDocument = 0,
|
||||
|
||||
#[event(input = "CloseDocumentPayloadPBV2")]
|
||||
CloseDocument = 1,
|
||||
|
||||
#[event(input = "ApplyActionPayloadPBV2")]
|
||||
ApplyAction = 2,
|
||||
}
|
8
frontend/rust-lib/flowy-document2/src/lib.rs
Normal file
8
frontend/rust-lib/flowy-document2/src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
pub mod entities;
|
||||
pub mod event_map;
|
||||
pub mod manager;
|
||||
pub mod protobuf;
|
||||
|
||||
mod document;
|
||||
mod event_handler;
|
||||
mod notification;
|
71
frontend/rust-lib/flowy-document2/src/manager.rs
Normal file
71
frontend/rust-lib/flowy-document2/src/manager.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use collab::{plugin_impl::disk::CollabDiskPlugin, preclude::CollabBuilder};
|
||||
use collab_persistence::CollabKV;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use crate::{
|
||||
document::{Document, DocumentDataWrapper},
|
||||
notification::{send_notification, DocumentNotification},
|
||||
};
|
||||
|
||||
pub trait DocumentUser: Send + Sync {
|
||||
fn user_id(&self) -> Result<i64, FlowyError>;
|
||||
fn token(&self) -> Result<String, FlowyError>; // unused now.
|
||||
fn kv_db(&self) -> Result<Arc<CollabKV>, FlowyError>;
|
||||
}
|
||||
|
||||
pub struct DocumentManager {
|
||||
documents: Arc<RwLock<HashMap<String, Arc<Document>>>>,
|
||||
user: Arc<dyn DocumentUser>,
|
||||
}
|
||||
|
||||
// unsafe impl Send for DocumentManager {}
|
||||
// unsafe impl Sync for DocumentManager {}
|
||||
|
||||
impl DocumentManager {
|
||||
pub fn new(user: Arc<dyn DocumentUser>) -> Self {
|
||||
Self {
|
||||
documents: Default::default(),
|
||||
user,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_document(&self, doc_id: String) -> FlowyResult<Arc<Document>> {
|
||||
if let Some(doc) = self.documents.read().get(&doc_id) {
|
||||
return Ok(doc.clone());
|
||||
}
|
||||
let collab = self.get_collab_for_doc_id(&doc_id)?;
|
||||
let data = DocumentDataWrapper::default();
|
||||
let document = Arc::new(Document::new(collab, data)?);
|
||||
|
||||
let clone_doc_id = doc_id.clone();
|
||||
let _document_data = document
|
||||
.lock()
|
||||
.open(move |_, _| {
|
||||
// TODO: add payload data.
|
||||
send_notification(&clone_doc_id, DocumentNotification::DidReceiveUpdate).send();
|
||||
})
|
||||
.map_err(|err| FlowyError::internal().context(err))?;
|
||||
self.documents.write().insert(doc_id, document.clone());
|
||||
Ok(document)
|
||||
}
|
||||
|
||||
pub fn close_document(&self, doc_id: String) -> FlowyResult<()> {
|
||||
self.documents.write().remove(&doc_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_collab_for_doc_id(&self, doc_id: &str) -> Result<collab::preclude::Collab, FlowyError> {
|
||||
let uid = self.user.user_id()?;
|
||||
let kv_db = self.user.kv_db()?;
|
||||
let mut collab = CollabBuilder::new(uid, doc_id).build();
|
||||
let disk_plugin = Arc::new(
|
||||
CollabDiskPlugin::new(uid, kv_db).map_err(|err| FlowyError::internal().context(err))?,
|
||||
);
|
||||
collab.add_plugin(disk_plugin);
|
||||
collab.initial();
|
||||
Ok(collab)
|
||||
}
|
||||
}
|
28
frontend/rust-lib/flowy-document2/src/notification.rs
Normal file
28
frontend/rust-lib/flowy-document2/src/notification.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use flowy_derive::ProtoBuf_Enum;
|
||||
use flowy_notification::NotificationBuilder;
|
||||
|
||||
|
||||
const OBSERVABLE_CATEGORY: &str = "Document";
|
||||
|
||||
#[derive(ProtoBuf_Enum, Debug)]
|
||||
pub(crate) enum DocumentNotification {
|
||||
Unknown = 0,
|
||||
|
||||
DidReceiveUpdate = 1,
|
||||
}
|
||||
|
||||
impl std::default::Default for DocumentNotification {
|
||||
fn default() -> Self {
|
||||
DocumentNotification::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<DocumentNotification> for i32 {
|
||||
fn from(notification: DocumentNotification) -> Self {
|
||||
notification as i32
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn send_notification(id: &str, ty: DocumentNotification) -> NotificationBuilder {
|
||||
NotificationBuilder::new(id, ty, OBSERVABLE_CATEGORY)
|
||||
}
|
@ -189,6 +189,9 @@ pub enum ErrorCode {
|
||||
|
||||
#[error("Only the date type can be used in calendar")]
|
||||
UnexpectedCalendarFieldType = 61,
|
||||
|
||||
#[error("Document Data Invalid")]
|
||||
DocumentDataInvalid = 62,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
@ -20,6 +20,7 @@ flowy-client-network-config= { path = "../../../shared-lib/flowy-client-network-
|
||||
flowy-sync = { path = "../../../shared-lib/flowy-sync"}
|
||||
user-model = { path = "../../../shared-lib/user-model"}
|
||||
flowy-folder2 = { path = "../flowy-folder2" }
|
||||
flowy-document2 = { path = "../flowy-document2" }
|
||||
flowy-user = { path = "../flowy-user" }
|
||||
flowy-document = { path = "../flowy-document" }
|
||||
lazy_static = "1.4.0"
|
||||
|
Loading…
Reference in New Issue
Block a user