Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f36e3ae378 | ||
|
|
dc6349b4b1 | ||
|
|
44d8def3ce | ||
|
|
c6ad57f11d | ||
|
|
ff23165d3e | ||
|
|
253e7597c4 | ||
|
|
d2e3fdfefd | ||
|
|
a2336f5bf7 | ||
|
|
8833df1740 | ||
|
|
1524b10a8a | ||
|
|
fe0fa9b530 | ||
|
|
80afcf44c0 | ||
|
|
e500c89978 | ||
|
|
079b9888a8 | ||
|
|
0fe383e538 | ||
|
|
c006e29afc |
No files matched your search
@@ -135,7 +135,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { target: x86_64-apple-darwin, os: macos-latest, extra-build-args: "" }
|
||||
- { target: x86_64-apple-darwin, os: macos-12, extra-build-args: "" }
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
# Release Notes
|
||||
## Version 0.6.4 - 16/07/2024
|
||||
### New Features
|
||||
- Enhanced the message style on the AI chat page.
|
||||
- Added the ability to choose cursor color and selection color from a palette in settings page.
|
||||
### Bug Fixes
|
||||
- Optimized the performance for loading recent pages.
|
||||
- Fixed an issue where the cursor would jump randomly when typing in the document title on mobile.
|
||||
|
||||
## Version 0.6.3 - 08/07/2024
|
||||
### New Features
|
||||
- Publish a Document to the Web
|
||||
|
||||
Vendored
+1
-1
@@ -138,4 +138,4 @@
|
||||
"cwd": "${workspaceRoot}/appflowy_tauri/"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
APPFLOWY_VERSION = "0.6.3"
|
||||
APPFLOWY_VERSION = "0.6.4"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
|
||||
@@ -53,7 +53,7 @@ android {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "io.appflowy.appflowy"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 33
|
||||
targetSdkVersion 34
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
multiDexEnabled true
|
||||
|
||||
+2
-2
@@ -82,11 +82,11 @@ void main() {
|
||||
HeadingBlockKeys.type,
|
||||
);
|
||||
expect(
|
||||
importedPageEditorState.getNodeAtPath([2])!.type,
|
||||
importedPageEditorState.getNodeAtPath([1])!.type,
|
||||
HeadingBlockKeys.type,
|
||||
);
|
||||
expect(
|
||||
importedPageEditorState.getNodeAtPath([4])!.type,
|
||||
importedPageEditorState.getNodeAtPath([2])!.type,
|
||||
TableBlockKeys.type,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -13,7 +13,9 @@ import 'util.dart';
|
||||
|
||||
extension AppFlowyAuthTest on WidgetTester {
|
||||
Future<void> tapGoogleLoginInButton() async {
|
||||
await tapButton(find.byKey(const Key('signInWithGoogleButton')));
|
||||
await tapButton(
|
||||
find.byKey(const Key('signInWithGoogleButton')),
|
||||
);
|
||||
}
|
||||
|
||||
/// Requires being on the SettingsPage.account of the SettingsDialog
|
||||
|
||||
@@ -65,6 +65,8 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- printing (1.0.0):
|
||||
- Flutter
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- SDWebImage (5.14.2):
|
||||
- SDWebImage/Core (= 5.14.2)
|
||||
@@ -101,6 +103,7 @@ DEPENDENCIES:
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- printing (from `.symlinks/plugins/printing/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
@@ -149,6 +152,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
printing:
|
||||
:path: ".symlinks/plugins/printing/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
@@ -170,7 +175,7 @@ SPEC CHECKSUMS:
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||
@@ -179,6 +184,7 @@ SPEC CHECKSUMS:
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
printing: 233e1b73bd1f4a05615548e9b5a324c98588640b
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
@@ -191,4 +197,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
COCOAPODS: 1.15.2
|
||||
+7
@@ -29,4 +29,11 @@ abstract class Env {
|
||||
defaultValue: '',
|
||||
)
|
||||
static const String afCloudUrl = _Env.afCloudUrl;
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: false,
|
||||
varName: 'INTERNAL_BUILD',
|
||||
defaultValue: '',
|
||||
)
|
||||
static const String internalBuild = _Env.internalBuild;
|
||||
}
|
||||
+1
-2
@@ -38,8 +38,7 @@ class _MobileRecentFolderState extends State<MobileRecentFolder> {
|
||||
builder: (context, state) {
|
||||
final ids = <String>{};
|
||||
|
||||
List<ViewPB> recentViews =
|
||||
state.views.reversed.map((e) => e.item).toList();
|
||||
List<ViewPB> recentViews = state.views.map((e) => e.item).toList();
|
||||
recentViews.retainWhere((element) => ids.add(element.id));
|
||||
|
||||
// only keep the first 20 items.
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ class _MobileRecentSpaceState extends State<MobileRecentSpace>
|
||||
|
||||
List<SectionViewPB> _filterRecentViews(List<SectionViewPB> recentViews) {
|
||||
final ids = <String>{};
|
||||
final filteredRecentViews = recentViews.reversed.toList();
|
||||
final filteredRecentViews = recentViews.toList();
|
||||
filteredRecentViews.retainWhere((e) => ids.add(e.item.id));
|
||||
return filteredRecentViews;
|
||||
}
|
||||
|
||||
+2
-2
@@ -25,14 +25,14 @@ class AboutSettingGroup extends StatelessWidget {
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.io/privacy/app'),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.io/privacy'),
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_mobile_termsAndConditions.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.io/terms/app'),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.io/terms'),
|
||||
),
|
||||
if (kDebugMode)
|
||||
MobileSettingItem(
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
@@ -480,7 +481,7 @@ class ChatState with _$ChatState {
|
||||
@freezed
|
||||
class LoadingState with _$LoadingState {
|
||||
const factory LoadingState.loading() = _Loading;
|
||||
const factory LoadingState.finish() = _Finish;
|
||||
const factory LoadingState.finish({FlowyError? error}) = _Finish;
|
||||
}
|
||||
|
||||
enum OnetimeShotType {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chat_file_bloc.freezed.dart';
|
||||
|
||||
class ChatFileBloc extends Bloc<ChatFileEvent, ChatFileState> {
|
||||
ChatFileBloc({
|
||||
required String chatId,
|
||||
dynamic message,
|
||||
}) : listener = LocalLLMListener(),
|
||||
super(ChatFileState.initial(message)) {
|
||||
listener.start(
|
||||
stateCallback: (pluginState) {
|
||||
if (!isClosed) {
|
||||
add(ChatFileEvent.updateLocalAIState(pluginState));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
on<ChatFileEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
final result = await ChatEventGetPluginState().send();
|
||||
result.fold(
|
||||
(pluginState) {
|
||||
if (!isClosed) {
|
||||
add(ChatFileEvent.updateLocalAIState(pluginState));
|
||||
}
|
||||
},
|
||||
(err) {},
|
||||
);
|
||||
},
|
||||
newFile: (String filePath) {
|
||||
final payload = ChatFilePB(filePath: filePath, chatId: chatId);
|
||||
ChatEventChatWithFile(payload).send();
|
||||
},
|
||||
updateLocalAIState: (PluginStatePB pluginState) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
supportChatWithFile:
|
||||
pluginState.state == RunningStatePB.Running,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final LocalLLMListener listener;
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await listener.stop();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChatFileEvent with _$ChatFileEvent {
|
||||
const factory ChatFileEvent.initial() = Initial;
|
||||
const factory ChatFileEvent.newFile(String filePath) = _NewFile;
|
||||
const factory ChatFileEvent.updateLocalAIState(PluginStatePB pluginState) =
|
||||
_UpdateLocalAIState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChatFileState with _$ChatFileState {
|
||||
const factory ChatFileState({
|
||||
required String text,
|
||||
@Default(false) bool supportChatWithFile,
|
||||
}) = _ChatFileState;
|
||||
|
||||
factory ChatFileState.initial(dynamic text) {
|
||||
return ChatFileState(
|
||||
text: text is String ? text : "",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
@@ -50,7 +52,7 @@ class AIChatUILayout {
|
||||
}
|
||||
}
|
||||
|
||||
class AIChatPage extends StatefulWidget {
|
||||
class AIChatPage extends StatelessWidget {
|
||||
const AIChatPage({
|
||||
super.key,
|
||||
required this.view,
|
||||
@@ -63,10 +65,58 @@ class AIChatPage extends StatefulWidget {
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
State<AIChatPage> createState() => _AIChatPageState();
|
||||
Widget build(BuildContext context) {
|
||||
if (userProfile.authenticator == AuthenticatorPB.AppFlowyCloud) {
|
||||
return BlocProvider(
|
||||
create: (context) => ChatFileBloc(chatId: view.id.toString()),
|
||||
child: BlocBuilder<ChatFileBloc, ChatFileState>(
|
||||
builder: (context, state) {
|
||||
return state.supportChatWithFile
|
||||
? DropTarget(
|
||||
onDragDone: (DropDoneDetails detail) async {
|
||||
for (final file in detail.files) {
|
||||
context
|
||||
.read<ChatFileBloc>()
|
||||
.add(ChatFileEvent.newFile(file.path));
|
||||
}
|
||||
},
|
||||
child: _ChatContentPage(
|
||||
view: view,
|
||||
userProfile: userProfile,
|
||||
),
|
||||
)
|
||||
: _ChatContentPage(
|
||||
view: view,
|
||||
userProfile: userProfile,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: FlowyText(
|
||||
LocaleKeys.chat_unsupportedCloudPrompt.tr(),
|
||||
fontSize: 20,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AIChatPageState extends State<AIChatPage> {
|
||||
class _ChatContentPage extends StatefulWidget {
|
||||
const _ChatContentPage({
|
||||
required this.view,
|
||||
required this.userProfile,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final ViewPB view;
|
||||
|
||||
@override
|
||||
State<_ChatContentPage> createState() => _ChatContentPageState();
|
||||
}
|
||||
|
||||
class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
late types.User _user;
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_avatar.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_input.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_popmenu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
@@ -176,9 +182,20 @@ class CopyButton extends StatelessWidget {
|
||||
size: const Size.square(14),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: textMessage.text));
|
||||
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
|
||||
onPressed: () async {
|
||||
final document = customMarkdownToDocument(textMessage.text);
|
||||
await getIt<ClipboardService>().setData(
|
||||
ClipboardServiceData(
|
||||
plainText: textMessage.text,
|
||||
inAppJson: jsonEncode(document.toJson()),
|
||||
),
|
||||
);
|
||||
if (context.mounted) {
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.grid_url_copiedNotification.tr(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'chat_input.dart';
|
||||
|
||||
|
||||
+373
@@ -0,0 +1,373 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:markdown_widget/markdown_widget.dart';
|
||||
|
||||
import 'selectable_highlight.dart';
|
||||
|
||||
enum AIMarkdownType {
|
||||
appflowyEditor,
|
||||
markdownWidget,
|
||||
}
|
||||
|
||||
// Wrap the appflowy_editor or markdown_widget as a chat text message widget
|
||||
class AIMarkdownText extends StatelessWidget {
|
||||
const AIMarkdownText({
|
||||
super.key,
|
||||
required this.markdown,
|
||||
this.type = AIMarkdownType.appflowyEditor,
|
||||
});
|
||||
|
||||
final String markdown;
|
||||
final AIMarkdownType type;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (type) {
|
||||
case AIMarkdownType.appflowyEditor:
|
||||
return _AppFlowyEditorMarkdown(markdown: markdown);
|
||||
case AIMarkdownType.markdownWidget:
|
||||
return _ThirdPartyMarkdown(markdown: markdown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _AppFlowyEditorMarkdown extends StatefulWidget {
|
||||
const _AppFlowyEditorMarkdown({
|
||||
required this.markdown,
|
||||
});
|
||||
|
||||
// the text should be the markdown format
|
||||
final String markdown;
|
||||
|
||||
@override
|
||||
State<_AppFlowyEditorMarkdown> createState() =>
|
||||
_AppFlowyEditorMarkdownState();
|
||||
}
|
||||
|
||||
class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> {
|
||||
late EditorState editorState;
|
||||
late final styleCustomizer = EditorStyleCustomizer(
|
||||
context: context,
|
||||
padding: EdgeInsets.zero,
|
||||
);
|
||||
late final editorStyle = styleCustomizer.style().copyWith(
|
||||
// hide the cursor
|
||||
cursorColor: Colors.transparent,
|
||||
cursorWidth: 0,
|
||||
);
|
||||
late EditorScrollController scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
editorState = _parseMarkdown(widget.markdown);
|
||||
scrollController = EditorScrollController(
|
||||
editorState: editorState,
|
||||
shrinkWrap: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant _AppFlowyEditorMarkdown oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.markdown != widget.markdown) {
|
||||
editorState.dispose();
|
||||
editorState = _parseMarkdown(widget.markdown);
|
||||
scrollController.dispose();
|
||||
scrollController = EditorScrollController(
|
||||
editorState: editorState,
|
||||
shrinkWrap: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
editorState.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final blockBuilders = getEditorBuilderMap(
|
||||
context: context,
|
||||
editorState: editorState,
|
||||
styleCustomizer: styleCustomizer,
|
||||
// the editor is not editable in the chat
|
||||
editable: false,
|
||||
);
|
||||
return IntrinsicHeight(
|
||||
child: AppFlowyEditor(
|
||||
shrinkWrap: true,
|
||||
// the editor is not editable in the chat
|
||||
editable: false,
|
||||
editorStyle: editorStyle,
|
||||
editorScrollController: scrollController,
|
||||
blockComponentBuilders: blockBuilders,
|
||||
commandShortcutEvents: [customCopyCommand],
|
||||
editorState: editorState,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
EditorState _parseMarkdown(String markdown) {
|
||||
final document = customMarkdownToDocument(markdown);
|
||||
final editorState = EditorState(document: document);
|
||||
return editorState;
|
||||
}
|
||||
}
|
||||
|
||||
class _ThirdPartyMarkdown extends StatelessWidget {
|
||||
const _ThirdPartyMarkdown({
|
||||
required this.markdown,
|
||||
});
|
||||
|
||||
final String markdown;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MarkdownWidget(
|
||||
data: markdown,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
config: configFromContext(context),
|
||||
);
|
||||
}
|
||||
|
||||
MarkdownConfig configFromContext(BuildContext context) {
|
||||
return MarkdownConfig(
|
||||
configs: [
|
||||
HrConfig(color: AFThemeExtension.of(context).textColor),
|
||||
_ChatH1Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
_ChatH2Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
_ChatH3Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
H4Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
H5Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
H6Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
PreConfig(
|
||||
builder: (code, language) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 800,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
|
||||
child: SelectableHighlightView(
|
||||
code,
|
||||
language: language,
|
||||
theme: getHighlightTheme(context),
|
||||
padding: const EdgeInsets.all(14),
|
||||
textStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PConfig(
|
||||
textStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
CodeConfig(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
BlockquoteConfig(
|
||||
sideColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
textColor: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, TextStyle> getHighlightTheme(BuildContext context) {
|
||||
return {
|
||||
'root': TextStyle(
|
||||
color: const Color(0xffabb2bf),
|
||||
backgroundColor:
|
||||
Theme.of(context).isLightMode ? Colors.white : Colors.black38,
|
||||
),
|
||||
'comment': const TextStyle(
|
||||
color: Color(0xff5c6370),
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
'quote': const TextStyle(
|
||||
color: Color(0xff5c6370),
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
'doctag': const TextStyle(color: Color(0xffc678dd)),
|
||||
'keyword': const TextStyle(color: Color(0xffc678dd)),
|
||||
'formula': const TextStyle(color: Color(0xffc678dd)),
|
||||
'section': const TextStyle(color: Color(0xffe06c75)),
|
||||
'name': const TextStyle(color: Color(0xffe06c75)),
|
||||
'selector-tag': const TextStyle(color: Color(0xffe06c75)),
|
||||
'deletion': const TextStyle(color: Color(0xffe06c75)),
|
||||
'subst': const TextStyle(color: Color(0xffe06c75)),
|
||||
'literal': const TextStyle(color: Color(0xff56b6c2)),
|
||||
'string': const TextStyle(color: Color(0xff98c379)),
|
||||
'regexp': const TextStyle(color: Color(0xff98c379)),
|
||||
'addition': const TextStyle(color: Color(0xff98c379)),
|
||||
'attribute': const TextStyle(color: Color(0xff98c379)),
|
||||
'meta-string': const TextStyle(color: Color(0xff98c379)),
|
||||
'built_in': const TextStyle(color: Color(0xffe6c07b)),
|
||||
'attr': const TextStyle(color: Color(0xffd19a66)),
|
||||
'variable': const TextStyle(color: Color(0xffd19a66)),
|
||||
'template-variable': const TextStyle(color: Color(0xffd19a66)),
|
||||
'type': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-class': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-attr': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-pseudo': const TextStyle(color: Color(0xffd19a66)),
|
||||
'number': const TextStyle(color: Color(0xffd19a66)),
|
||||
'symbol': const TextStyle(color: Color(0xff61aeee)),
|
||||
'bullet': const TextStyle(color: Color(0xff61aeee)),
|
||||
'link': const TextStyle(color: Color(0xff61aeee)),
|
||||
'meta': const TextStyle(color: Color(0xff61aeee)),
|
||||
'selector-id': const TextStyle(color: Color(0xff61aeee)),
|
||||
'title': const TextStyle(color: Color(0xff61aeee)),
|
||||
'emphasis': const TextStyle(fontStyle: FontStyle.italic),
|
||||
'strong': const TextStyle(fontWeight: FontWeight.bold),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _ChatH1Config extends HeadingConfig {
|
||||
const _ChatH1Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 32,
|
||||
height: 40 / 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h1.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
|
||||
///config class for h2
|
||||
class _ChatH2Config extends HeadingConfig {
|
||||
const _ChatH2Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 24,
|
||||
height: 30 / 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h2.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
|
||||
class _ChatH3Config extends HeadingConfig {
|
||||
const _ChatH3Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 24,
|
||||
height: 30 / 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h3.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
+2
-242
@@ -2,19 +2,15 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_loading.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/message/ai_markdown_text.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||
import 'package:markdown_widget/markdown_widget.dart';
|
||||
|
||||
import 'selectable_highlight.dart';
|
||||
|
||||
class ChatAITextMessageWidget extends StatelessWidget {
|
||||
const ChatAITextMessageWidget({
|
||||
@@ -59,248 +55,12 @@ class ChatAITextMessageWidget extends StatelessWidget {
|
||||
if (state.text.isEmpty) {
|
||||
return const ChatAILoading();
|
||||
} else {
|
||||
return _textWidgetBuilder(user, context, state.text);
|
||||
return AIMarkdownText(markdown: state.text);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _textWidgetBuilder(
|
||||
User user,
|
||||
BuildContext context,
|
||||
String text,
|
||||
) {
|
||||
return MarkdownWidget(
|
||||
data: text,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
config: configFromContext(context),
|
||||
);
|
||||
}
|
||||
|
||||
MarkdownConfig configFromContext(BuildContext context) {
|
||||
return MarkdownConfig(
|
||||
configs: [
|
||||
HrConfig(color: AFThemeExtension.of(context).textColor),
|
||||
ChatH1Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
ChatH2Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
ChatH3Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
H4Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
H5Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
H6Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
PreConfig(
|
||||
builder: (code, language) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 800,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
|
||||
child: SelectableHighlightView(
|
||||
code,
|
||||
language: language,
|
||||
theme: getHightlineTheme(context),
|
||||
padding: const EdgeInsets.all(14),
|
||||
textStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PConfig(
|
||||
textStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
CodeConfig(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
BlockquoteConfig(
|
||||
sideColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
textColor: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, TextStyle> getHightlineTheme(BuildContext context) {
|
||||
return {
|
||||
'root': TextStyle(
|
||||
color: const Color(0xffabb2bf),
|
||||
backgroundColor:
|
||||
Theme.of(context).isLightMode ? Colors.white : Colors.black38,
|
||||
),
|
||||
'comment':
|
||||
const TextStyle(color: Color(0xff5c6370), fontStyle: FontStyle.italic),
|
||||
'quote':
|
||||
const TextStyle(color: Color(0xff5c6370), fontStyle: FontStyle.italic),
|
||||
'doctag': const TextStyle(color: Color(0xffc678dd)),
|
||||
'keyword': const TextStyle(color: Color(0xffc678dd)),
|
||||
'formula': const TextStyle(color: Color(0xffc678dd)),
|
||||
'section': const TextStyle(color: Color(0xffe06c75)),
|
||||
'name': const TextStyle(color: Color(0xffe06c75)),
|
||||
'selector-tag': const TextStyle(color: Color(0xffe06c75)),
|
||||
'deletion': const TextStyle(color: Color(0xffe06c75)),
|
||||
'subst': const TextStyle(color: Color(0xffe06c75)),
|
||||
'literal': const TextStyle(color: Color(0xff56b6c2)),
|
||||
'string': const TextStyle(color: Color(0xff98c379)),
|
||||
'regexp': const TextStyle(color: Color(0xff98c379)),
|
||||
'addition': const TextStyle(color: Color(0xff98c379)),
|
||||
'attribute': const TextStyle(color: Color(0xff98c379)),
|
||||
'meta-string': const TextStyle(color: Color(0xff98c379)),
|
||||
'built_in': const TextStyle(color: Color(0xffe6c07b)),
|
||||
'attr': const TextStyle(color: Color(0xffd19a66)),
|
||||
'variable': const TextStyle(color: Color(0xffd19a66)),
|
||||
'template-variable': const TextStyle(color: Color(0xffd19a66)),
|
||||
'type': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-class': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-attr': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-pseudo': const TextStyle(color: Color(0xffd19a66)),
|
||||
'number': const TextStyle(color: Color(0xffd19a66)),
|
||||
'symbol': const TextStyle(color: Color(0xff61aeee)),
|
||||
'bullet': const TextStyle(color: Color(0xff61aeee)),
|
||||
'link': const TextStyle(color: Color(0xff61aeee)),
|
||||
'meta': const TextStyle(color: Color(0xff61aeee)),
|
||||
'selector-id': const TextStyle(color: Color(0xff61aeee)),
|
||||
'title': const TextStyle(color: Color(0xff61aeee)),
|
||||
'emphasis': const TextStyle(fontStyle: FontStyle.italic),
|
||||
'strong': const TextStyle(fontWeight: FontWeight.bold),
|
||||
};
|
||||
}
|
||||
|
||||
class ChatH1Config extends HeadingConfig {
|
||||
const ChatH1Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 32,
|
||||
height: 40 / 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h1.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
|
||||
///config class for h2
|
||||
class ChatH2Config extends HeadingConfig {
|
||||
const ChatH2Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 24,
|
||||
height: 30 / 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h2.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
|
||||
class ChatH3Config extends HeadingConfig {
|
||||
const ChatH3Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 24,
|
||||
height: 30 / 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h3.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
|
||||
class StreamingError extends StatelessWidget {
|
||||
|
||||
-1
@@ -52,7 +52,6 @@ class TextMessageText extends StatelessWidget {
|
||||
text,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
lineHeight: 1.5,
|
||||
maxLines: null,
|
||||
selectable: true,
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
|
||||
@@ -52,7 +52,7 @@ class DatabaseTabBarBloc
|
||||
_createLinkedView(layout.layoutType, name ?? layout.layoutName);
|
||||
},
|
||||
deleteView: (String viewId) async {
|
||||
final result = await ViewBackendService.delete(viewId: viewId);
|
||||
final result = await ViewBackendService.deleteView(viewId: viewId);
|
||||
result.fold(
|
||||
(l) {},
|
||||
(r) => Log.error(r),
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:percent_indicator/percent_indicator.dart';
|
||||
|
||||
import '../../application/cell/bloc/checklist_cell_bloc.dart';
|
||||
|
||||
class ChecklistProgressBar extends StatefulWidget {
|
||||
@@ -68,7 +68,7 @@ class _ChecklistProgressBarState extends State<ChecklistProgressBar> {
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: PlatformExtension.isDesktop ? 36 : 45,
|
||||
width: 45,
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
child: Text(
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:appflowy/util/color_to_hex_string.dart';
|
||||
import 'package:appflowy/util/debounce.dart';
|
||||
import 'package:appflowy/util/throttle.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
@@ -37,6 +38,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'document_bloc.freezed.dart';
|
||||
|
||||
bool enableDocumentInternalLog = false;
|
||||
|
||||
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
DocumentBloc({
|
||||
required this.documentId,
|
||||
@@ -212,6 +215,10 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
}
|
||||
|
||||
Future<EditorState?> _initAppFlowyEditorState(DocumentDataPB data) async {
|
||||
if (enableDocumentInternalLog) {
|
||||
Log.info('document data: ${data.toProto3Json()}');
|
||||
}
|
||||
|
||||
final document = data.toDocument();
|
||||
if (document == null) {
|
||||
assert(false, 'document is null');
|
||||
|
||||
+8
@@ -180,6 +180,8 @@ extension NodeToBlock on Node {
|
||||
String? parentId,
|
||||
String? childrenId,
|
||||
Attributes? attributes,
|
||||
String? externalId,
|
||||
String? externalType,
|
||||
}) {
|
||||
assert(id.isNotEmpty);
|
||||
final block = BlockPB.create()
|
||||
@@ -192,6 +194,12 @@ extension NodeToBlock on Node {
|
||||
if (parentId != null && parentId.isNotEmpty) {
|
||||
block.parentId = parentId;
|
||||
}
|
||||
if (externalId != null && externalId.isNotEmpty) {
|
||||
block.externalId = externalId;
|
||||
}
|
||||
if (externalType != null && externalType.isNotEmpty) {
|
||||
block.externalType = externalType;
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
|
||||
+23
-13
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@@ -21,6 +22,8 @@ import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:nanoid/nanoid.dart';
|
||||
|
||||
const _kExternalTextType = 'text';
|
||||
|
||||
/// Uses to adjust the data structure between the editor and the backend.
|
||||
///
|
||||
/// The editor uses a tree structure to represent the document, while the backend uses a flat structure.
|
||||
@@ -34,11 +37,9 @@ class TransactionAdapter {
|
||||
final DocumentService documentService;
|
||||
final String documentId;
|
||||
|
||||
final bool _enableDebug = false;
|
||||
|
||||
Future<void> apply(Transaction transaction, EditorState editorState) async {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
if (_enableDebug) {
|
||||
if (enableDocumentInternalLog) {
|
||||
Log.debug('transaction => ${transaction.toJson()}');
|
||||
}
|
||||
final actions = transaction.operations
|
||||
@@ -60,8 +61,10 @@ class TransactionAdapter {
|
||||
textId: payload.textId,
|
||||
delta: payload.delta,
|
||||
);
|
||||
if (_enableDebug) {
|
||||
Log.debug('create external text: ${payload.delta}');
|
||||
if (enableDocumentInternalLog) {
|
||||
Log.debug(
|
||||
'[editor_transaction_adapter] create external text: ${payload.delta}',
|
||||
);
|
||||
}
|
||||
} else if (type == TextDeltaType.update) {
|
||||
await documentService.updateExternalText(
|
||||
@@ -69,8 +72,10 @@ class TransactionAdapter {
|
||||
textId: payload.textId,
|
||||
delta: payload.delta,
|
||||
);
|
||||
if (_enableDebug) {
|
||||
Log.debug('update external text: ${payload.delta}');
|
||||
if (enableDocumentInternalLog) {
|
||||
Log.debug(
|
||||
'[editor_transaction_adapter] update external text: ${payload.delta}',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,9 +87,9 @@ class TransactionAdapter {
|
||||
);
|
||||
final elapsed = stopwatch.elapsedMilliseconds;
|
||||
stopwatch.stop();
|
||||
if (_enableDebug) {
|
||||
if (enableDocumentInternalLog) {
|
||||
Log.debug(
|
||||
'apply transaction cost: total $elapsed ms, converter action $actionCostTime ms, apply action ${elapsed - actionCostTime} ms',
|
||||
'[editor_transaction_adapter] apply transaction cost: total $elapsed ms, converter action $actionCostTime ms, apply action ${elapsed - actionCostTime} ms',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -136,8 +141,9 @@ extension on InsertOperation {
|
||||
// create the external text if the node contains the delta in its data.
|
||||
final delta = node.delta;
|
||||
TextDeltaPayloadPB? textDeltaPayloadPB;
|
||||
String? textId;
|
||||
if (delta != null) {
|
||||
final textId = nanoid(6);
|
||||
textId = nanoid(6);
|
||||
|
||||
textDeltaPayloadPB = TextDeltaPayloadPB(
|
||||
documentId: documentId,
|
||||
@@ -148,13 +154,17 @@ extension on InsertOperation {
|
||||
// sync the text id to the node
|
||||
node.externalValues = ExternalValues(
|
||||
externalId: textId,
|
||||
externalType: 'text',
|
||||
externalType: _kExternalTextType,
|
||||
);
|
||||
}
|
||||
|
||||
// remove the delta from the data when the incremental update is stable.
|
||||
final payload = BlockActionPayloadPB()
|
||||
..block = node.toBlock(childrenId: nanoid(6))
|
||||
..block = node.toBlock(
|
||||
childrenId: nanoid(6),
|
||||
externalId: textId,
|
||||
externalType: textId != null ? _kExternalTextType : null,
|
||||
)
|
||||
..parentId = parentId
|
||||
..prevId = prevId;
|
||||
|
||||
@@ -248,7 +258,7 @@ extension on UpdateOperation {
|
||||
|
||||
node.externalValues = ExternalValues(
|
||||
externalId: textId,
|
||||
externalType: 'text',
|
||||
externalType: _kExternalTextType,
|
||||
);
|
||||
|
||||
actions.add(
|
||||
|
||||
+16
-5
@@ -1,16 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
CodeBlockCopyBuilder codeBlockCopyBuilder =
|
||||
(_, node) => _CopyButton(node: node);
|
||||
@@ -28,16 +29,26 @@ class _CopyButton extends StatelessWidget {
|
||||
message: LocaleKeys.document_codeBlock_copyTooltip.tr(),
|
||||
child: FlowyIconButton(
|
||||
onPressed: () async {
|
||||
final delta = node.delta;
|
||||
if (delta == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final document = Document.blank()
|
||||
..insert([0], [node.copyWith()])
|
||||
..toJson();
|
||||
|
||||
await getIt<ClipboardService>().setData(
|
||||
ClipboardServiceData(
|
||||
plainText: node.delta?.toPlainText(),
|
||||
plainText: delta.toPlainText(),
|
||||
inAppJson: jsonEncode(document.toJson()),
|
||||
),
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
showSnackBarMessage(
|
||||
showToastNotification(
|
||||
context,
|
||||
LocaleKeys.document_codeBlock_codeCopiedSnackbar.tr(),
|
||||
message: LocaleKeys.document_codeBlock_codeCopiedSnackbar.tr(),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
+2
-2
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart';
|
||||
@@ -10,6 +8,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
|
||||
@@ -54,6 +53,7 @@ CommandShortcutEventHandler _pasteCommandHandler = (editorState) {
|
||||
|
||||
// try to paste the content in order, if any of them is failed, then try the next one
|
||||
if (inAppJson != null && inAppJson.isNotEmpty) {
|
||||
debugPrint('paste in app json: $inAppJson');
|
||||
await editorState.deleteSelectionIfNeeded();
|
||||
if (await editorState.pasteInAppJson(inAppJson)) {
|
||||
return;
|
||||
|
||||
+13
-3
@@ -75,7 +75,9 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
|
||||
child: BlocConsumer<DocumentImmersiveCoverBloc,
|
||||
DocumentImmersiveCoverState>(
|
||||
listener: (context, state) {
|
||||
textEditingController.text = state.name;
|
||||
if (textEditingController.text.isEmpty) {
|
||||
textEditingController.text = state.name;
|
||||
}
|
||||
},
|
||||
builder: (_, state) {
|
||||
final iconAndTitle = _buildIconAndTitle(context, state);
|
||||
@@ -159,8 +161,16 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
|
||||
state.cover.isNone || state.cover.isPresets ? null : Colors.white,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onChanged: _rename,
|
||||
onSubmitted: _rename,
|
||||
onChanged: (name) => Debounce.debounce(
|
||||
'rename',
|
||||
const Duration(milliseconds: 300),
|
||||
() => _rename(name),
|
||||
),
|
||||
onSubmitted: (name) => Debounce.debounce(
|
||||
'rename',
|
||||
const Duration(milliseconds: 300),
|
||||
() => _rename(name),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:markdown/markdown.dart' as md;
|
||||
|
||||
class MarkdownCodeBlockParser extends CustomMarkdownParser {
|
||||
const MarkdownCodeBlockParser();
|
||||
|
||||
@override
|
||||
List<Node> transform(
|
||||
md.Node element,
|
||||
List<CustomMarkdownParser> parsers, {
|
||||
MarkdownListType listType = MarkdownListType.unknown,
|
||||
int? startNumber,
|
||||
}) {
|
||||
if (element is! md.Element) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (element.tag != 'pre') {
|
||||
return [];
|
||||
}
|
||||
|
||||
final ec = element.children;
|
||||
if (ec == null || ec.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final code = ec.first;
|
||||
if (code is! md.Element || code.tag != 'code') {
|
||||
return [];
|
||||
}
|
||||
|
||||
String? language;
|
||||
if (code.attributes.containsKey('class')) {
|
||||
final classes = code.attributes['class']!.split(' ');
|
||||
final languageClass = classes.firstWhere(
|
||||
(c) => c.startsWith('language-'),
|
||||
orElse: () => '',
|
||||
);
|
||||
language = languageClass.substring('language-'.length);
|
||||
}
|
||||
|
||||
return [
|
||||
codeBlockNode(
|
||||
language: language,
|
||||
delta: Delta()..insert(code.textContent.trimRight()),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
export 'callout_node_parser.dart';
|
||||
export 'markdown_code_parser.dart';
|
||||
export 'math_equation_node_parser.dart';
|
||||
export 'toggle_list_node_parser.dart';
|
||||
+4
-5
@@ -8,6 +8,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/me
|
||||
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/service_handler.dart';
|
||||
import 'package:appflowy/shared/list_extension.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
@@ -64,11 +65,9 @@ class InlinePageReferenceService extends InlineActionsDelegate {
|
||||
|
||||
_recentViewsInitialized = true;
|
||||
|
||||
final views = (await _recentService.recentViews())
|
||||
.reversed
|
||||
.map((e) => e.item)
|
||||
.toSet()
|
||||
.toList();
|
||||
final sectionViews = await _recentService.recentViews();
|
||||
final views =
|
||||
sectionViews.unique((e) => e.item.id).map((e) => e.item).toList();
|
||||
|
||||
// Filter by viewLayout
|
||||
views.retainWhere(
|
||||
|
||||
@@ -35,6 +35,9 @@ enum FeatureFlag {
|
||||
// used for controlling whether to show plan+billing options in settings
|
||||
planBilling,
|
||||
|
||||
// used for space design
|
||||
spaceDesign,
|
||||
|
||||
// used for ignore the conflicted feature flag
|
||||
unknown;
|
||||
|
||||
@@ -88,6 +91,8 @@ enum FeatureFlag {
|
||||
|
||||
bool get isOn {
|
||||
if ([
|
||||
// release this feature in version 0.6.1
|
||||
FeatureFlag.spaceDesign,
|
||||
// release this feature in version 0.5.9
|
||||
FeatureFlag.search,
|
||||
// release this feature in version 0.5.6
|
||||
@@ -105,15 +110,16 @@ enum FeatureFlag {
|
||||
}
|
||||
|
||||
switch (this) {
|
||||
case FeatureFlag.search:
|
||||
case FeatureFlag.syncDocument:
|
||||
case FeatureFlag.syncDatabase:
|
||||
case FeatureFlag.spaceDesign:
|
||||
return true;
|
||||
case FeatureFlag.collaborativeWorkspace:
|
||||
case FeatureFlag.membersSettings:
|
||||
case FeatureFlag.planBilling:
|
||||
case FeatureFlag.unknown:
|
||||
return false;
|
||||
case FeatureFlag.search:
|
||||
case FeatureFlag.syncDocument:
|
||||
case FeatureFlag.syncDatabase:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +137,8 @@ enum FeatureFlag {
|
||||
return 'if it\'s on, the command palette and search button will be available';
|
||||
case FeatureFlag.planBilling:
|
||||
return 'if it\'s on, plan and billing pages will be available in Settings';
|
||||
case FeatureFlag.spaceDesign:
|
||||
return 'if it\'s on, the space design feature will be available';
|
||||
case FeatureFlag.unknown:
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/markdown_code_parser.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
Document customMarkdownToDocument(String markdown) {
|
||||
return markdownToDocument(
|
||||
markdown,
|
||||
markdownParsers: [
|
||||
const MarkdownCodeBlockParser(),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
|
||||
class WindowSizeManager {
|
||||
static const double minWindowHeight = 600.0;
|
||||
static const double minWindowWidth = 800.0;
|
||||
static const double minWindowHeight = 640.0;
|
||||
static const double minWindowWidth = 960.0;
|
||||
// Preventing failed assertion due to Texture Descriptor Validation
|
||||
static const double maxWindowHeight = 8192.0;
|
||||
static const double maxWindowWidth = 8192.0;
|
||||
|
||||
+2
-10
@@ -54,16 +54,8 @@ class DesktopSignInScreen extends StatelessWidget {
|
||||
const SignInAnonymousButtonV2(),
|
||||
const VSpace(10),
|
||||
|
||||
SwitchSignInSignUpButton(
|
||||
onTap: () {
|
||||
final type = state.loginType == LoginType.signIn
|
||||
? LoginType.signUp
|
||||
: LoginType.signIn;
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(SignInEvent.switchLoginType(type));
|
||||
},
|
||||
),
|
||||
// sign in agreement
|
||||
const SignInAgreement(),
|
||||
|
||||
// loading status
|
||||
const VSpace(indicatorMinHeight),
|
||||
|
||||
+1
-10
@@ -39,16 +39,7 @@ class MobileSignInScreen extends StatelessWidget {
|
||||
const VSpace(spacing),
|
||||
const SignInAnonymousButtonV2(),
|
||||
const VSpace(spacing),
|
||||
SwitchSignInSignUpButton(
|
||||
onTap: () {
|
||||
final type = state.loginType == LoginType.signIn
|
||||
? LoginType.signUp
|
||||
: LoginType.signIn;
|
||||
context.read<SignInBloc>().add(
|
||||
SignInEvent.switchLoginType(type),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SignInAgreement(),
|
||||
const VSpace(spacing),
|
||||
_buildSettingsButton(context),
|
||||
if (!isAuthEnabled) const Spacer(flex: 2),
|
||||
|
||||
+11
-10
@@ -1,14 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
|
||||
class SignInWithMagicLinkButtons extends StatefulWidget {
|
||||
const SignInWithMagicLinkButtons({super.key});
|
||||
@@ -53,18 +53,19 @@ class _SignInWithMagicLinkButtonsState
|
||||
|
||||
void _sendMagicLink(BuildContext context, String email) {
|
||||
if (!isEmail(email)) {
|
||||
return showSnackBarMessage(
|
||||
return showToastNotification(
|
||||
context,
|
||||
LocaleKeys.signIn_invalidEmail.tr(),
|
||||
duration: const Duration(seconds: 8),
|
||||
message: LocaleKeys.signIn_invalidEmail.tr(),
|
||||
type: ToastificationType.error,
|
||||
);
|
||||
}
|
||||
|
||||
context.read<SignInBloc>().add(SignInEvent.signedWithMagicLink(email));
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
LocaleKeys.signIn_magicLinkSent.tr(),
|
||||
duration: const Duration(seconds: 1000),
|
||||
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
title: LocaleKeys.signIn_magicLinkSent.tr(),
|
||||
description: LocaleKeys.signIn_magicLinkSentDescription.tr(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SignInAgreement extends StatelessWidget {
|
||||
const SignInAgreement({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${LocaleKeys.web_signInAgreement.tr()} ',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
),
|
||||
TextSpan(
|
||||
text: '${LocaleKeys.web_termOfUse.tr()} ',
|
||||
style: const TextStyle(color: Colors.blue, fontSize: 12),
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => afLaunchUrlString('https://appflowy.io/terms'),
|
||||
),
|
||||
TextSpan(
|
||||
text: '${LocaleKeys.web_and.tr()} ',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
),
|
||||
TextSpan(
|
||||
text: LocaleKeys.web_privacyPolicy.tr(),
|
||||
style: const TextStyle(color: Colors.blue, fontSize: 12),
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => afLaunchUrlString('https://appflowy.io/privacy'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
export 'magic_link_sign_in_buttons.dart';
|
||||
export 'sign_in_anonymous_button.dart';
|
||||
export 'sign_in_or_logout_button.dart';
|
||||
export 'switch_sign_in_sign_up_button.dart';
|
||||
|
||||
// export 'switch_sign_in_sign_up_button.dart';
|
||||
export 'third_party_sign_in_buttons.dart';
|
||||
export 'sign_in_agreement.dart';
|
||||
+6
-3
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/shared/list_extension.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_listener.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
@@ -7,6 +8,7 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// This is a lazy-singleton to share recent views across the application.
|
||||
@@ -37,7 +39,7 @@ class CachedRecentService {
|
||||
|
||||
_listener.start(recentViewsUpdated: _recentViewsUpdated);
|
||||
_recentViews = await _readRecentViews().fold(
|
||||
(s) => s.items,
|
||||
(s) => s.items.unique((e) => e.item.id),
|
||||
(_) => [],
|
||||
);
|
||||
_completer.complete();
|
||||
@@ -68,7 +70,8 @@ class CachedRecentService {
|
||||
|
||||
Future<FlowyResult<RepeatedRecentViewPB, FlowyError>>
|
||||
_readRecentViews() async {
|
||||
final result = await FolderEventReadRecentViews().send();
|
||||
final payload = ReadRecentViewsPB(start: Int64(), limit: Int64(100));
|
||||
final result = await FolderEventReadRecentViews(payload).send();
|
||||
return result.fold(
|
||||
(recentViews) {
|
||||
return FlowyResult.success(
|
||||
@@ -101,7 +104,7 @@ class CachedRecentService {
|
||||
final viewIds = result.toNullable();
|
||||
if (viewIds != null) {
|
||||
_recentViews = await _readRecentViews().fold(
|
||||
(s) => s.items,
|
||||
(s) => s.items.unique((e) => e.item.id),
|
||||
(_) => [],
|
||||
);
|
||||
}
|
||||
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
part 'download_model_bloc.freezed.dart';
|
||||
|
||||
class DownloadModelBloc extends Bloc<DownloadModelEvent, DownloadModelState> {
|
||||
DownloadModelBloc(LLMModelPB model)
|
||||
: super(DownloadModelState(model: model)) {
|
||||
on<DownloadModelEvent>(_handleEvent);
|
||||
}
|
||||
|
||||
Future<void> _handleEvent(
|
||||
DownloadModelEvent event,
|
||||
Emitter<DownloadModelState> emit,
|
||||
) async {
|
||||
await event.when(
|
||||
started: () async {
|
||||
final downloadStream = DownloadingStream();
|
||||
downloadStream.listen(
|
||||
onModelPercentage: (name, percent) {
|
||||
if (!isClosed) {
|
||||
add(
|
||||
DownloadModelEvent.updatePercent(name, percent),
|
||||
);
|
||||
}
|
||||
},
|
||||
onPluginPercentage: (percent) {
|
||||
if (!isClosed) {
|
||||
add(DownloadModelEvent.updatePercent("AppFlowy Plugin", percent));
|
||||
}
|
||||
},
|
||||
onFinish: () {
|
||||
add(const DownloadModelEvent.downloadFinish());
|
||||
},
|
||||
onError: (err) {
|
||||
// emit(state.copyWith(downloadError: err));
|
||||
},
|
||||
);
|
||||
|
||||
final payload =
|
||||
DownloadLLMPB(progressStream: Int64(downloadStream.nativePort));
|
||||
final result = await ChatEventDownloadLLMResource(payload).send();
|
||||
result.fold((_) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
downloadStream: downloadStream,
|
||||
loadingState: const LoadingState.finish(),
|
||||
downloadError: null,
|
||||
),
|
||||
);
|
||||
}, (err) {
|
||||
emit(state.copyWith(loadingState: LoadingState.finish(error: err)));
|
||||
});
|
||||
},
|
||||
updatePercent: (String object, double percent) {
|
||||
emit(state.copyWith(object: object, percent: percent));
|
||||
},
|
||||
downloadFinish: () {
|
||||
emit(state.copyWith(isFinish: true));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DownloadModelEvent with _$DownloadModelEvent {
|
||||
const factory DownloadModelEvent.started() = _Started;
|
||||
const factory DownloadModelEvent.updatePercent(
|
||||
String object,
|
||||
double percent,
|
||||
) = _UpdatePercent;
|
||||
const factory DownloadModelEvent.downloadFinish() = _DownloadFinish;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DownloadModelState with _$DownloadModelState {
|
||||
const factory DownloadModelState({
|
||||
required LLMModelPB model,
|
||||
DownloadingStream? downloadStream,
|
||||
String? downloadError,
|
||||
@Default("") String object,
|
||||
@Default(0) double percent,
|
||||
@Default(false) bool isFinish,
|
||||
@Default(LoadingState.loading()) LoadingState loadingState,
|
||||
}) = _DownloadModelState;
|
||||
}
|
||||
|
||||
class DownloadingStream {
|
||||
DownloadingStream() {
|
||||
_port.handler = _controller.add;
|
||||
}
|
||||
|
||||
final RawReceivePort _port = RawReceivePort();
|
||||
StreamSubscription<String>? _sub;
|
||||
final StreamController<String> _controller = StreamController.broadcast();
|
||||
int get nativePort => _port.sendPort.nativePort;
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _sub?.cancel();
|
||||
await _controller.close();
|
||||
_port.close();
|
||||
}
|
||||
|
||||
void listen({
|
||||
void Function(String modelName, double percent)? onModelPercentage,
|
||||
void Function(double percent)? onPluginPercentage,
|
||||
void Function(String data)? onError,
|
||||
void Function()? onFinish,
|
||||
}) {
|
||||
_sub = _controller.stream.listen((text) {
|
||||
if (text.contains(':progress:')) {
|
||||
final progressIndex = text.indexOf(':progress:');
|
||||
final modelName = text.substring(0, progressIndex);
|
||||
final progressValue = text
|
||||
.substring(progressIndex + 10); // 10 is the length of ":progress:"
|
||||
final percent = double.tryParse(progressValue);
|
||||
if (percent != null) {
|
||||
onModelPercentage?.call(modelName, percent);
|
||||
}
|
||||
} else if (text.startsWith('plugin:progress:')) {
|
||||
final percent = double.tryParse(text.substring(16));
|
||||
if (percent != null) {
|
||||
onPluginPercentage?.call(percent);
|
||||
}
|
||||
} else if (text.startsWith('finish')) {
|
||||
onFinish?.call();
|
||||
} else if (text.startsWith('error:')) {
|
||||
// substring 6 to remove "error:"
|
||||
onError?.call(text.substring(6));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'local_ai_bloc.freezed.dart';
|
||||
|
||||
class LocalAISettingBloc
|
||||
extends Bloc<LocalAISettingEvent, LocalAISettingState> {
|
||||
LocalAISettingBloc()
|
||||
: listener = LocalLLMListener(),
|
||||
super(const LocalAISettingState()) {
|
||||
listener.start(
|
||||
stateCallback: (newState) {
|
||||
if (!isClosed) {
|
||||
add(LocalAISettingEvent.updateLLMRunningState(newState.state));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
on<LocalAISettingEvent>(_handleEvent);
|
||||
}
|
||||
|
||||
final LocalLLMListener listener;
|
||||
|
||||
/// Handles incoming events and dispatches them to the appropriate handler.
|
||||
Future<void> _handleEvent(
|
||||
LocalAISettingEvent event,
|
||||
Emitter<LocalAISettingState> emit,
|
||||
) async {
|
||||
await event.when(
|
||||
started: _handleStarted,
|
||||
didLoadModelInfo: (FlowyResult<LLMModelInfoPB, FlowyError> result) {
|
||||
result.fold(
|
||||
(modelInfo) {
|
||||
_fetchCurremtLLMState();
|
||||
emit(
|
||||
state.copyWith(
|
||||
modelInfo: modelInfo,
|
||||
models: modelInfo.models,
|
||||
selectedLLMModel: modelInfo.selectedModel,
|
||||
fetchModelInfoState: const LoadingState.finish(),
|
||||
),
|
||||
);
|
||||
},
|
||||
(err) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
fetchModelInfoState: LoadingState.finish(error: err),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
selectLLMConfig: (LLMModelPB llmModel) async {
|
||||
final result = await ChatEventUpdateLocalLLM(llmModel).send();
|
||||
result.fold(
|
||||
(llmResource) {
|
||||
// If all resources are downloaded, show reload plugin
|
||||
if (llmResource.pendingResources.isNotEmpty) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectedLLMModel: llmModel,
|
||||
localAIInfo: LocalAIProgress.showDownload(
|
||||
llmResource,
|
||||
llmModel,
|
||||
),
|
||||
selectLLMState: const LoadingState.finish(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectedLLMModel: llmModel,
|
||||
selectLLMState: const LoadingState.finish(),
|
||||
localAIInfo: const LocalAIProgress.checkPluginState(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
(err) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectLLMState: LoadingState.finish(error: err),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
refreshLLMState: (LocalModelResourcePB llmResource) {
|
||||
if (state.selectedLLMModel == null) {
|
||||
Log.error(
|
||||
'Unexpected null selected config. It should be set already',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// reload plugin if all resources are downloaded
|
||||
if (llmResource.pendingResources.isEmpty) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
localAIInfo: const LocalAIProgress.checkPluginState(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (state.selectedLLMModel != null) {
|
||||
// Go to download page if the selected model is downloading
|
||||
if (llmResource.isDownloading) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
localAIInfo:
|
||||
LocalAIProgress.startDownloading(state.selectedLLMModel!),
|
||||
selectLLMState: const LoadingState.finish(),
|
||||
),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
localAIInfo: LocalAIProgress.showDownload(
|
||||
llmResource,
|
||||
state.selectedLLMModel!,
|
||||
),
|
||||
selectLLMState: const LoadingState.finish(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
startDownloadModel: (LLMModelPB llmModel) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
localAIInfo: LocalAIProgress.startDownloading(llmModel),
|
||||
selectLLMState: const LoadingState.finish(),
|
||||
),
|
||||
);
|
||||
},
|
||||
cancelDownload: () async {
|
||||
final _ = await ChatEventCancelDownloadLLMResource().send();
|
||||
_fetchCurremtLLMState();
|
||||
},
|
||||
finishDownload: () async {
|
||||
emit(
|
||||
state.copyWith(localAIInfo: const LocalAIProgress.finishDownload()),
|
||||
);
|
||||
},
|
||||
updateLLMRunningState: (RunningStatePB newRunningState) {
|
||||
if (newRunningState == RunningStatePB.Stopped) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
runningState: newRunningState,
|
||||
localAIInfo: const LocalAIProgress.checkPluginState(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(state.copyWith(runningState: newRunningState));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _fetchCurremtLLMState() async {
|
||||
final result = await ChatEventGetLocalLLMState().send();
|
||||
result.fold(
|
||||
(llmResource) {
|
||||
if (!isClosed) {
|
||||
add(LocalAISettingEvent.refreshLLMState(llmResource));
|
||||
}
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles the event to fetch local AI settings when the application starts.
|
||||
Future<void> _handleStarted() async {
|
||||
final result = await ChatEventRefreshLocalAIModelInfo().send();
|
||||
if (!isClosed) {
|
||||
add(LocalAISettingEvent.didLoadModelInfo(result));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await listener.stop();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class LocalAISettingEvent with _$LocalAISettingEvent {
|
||||
const factory LocalAISettingEvent.started() = _Started;
|
||||
const factory LocalAISettingEvent.didLoadModelInfo(
|
||||
FlowyResult<LLMModelInfoPB, FlowyError> result,
|
||||
) = _ModelInfo;
|
||||
const factory LocalAISettingEvent.selectLLMConfig(LLMModelPB config) =
|
||||
_SelectLLMConfig;
|
||||
|
||||
const factory LocalAISettingEvent.refreshLLMState(
|
||||
LocalModelResourcePB llmResource,
|
||||
) = _RefreshLLMResource;
|
||||
const factory LocalAISettingEvent.startDownloadModel(LLMModelPB llmModel) =
|
||||
_StartDownloadModel;
|
||||
|
||||
const factory LocalAISettingEvent.cancelDownload() = _CancelDownload;
|
||||
const factory LocalAISettingEvent.finishDownload() = _FinishDownload;
|
||||
const factory LocalAISettingEvent.updateLLMRunningState(
|
||||
RunningStatePB newRunningState,
|
||||
) = _RunningState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class LocalAISettingState with _$LocalAISettingState {
|
||||
const factory LocalAISettingState({
|
||||
LLMModelInfoPB? modelInfo,
|
||||
LLMModelPB? selectedLLMModel,
|
||||
LocalAIProgress? localAIInfo,
|
||||
@Default(LoadingState.loading()) LoadingState fetchModelInfoState,
|
||||
@Default(LoadingState.loading()) LoadingState selectLLMState,
|
||||
@Default([]) List<LLMModelPB> models,
|
||||
@Default(RunningStatePB.Connecting) RunningStatePB runningState,
|
||||
}) = _LocalAISettingState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class LocalAIProgress with _$LocalAIProgress {
|
||||
// when user select a new model, it will call requestDownload
|
||||
const factory LocalAIProgress.requestDownloadInfo(
|
||||
LocalModelResourcePB llmResource,
|
||||
LLMModelPB llmModel,
|
||||
) = _RequestDownload;
|
||||
|
||||
// when user comes back to the setting page, it will auto detect current llm state
|
||||
const factory LocalAIProgress.showDownload(
|
||||
LocalModelResourcePB llmResource,
|
||||
LLMModelPB llmModel,
|
||||
) = _DownloadNeeded;
|
||||
|
||||
// when start downloading the model
|
||||
const factory LocalAIProgress.startDownloading(LLMModelPB llmModel) =
|
||||
_Downloading;
|
||||
const factory LocalAIProgress.finishDownload() = _Finish;
|
||||
const factory LocalAIProgress.checkPluginState() = _PluginState;
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_notification.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/notification.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/subject.pb.dart';
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
typedef PluginStateCallback = void Function(PluginStatePB state);
|
||||
|
||||
class LocalLLMListener {
|
||||
LocalLLMListener() {
|
||||
_parser =
|
||||
ChatNotificationParser(id: "appflowy_chat_plugin", callback: _callback);
|
||||
_subscription = RustStreamReceiver.listen(
|
||||
(observable) => _parser?.parse(observable),
|
||||
);
|
||||
}
|
||||
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
ChatNotificationParser? _parser;
|
||||
|
||||
PluginStateCallback? stateCallback;
|
||||
void Function()? finishStreamingCallback;
|
||||
|
||||
void start({
|
||||
PluginStateCallback? stateCallback,
|
||||
}) {
|
||||
this.stateCallback = stateCallback;
|
||||
}
|
||||
|
||||
void _callback(
|
||||
ChatNotification ty,
|
||||
FlowyResult<Uint8List, FlowyError> result,
|
||||
) {
|
||||
result.map((r) {
|
||||
switch (ty) {
|
||||
case ChatNotification.UpdateChatPluginState:
|
||||
stateCallback?.call(PluginStatePB.fromBuffer(r));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _subscription?.cancel();
|
||||
_subscription = null;
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'plugin_state_bloc.freezed.dart';
|
||||
|
||||
class PluginStateBloc extends Bloc<PluginStateEvent, PluginStateState> {
|
||||
PluginStateBloc()
|
||||
: listener = LocalLLMListener(),
|
||||
super(const PluginStateState(action: PluginStateAction.init())) {
|
||||
listener.start(
|
||||
stateCallback: (pluginState) {
|
||||
if (!isClosed) {
|
||||
add(PluginStateEvent.updateState(pluginState));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
on<PluginStateEvent>(_handleEvent);
|
||||
}
|
||||
|
||||
final LocalLLMListener listener;
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await listener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _handleEvent(
|
||||
PluginStateEvent event,
|
||||
Emitter<PluginStateState> emit,
|
||||
) async {
|
||||
await event.when(
|
||||
started: () async {
|
||||
final result = await ChatEventGetPluginState().send();
|
||||
result.fold(
|
||||
(pluginState) {
|
||||
if (!isClosed) {
|
||||
add(PluginStateEvent.updateState(pluginState));
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err.toString()),
|
||||
);
|
||||
},
|
||||
updateState: (PluginStatePB pluginState) {
|
||||
switch (pluginState.state) {
|
||||
case RunningStatePB.Running:
|
||||
emit(const PluginStateState(action: PluginStateAction.ready()));
|
||||
break;
|
||||
default:
|
||||
emit(
|
||||
state.copyWith(action: const PluginStateAction.reloadRequired()),
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
restartLocalAI: () {
|
||||
ChatEventRestartLocalAI().send();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class PluginStateEvent with _$PluginStateEvent {
|
||||
const factory PluginStateEvent.started() = _Started;
|
||||
const factory PluginStateEvent.updateState(PluginStatePB pluginState) =
|
||||
_UpdatePluginState;
|
||||
const factory PluginStateEvent.restartLocalAI() = _RestartLocalAI;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class PluginStateState with _$PluginStateState {
|
||||
const factory PluginStateState({required PluginStateAction action}) =
|
||||
_PluginStateState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class PluginStateAction with _$PluginStateAction {
|
||||
const factory PluginStateAction.init() = _Init;
|
||||
const factory PluginStateAction.ready() = _Ready;
|
||||
const factory PluginStateAction.reloadRequired() = _ReloadRequired;
|
||||
}
|
||||
-195
@@ -1,195 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
part 'setting_local_ai_bloc.freezed.dart';
|
||||
|
||||
class SettingsAILocalBloc
|
||||
extends Bloc<SettingsAILocalEvent, SettingsAILocalState> {
|
||||
SettingsAILocalBloc() : super(const SettingsAILocalState()) {
|
||||
on<SettingsAILocalEvent>(_handleEvent);
|
||||
}
|
||||
|
||||
/// Handles incoming events and dispatches them to the appropriate handler.
|
||||
Future<void> _handleEvent(
|
||||
SettingsAILocalEvent event,
|
||||
Emitter<SettingsAILocalState> emit,
|
||||
) async {
|
||||
await event.when(
|
||||
started: _handleStarted,
|
||||
didUpdateAISetting: (settings) async {
|
||||
_handleDidUpdateAISetting(settings, emit);
|
||||
},
|
||||
updateChatBin: (chatBinPath) async {
|
||||
await _handleUpdatePath(
|
||||
filePath: chatBinPath,
|
||||
emit: emit,
|
||||
stateUpdater: () => state.copyWith(
|
||||
chatBinPath: chatBinPath.trim(),
|
||||
chatBinPathError: null,
|
||||
),
|
||||
errorUpdater: (error) => state.copyWith(chatBinPathError: error),
|
||||
);
|
||||
},
|
||||
updateChatModelPath: (chatModelPath) async {
|
||||
await _handleUpdatePath(
|
||||
filePath: chatModelPath,
|
||||
emit: emit,
|
||||
stateUpdater: () => state.copyWith(
|
||||
chatModelPath: chatModelPath.trim(),
|
||||
chatModelPathError: null,
|
||||
),
|
||||
errorUpdater: (error) => state.copyWith(chatModelPathError: error),
|
||||
);
|
||||
},
|
||||
toggleLocalAI: () async {
|
||||
emit(state.copyWith(localAIEnabled: !state.localAIEnabled));
|
||||
},
|
||||
saveSetting: () async {
|
||||
_handleSaveSetting();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles the event to fetch local AI settings when the application starts.
|
||||
Future<void> _handleStarted() async {
|
||||
final result = await ChatEventGetLocalAISetting().send();
|
||||
result.fold(
|
||||
(setting) {
|
||||
if (!isClosed) {
|
||||
add(SettingsAILocalEvent.didUpdateAISetting(setting));
|
||||
}
|
||||
},
|
||||
(err) => Log.error('Failed to get local AI setting: $err'),
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles the event to update the AI settings in the state.
|
||||
void _handleDidUpdateAISetting(
|
||||
LocalLLMSettingPB settings,
|
||||
Emitter<SettingsAILocalState> emit,
|
||||
) {
|
||||
final newState = state.copyWith(
|
||||
aiSettings: settings,
|
||||
chatBinPath: settings.chatBinPath,
|
||||
chatModelPath: settings.chatModelPath,
|
||||
localAIEnabled: settings.enabled,
|
||||
loadingState: const LoadingState.finish(),
|
||||
);
|
||||
emit(newState.copyWith(saveButtonEnabled: _saveButtonEnabled(newState)));
|
||||
}
|
||||
|
||||
/// Handles updating file paths (both chat binary and chat model paths).
|
||||
Future<void> _handleUpdatePath({
|
||||
required String filePath,
|
||||
required Emitter<SettingsAILocalState> emit,
|
||||
required SettingsAILocalState Function() stateUpdater,
|
||||
required SettingsAILocalState Function(String) errorUpdater,
|
||||
}) async {
|
||||
filePath = filePath.trim();
|
||||
if (filePath.isEmpty) {
|
||||
emit(stateUpdater());
|
||||
return;
|
||||
}
|
||||
|
||||
final validationError = await _validatePath(filePath);
|
||||
if (validationError != null) {
|
||||
emit(errorUpdater(validationError));
|
||||
return;
|
||||
}
|
||||
|
||||
final newState = stateUpdater();
|
||||
emit(newState.copyWith(saveButtonEnabled: _saveButtonEnabled(newState)));
|
||||
}
|
||||
|
||||
/// Validates the provided file path.
|
||||
Future<String?> _validatePath(String filePath) async {
|
||||
if (!isAbsolutePath(filePath)) {
|
||||
return "$filePath must be absolute";
|
||||
}
|
||||
|
||||
if (!await pathExists(filePath)) {
|
||||
return "$filePath does not exist";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Handles saving the updated settings.
|
||||
void _handleSaveSetting() {
|
||||
if (state.aiSettings == null) return;
|
||||
state.aiSettings!.freeze();
|
||||
final newSetting = state.aiSettings!.rebuild((value) {
|
||||
value
|
||||
..chatBinPath = state.chatBinPath ?? value.chatBinPath
|
||||
..chatModelPath = state.chatModelPath ?? value.chatModelPath
|
||||
..enabled = state.localAIEnabled;
|
||||
});
|
||||
|
||||
ChatEventUpdateLocalAISetting(newSetting).send().then((result) {
|
||||
result.fold(
|
||||
(_) {
|
||||
if (!isClosed) {
|
||||
add(SettingsAILocalEvent.didUpdateAISetting(newSetting));
|
||||
}
|
||||
},
|
||||
(err) => Log.error('Failed to update local AI setting: $err'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Determines if the save button should be enabled based on the state.
|
||||
bool _saveButtonEnabled(SettingsAILocalState newState) {
|
||||
return newState.chatBinPathError == null &&
|
||||
newState.chatModelPathError == null &&
|
||||
newState.chatBinPath != null &&
|
||||
newState.chatModelPath != null;
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SettingsAILocalEvent with _$SettingsAILocalEvent {
|
||||
const factory SettingsAILocalEvent.started() = _Started;
|
||||
const factory SettingsAILocalEvent.didUpdateAISetting(
|
||||
LocalLLMSettingPB settings,
|
||||
) = _GetAISetting;
|
||||
const factory SettingsAILocalEvent.updateChatBin(String chatBinPath) =
|
||||
_UpdateChatBin;
|
||||
const factory SettingsAILocalEvent.updateChatModelPath(String chatModelPath) =
|
||||
_UpdateChatModelPath;
|
||||
const factory SettingsAILocalEvent.toggleLocalAI() = _EnableLocalAI;
|
||||
const factory SettingsAILocalEvent.saveSetting() = _SaveSetting;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SettingsAILocalState with _$SettingsAILocalState {
|
||||
const factory SettingsAILocalState({
|
||||
LocalLLMSettingPB? aiSettings,
|
||||
String? chatBinPath,
|
||||
String? chatBinPathError,
|
||||
String? chatModelPath,
|
||||
String? chatModelPathError,
|
||||
@Default(false) bool localAIEnabled,
|
||||
@Default(false) bool saveButtonEnabled,
|
||||
@Default(LoadingState.loading()) LoadingState loadingState,
|
||||
}) = _SettingsAILocalState;
|
||||
}
|
||||
|
||||
/// Checks if a given file path is absolute.
|
||||
bool isAbsolutePath(String filePath) {
|
||||
return path.isAbsolute(filePath);
|
||||
}
|
||||
|
||||
/// Checks if a given file or directory path exists.
|
||||
Future<bool> pathExists(String filePath) async {
|
||||
final file = File(filePath);
|
||||
final directory = Directory(filePath);
|
||||
|
||||
return await file.exists() || await directory.exists();
|
||||
}
|
||||
@@ -141,7 +141,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
if (deletedSpace == null) {
|
||||
return;
|
||||
}
|
||||
await ViewBackendService.delete(viewId: deletedSpace.id);
|
||||
await ViewBackendService.deleteView(viewId: deletedSpace.id);
|
||||
},
|
||||
rename: (space, name) async {
|
||||
add(SpaceEvent.update(name: name));
|
||||
@@ -433,6 +433,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
workspaceId: workspaceId,
|
||||
)..start(
|
||||
sectionChanged: (result) async {
|
||||
Log.info('did receive section views changed');
|
||||
add(const SpaceEvent.didReceiveSpaceUpdate());
|
||||
},
|
||||
);
|
||||
@@ -503,6 +504,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
if (_workspaceId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final user =
|
||||
await UserBackendService.getCurrentUserProfile().getOrThrow();
|
||||
@@ -526,6 +528,13 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
(e) => e.isSpace && e.spacePermission == SpacePermission.publicToAll,
|
||||
);
|
||||
publicViews = publicViews.where((e) => !e.isSpace).toList();
|
||||
|
||||
for (final view in publicViews) {
|
||||
Log.info(
|
||||
'migrating: the public view should be migrated: ${view.name}(${view.id})',
|
||||
);
|
||||
}
|
||||
|
||||
// if there is already a public space, don't migrate the public space
|
||||
// only migrate the public space if there are any public views
|
||||
if (publicViews.isEmpty || containsPublicSpace) {
|
||||
@@ -568,6 +577,13 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
(e) => e.isSpace && e.spacePermission == SpacePermission.private,
|
||||
);
|
||||
privateViews = privateViews.where((e) => !e.isSpace).toList();
|
||||
|
||||
for (final view in privateViews) {
|
||||
Log.info(
|
||||
'migrating: the private view should be migrated: ${view.name}(${view.id})',
|
||||
);
|
||||
}
|
||||
|
||||
if (privateViews.isEmpty || containsPrivateSpace) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
||||
// unpublish the page and all its child pages if they are published
|
||||
await _unpublishPage(view);
|
||||
|
||||
final result = await ViewBackendService.delete(viewId: view.id);
|
||||
final result = await ViewBackendService.deleteView(viewId: view.id);
|
||||
|
||||
emit(
|
||||
result.fold(
|
||||
|
||||
@@ -122,17 +122,17 @@ class ViewBackendService {
|
||||
});
|
||||
}
|
||||
|
||||
static Future<FlowyResult<void, FlowyError>> delete({
|
||||
static Future<FlowyResult<void, FlowyError>> deleteView({
|
||||
required String viewId,
|
||||
}) {
|
||||
final request = RepeatedViewIdPB.create()..items.add(viewId);
|
||||
return FolderEventDeleteView(request).send();
|
||||
}
|
||||
|
||||
static Future<FlowyResult<void, FlowyError>> deleteView({
|
||||
required String viewId,
|
||||
static Future<FlowyResult<void, FlowyError>> deleteViews({
|
||||
required List<String> viewIds,
|
||||
}) {
|
||||
final request = RepeatedViewIdPB.create()..items.add(viewId);
|
||||
final request = RepeatedViewIdPB.create()..items.addAll(viewIds);
|
||||
return FolderEventDeleteView(request).send();
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
|
||||
@@ -8,6 +6,7 @@ import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_v
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class RecentViewsList extends StatelessWidget {
|
||||
@@ -24,7 +23,7 @@ class RecentViewsList extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
// We remove duplicates by converting the list to a set first
|
||||
final List<ViewPB> recentViews =
|
||||
state.views.reversed.map((e) => e.item).toSet().toList();
|
||||
state.views.map((e) => e.item).toSet().toList();
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
|
||||
+2
-2
@@ -4,11 +4,11 @@ import 'dart:io';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/migration/editor_migration.dart';
|
||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/settings/share/import_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/import/import_type.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -220,7 +220,7 @@ class _ImportPanelState extends State<ImportPanel> {
|
||||
Uint8List? _documentDataFrom(ImportType importType, String data) {
|
||||
switch (importType) {
|
||||
case ImportType.markdownOrText:
|
||||
final document = markdownToDocument(data);
|
||||
final document = customMarkdownToDocument(data);
|
||||
return DocumentDataPBFromTo.fromDocument(document)?.writeToBuffer();
|
||||
case ImportType.historyDocument:
|
||||
final document = EditorMigration.migrateDocument(data);
|
||||
|
||||
@@ -27,7 +27,6 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_migration.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
show UserProfilePB;
|
||||
@@ -371,11 +370,6 @@ class _SidebarState extends State<_Sidebar> {
|
||||
final sidebarSectionBloc = context.watch<SidebarSectionsBloc>();
|
||||
final containsSpace = sidebarSectionBloc.state.containsSpace;
|
||||
|
||||
Log.info('fetch the space info from sidebar section: $containsSpace');
|
||||
Log.info(
|
||||
'fetch the space info from space: ${spaceState.spaces.isNotEmpty}',
|
||||
);
|
||||
|
||||
if (containsSpace && spaceState.spaces.isEmpty) {
|
||||
context.read<SpaceBloc>().add(const SpaceEvent.didReceiveSpaceUpdate());
|
||||
}
|
||||
|
||||
+140
-38
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
@@ -223,23 +224,96 @@ class SpaceCancelOrConfirmButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ConfirmDeletionPopup extends StatefulWidget {
|
||||
const ConfirmDeletionPopup({
|
||||
class SpaceOkButton extends StatelessWidget {
|
||||
const SpaceOkButton({
|
||||
super.key,
|
||||
required this.onConfirm,
|
||||
required this.confirmButtonName,
|
||||
this.confirmButtonColor,
|
||||
});
|
||||
|
||||
final VoidCallback onConfirm;
|
||||
final String confirmButtonName;
|
||||
final Color? confirmButtonColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
DecoratedBox(
|
||||
decoration: ShapeDecoration(
|
||||
color: confirmButtonColor ?? Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 9.0),
|
||||
radius: BorderRadius.circular(8),
|
||||
text: FlowyText.regular(
|
||||
confirmButtonName,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: onConfirm,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum ConfirmPopupStyle {
|
||||
onlyOk,
|
||||
cancelAndOk,
|
||||
}
|
||||
|
||||
class ConfirmPopupColor {
|
||||
static Color titleColor(BuildContext context) {
|
||||
if (Theme.of(context).isLightMode) {
|
||||
return const Color(0xFF171717).withOpacity(0.8);
|
||||
}
|
||||
return const Color(0xFFffffff).withOpacity(0.8);
|
||||
}
|
||||
|
||||
static Color descriptionColor(BuildContext context) {
|
||||
if (Theme.of(context).isLightMode) {
|
||||
return const Color(0xFF171717).withOpacity(0.8);
|
||||
}
|
||||
return const Color(0xFFffffff).withOpacity(0.72);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfirmPopup extends StatefulWidget {
|
||||
const ConfirmPopup({
|
||||
super.key,
|
||||
this.style = ConfirmPopupStyle.cancelAndOk,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.onConfirm,
|
||||
this.confirmLabel,
|
||||
this.confirmButtonColor,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String description;
|
||||
final VoidCallback onConfirm;
|
||||
final Color? confirmButtonColor;
|
||||
final ConfirmPopupStyle style;
|
||||
|
||||
/// The label of the confirm button.
|
||||
///
|
||||
/// Defaults to 'Delete' for [ConfirmPopupStyle.cancelAndOk] style.
|
||||
/// Defaults to 'Ok' for [ConfirmPopupStyle.onlyOk] style.
|
||||
///
|
||||
final String? confirmLabel;
|
||||
|
||||
@override
|
||||
State<ConfirmDeletionPopup> createState() => _ConfirmDeletionPopupState();
|
||||
State<ConfirmPopup> createState() => _ConfirmPopupState();
|
||||
}
|
||||
|
||||
class _ConfirmDeletionPopupState extends State<ConfirmDeletionPopup> {
|
||||
class _ConfirmPopupState extends State<ConfirmPopup> {
|
||||
final focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
@@ -262,46 +336,74 @@ class _ConfirmDeletionPopupState extends State<ConfirmDeletionPopup> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText(
|
||||
widget.title,
|
||||
fontSize: 14.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const HSpace(6.0),
|
||||
FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: const FlowySvg(FlowySvgs.upgrade_close_s),
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const VSpace(8.0),
|
||||
FlowyText.regular(
|
||||
widget.description,
|
||||
fontSize: 12.0,
|
||||
color: Theme.of(context).hintColor,
|
||||
maxLines: 3,
|
||||
lineHeight: 1.4,
|
||||
),
|
||||
_buildTitle(),
|
||||
const VSpace(6.0),
|
||||
_buildDescription(),
|
||||
const VSpace(20.0),
|
||||
SpaceCancelOrConfirmButton(
|
||||
onCancel: () => Navigator.of(context).pop(),
|
||||
onConfirm: () {
|
||||
widget.onConfirm();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
confirmButtonName: LocaleKeys.space_delete.tr(),
|
||||
confirmButtonColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
_buildStyledButton(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTitle() {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText(
|
||||
widget.title,
|
||||
fontSize: 14.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: ConfirmPopupColor.titleColor(context),
|
||||
),
|
||||
),
|
||||
const HSpace(6.0),
|
||||
FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: const FlowySvg(FlowySvgs.upgrade_close_s),
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDescription() {
|
||||
return FlowyText.regular(
|
||||
widget.description,
|
||||
fontSize: 12.0,
|
||||
color: ConfirmPopupColor.descriptionColor(context),
|
||||
maxLines: 3,
|
||||
lineHeight: 1.4,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStyledButton(BuildContext context) {
|
||||
switch (widget.style) {
|
||||
case ConfirmPopupStyle.onlyOk:
|
||||
return SpaceOkButton(
|
||||
onConfirm: () {
|
||||
widget.onConfirm();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
confirmButtonName: widget.confirmLabel ?? LocaleKeys.button_ok.tr(),
|
||||
confirmButtonColor: widget.confirmButtonColor ??
|
||||
Theme.of(context).colorScheme.primary,
|
||||
);
|
||||
case ConfirmPopupStyle.cancelAndOk:
|
||||
return SpaceCancelOrConfirmButton(
|
||||
onCancel: () => Navigator.of(context).pop(),
|
||||
onConfirm: () {
|
||||
widget.onConfirm();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
confirmButtonName:
|
||||
widget.confirmLabel ?? LocaleKeys.space_delete.tr(),
|
||||
confirmButtonColor:
|
||||
widget.confirmButtonColor ?? Theme.of(context).colorScheme.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpacePopup extends StatelessWidget {
|
||||
|
||||
+93
-3
@@ -7,6 +7,7 @@ import 'package:appflowy/workspace/presentation/settings/shared/single_setting_a
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -24,7 +25,7 @@ class FixDataWidget extends StatelessWidget {
|
||||
.tr(),
|
||||
buttonLabel: LocaleKeys.settings_manageDataPage_data_fixButton.tr(),
|
||||
onPressed: () {
|
||||
FixDataManager.checkWorkspaceHealth(dryRun: true);
|
||||
WorkspaceDataManager.checkWorkspaceHealth(dryRun: true);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -32,7 +33,7 @@ class FixDataWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class FixDataManager {
|
||||
class WorkspaceDataManager {
|
||||
static Future<void> checkWorkspaceHealth({
|
||||
required bool dryRun,
|
||||
}) async {
|
||||
@@ -63,6 +64,9 @@ class FixDataManager {
|
||||
// check the health of the spaces
|
||||
await checkSpaceHealth(workspace: workspace, allViews: allViews);
|
||||
|
||||
// check the health of the views
|
||||
await checkViewHealth(workspace: workspace, allViews: allViews);
|
||||
|
||||
// add other checks here
|
||||
// ...
|
||||
} catch (e) {
|
||||
@@ -83,7 +87,6 @@ class FixDataManager {
|
||||
workspaceChildViews.map((e) => e.id).toSet();
|
||||
final spaces = allViews.where((e) => e.isSpace).toList();
|
||||
|
||||
//
|
||||
for (final space in spaces) {
|
||||
// the space is the top level view, so its parent view id should be the workspace id
|
||||
// and the workspace should have the space in its child views
|
||||
@@ -106,6 +109,93 @@ class FixDataManager {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<ViewPB>> checkViewHealth({
|
||||
ViewPB? workspace,
|
||||
List<ViewPB>? allViews,
|
||||
bool dryRun = true,
|
||||
}) async {
|
||||
// Views whose parent view does not have the view in its child views
|
||||
final List<ViewPB> unlistedChildViews = [];
|
||||
// Views whose parent is not in allViews
|
||||
final List<ViewPB> orphanViews = [];
|
||||
|
||||
try {
|
||||
if (workspace == null || allViews == null) {
|
||||
final currentWorkspace =
|
||||
await UserBackendService.getCurrentWorkspace().getOrThrow();
|
||||
// get all the views in the workspace
|
||||
final result = await ViewBackendService.getAllViews().getOrThrow();
|
||||
allViews = result.items;
|
||||
workspace = allViews.firstWhereOrNull(
|
||||
(e) => e.id == currentWorkspace.id,
|
||||
);
|
||||
}
|
||||
|
||||
for (final view in allViews) {
|
||||
if (view.parentViewId == '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
final parentView = allViews.firstWhereOrNull(
|
||||
(e) => e.id == view.parentViewId,
|
||||
);
|
||||
|
||||
if (parentView == null) {
|
||||
orphanViews.add(view);
|
||||
continue;
|
||||
}
|
||||
|
||||
final childViewsOfParent =
|
||||
await ViewBackendService.getChildViews(viewId: parentView.id)
|
||||
.getOrThrow();
|
||||
final result = childViewsOfParent.any((e) => e.id == view.id);
|
||||
if (!result) {
|
||||
unlistedChildViews.add(view);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error('Failed to check space health: $e');
|
||||
return [];
|
||||
}
|
||||
|
||||
for (final view in unlistedChildViews) {
|
||||
Log.info(
|
||||
'[workspace] found an issue: view is not in the parent view\'s child views, view: ${view.toProto3Json()}}',
|
||||
);
|
||||
}
|
||||
|
||||
for (final view in orphanViews) {
|
||||
Log.debug('[workspace] orphanViews: ${view.toProto3Json()}');
|
||||
}
|
||||
|
||||
if (!dryRun && unlistedChildViews.isNotEmpty) {
|
||||
Log.info(
|
||||
'[workspace] start to fix ${unlistedChildViews.length} unlistedChildViews ...',
|
||||
);
|
||||
for (final view in unlistedChildViews) {
|
||||
// move the view to the parent view if it is not in the parent view's child views
|
||||
Log.info(
|
||||
'[workspace] move view: $view to its parent view ${view.parentViewId}',
|
||||
);
|
||||
await ViewBackendService.moveViewV2(
|
||||
viewId: view.id,
|
||||
newParentId: view.parentViewId,
|
||||
prevViewId: null,
|
||||
);
|
||||
}
|
||||
|
||||
Log.info('[workspace] end to fix unlistedChildViews');
|
||||
}
|
||||
|
||||
if (unlistedChildViews.isEmpty && orphanViews.isEmpty) {
|
||||
Log.info('[workspace] all views are healthy');
|
||||
}
|
||||
|
||||
Log.info('[workspace] done checking view health');
|
||||
|
||||
return unlistedChildViews;
|
||||
}
|
||||
|
||||
static void dumpViews(String prefix, List<ViewPB> views) {
|
||||
for (int i = 0; i < views.length; i++) {
|
||||
final view = views[i];
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/download_model_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||
|
||||
class DownloadingIndicator extends StatelessWidget {
|
||||
const DownloadingIndicator({
|
||||
required this.llmModel,
|
||||
required this.onCancel,
|
||||
required this.onFinish,
|
||||
super.key,
|
||||
});
|
||||
final LLMModelPB llmModel;
|
||||
final VoidCallback onCancel;
|
||||
final VoidCallback onFinish;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
DownloadModelBloc(llmModel)..add(const DownloadModelEvent.started()),
|
||||
child: BlocListener<DownloadModelBloc, DownloadModelState>(
|
||||
listener: (context, state) {
|
||||
if (state.isFinish) {
|
||||
onFinish();
|
||||
}
|
||||
},
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// const DownloadingPrompt(),
|
||||
// const VSpace(12),
|
||||
DownloadingProgressBar(onCancel: onCancel),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadingProgressBar extends StatelessWidget {
|
||||
const DownloadingProgressBar({required this.onCancel, super.key});
|
||||
|
||||
final VoidCallback onCancel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DownloadModelBloc, DownloadModelState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FlowyText(
|
||||
"${LocaleKeys.settings_aiPage_keys_downloadingModel.tr()}: ${state.object}",
|
||||
fontSize: 11,
|
||||
),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: LinearPercentIndicator(
|
||||
lineHeight: 9.0,
|
||||
percent: state.percent,
|
||||
padding: EdgeInsets.zero,
|
||||
progressColor: AFThemeExtension.of(context).success,
|
||||
backgroundColor:
|
||||
AFThemeExtension.of(context).progressBarBGColor,
|
||||
barRadius: const Radius.circular(8),
|
||||
trailing: FlowyText(
|
||||
"${(state.percent * 100).toStringAsFixed(0)}%",
|
||||
fontSize: 11,
|
||||
color: AFThemeExtension.of(context).success,
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(12),
|
||||
FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: FlowyText(
|
||||
LocaleKeys.button_cancel.tr(),
|
||||
fontSize: 11,
|
||||
),
|
||||
onTap: onCancel,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/local_ai_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class InitLocalAIIndicator extends StatelessWidget {
|
||||
const InitLocalAIIndicator({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFEDF7ED),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(4),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
child: BlocBuilder<LocalAISettingBloc, LocalAISettingState>(
|
||||
builder: (context, state) {
|
||||
switch (state.runningState) {
|
||||
case RunningStatePB.Connecting:
|
||||
case RunningStatePB.Connected:
|
||||
return Row(
|
||||
children: [
|
||||
const HSpace(8),
|
||||
FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_localAILoading.tr(),
|
||||
fontSize: 11,
|
||||
color: const Color(0xFF1E4620),
|
||||
),
|
||||
],
|
||||
);
|
||||
case RunningStatePB.Running:
|
||||
return Row(
|
||||
children: [
|
||||
const HSpace(8),
|
||||
const FlowySvg(
|
||||
FlowySvgs.download_success_s,
|
||||
color: Color(0xFF2E7D32),
|
||||
),
|
||||
const HSpace(6),
|
||||
FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_localAILoaded.tr(),
|
||||
fontSize: 11,
|
||||
color: const Color(0xFF1E4620),
|
||||
),
|
||||
],
|
||||
);
|
||||
case RunningStatePB.Stopped:
|
||||
return Row(
|
||||
children: [
|
||||
const HSpace(8),
|
||||
FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_localAIStopped.tr(),
|
||||
fontSize: 11,
|
||||
color: const Color(0xFFC62828),
|
||||
),
|
||||
],
|
||||
);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/local_ai_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/downloading.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/init_local_ai.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/plugin_state.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_dropdown.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class LocalModelConfig extends StatelessWidget {
|
||||
const LocalModelConfig({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
if (state.aiSettings == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
if (state.aiSettings!.aiModel != AIModelPB.LocalAIModel) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
LocalAISettingBloc()..add(const LocalAISettingEvent.started()),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_aiPage_keys_llmModel.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
BlocBuilder<LocalAISettingBloc, LocalAISettingState>(
|
||||
builder: (context, state) {
|
||||
return state.fetchModelInfoState.when(
|
||||
loading: () =>
|
||||
const CircularProgressIndicator.adaptive(),
|
||||
finish: (err) {
|
||||
return (err == null)
|
||||
? const _SelectLocalModelDropdownMenu()
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const IntrinsicHeight(child: _LocalLLMInfoWidget()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SelectLocalModelDropdownMenu extends StatelessWidget {
|
||||
const _SelectLocalModelDropdownMenu();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LocalAISettingBloc, LocalAISettingState>(
|
||||
builder: (context, state) {
|
||||
return Flexible(
|
||||
child: SettingsDropdown<LLMModelPB>(
|
||||
key: const Key('_SelectLocalModelDropdownMenu'),
|
||||
onChanged: (model) => context.read<LocalAISettingBloc>().add(
|
||||
LocalAISettingEvent.selectLLMConfig(model),
|
||||
),
|
||||
selectedOption: state.selectedLLMModel!,
|
||||
options: state.models
|
||||
.map(
|
||||
(llm) => buildDropdownMenuEntry<LLMModelPB>(
|
||||
context,
|
||||
value: llm,
|
||||
label: llm.chatModel,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LocalLLMInfoWidget extends StatelessWidget {
|
||||
const _LocalLLMInfoWidget();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LocalAISettingBloc, LocalAISettingState>(
|
||||
builder: (context, state) {
|
||||
final error = errorFromState(state);
|
||||
if (error == null) {
|
||||
// If the error is null, handle selected llm model.
|
||||
if (state.localAIInfo != null) {
|
||||
final child = state.localAIInfo!.when(
|
||||
requestDownloadInfo: (
|
||||
LocalModelResourcePB llmResource,
|
||||
LLMModelPB llmModel,
|
||||
) {
|
||||
_showDownloadDialog(context, llmResource, llmModel);
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
showDownload: (
|
||||
LocalModelResourcePB llmResource,
|
||||
LLMModelPB llmModel,
|
||||
) =>
|
||||
_ShowDownloadIndicator(
|
||||
llmResource: llmResource,
|
||||
llmModel: llmModel,
|
||||
),
|
||||
startDownloading: (llmModel) {
|
||||
return DownloadingIndicator(
|
||||
key: UniqueKey(),
|
||||
llmModel: llmModel,
|
||||
onFinish: () => context
|
||||
.read<LocalAISettingBloc>()
|
||||
.add(const LocalAISettingEvent.finishDownload()),
|
||||
onCancel: () => context
|
||||
.read<LocalAISettingBloc>()
|
||||
.add(const LocalAISettingEvent.cancelDownload()),
|
||||
);
|
||||
},
|
||||
finishDownload: () => const InitLocalAIIndicator(),
|
||||
checkPluginState: () => const CheckPluginStateIndicator(),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 14),
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
} else {
|
||||
return FlowyText(
|
||||
error.msg,
|
||||
maxLines: 10,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showDownloadDialog(
|
||||
BuildContext context,
|
||||
LocalModelResourcePB llmResource,
|
||||
LLMModelPB llmModel,
|
||||
) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
useRootNavigator: false,
|
||||
builder: (dialogContext) {
|
||||
return _LLMModelDownloadDialog(
|
||||
llmResource: llmResource,
|
||||
onOkPressed: () {
|
||||
context.read<LocalAISettingBloc>().add(
|
||||
LocalAISettingEvent.startDownloadModel(
|
||||
llmModel,
|
||||
),
|
||||
);
|
||||
},
|
||||
onCancelPressed: () {
|
||||
context.read<LocalAISettingBloc>().add(
|
||||
const LocalAISettingEvent.cancelDownload(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
debugLabel: 'localModel.download',
|
||||
);
|
||||
}
|
||||
|
||||
FlowyError? errorFromState(LocalAISettingState state) {
|
||||
final err = state.fetchModelInfoState.when(
|
||||
loading: () => null,
|
||||
finish: (err) => err,
|
||||
);
|
||||
|
||||
if (err == null) {
|
||||
state.selectLLMState.when(
|
||||
loading: () => null,
|
||||
finish: (err) => err,
|
||||
);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
class _LLMModelDownloadDialog extends StatelessWidget {
|
||||
const _LLMModelDownloadDialog({
|
||||
required this.llmResource,
|
||||
required this.onOkPressed,
|
||||
required this.onCancelPressed,
|
||||
});
|
||||
final LocalModelResourcePB llmResource;
|
||||
final VoidCallback onOkPressed;
|
||||
final VoidCallback onCancelPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NavigatorOkCancelDialog(
|
||||
title: LocaleKeys.settings_aiPage_keys_downloadLLMPrompt.tr(
|
||||
args: [
|
||||
llmResource.pendingResources[0].name,
|
||||
],
|
||||
),
|
||||
message: llmResource.pendingResources[0].fileSize == 0
|
||||
? ""
|
||||
: LocaleKeys.settings_aiPage_keys_downloadLLMPromptDetail.tr(
|
||||
args: [
|
||||
llmResource.pendingResources[0].name,
|
||||
llmResource.pendingResources[0].fileSize.toString(),
|
||||
],
|
||||
),
|
||||
okTitle: LocaleKeys.button_confirm.tr(),
|
||||
cancelTitle: LocaleKeys.button_cancel.tr(),
|
||||
onOkPressed: onOkPressed,
|
||||
onCancelPressed: onCancelPressed,
|
||||
titleUpperCase: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShowDownloadIndicator extends StatelessWidget {
|
||||
const _ShowDownloadIndicator({
|
||||
required this.llmResource,
|
||||
required this.llmModel,
|
||||
});
|
||||
final LocalModelResourcePB llmResource;
|
||||
final LLMModelPB llmModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LocalAISettingBloc, LocalAISettingState>(
|
||||
builder: (context, state) {
|
||||
return Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
IntrinsicWidth(
|
||||
child: SizedBox(
|
||||
height: 30,
|
||||
child: FlowyButton(
|
||||
text: FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_downloadAIModelButton.tr(),
|
||||
fontSize: 14,
|
||||
color: const Color(0xFF005483),
|
||||
),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.local_model_download_s,
|
||||
color: Color(0xFF005483),
|
||||
),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
useRootNavigator: false,
|
||||
builder: (dialogContext) {
|
||||
return _LLMModelDownloadDialog(
|
||||
llmResource: llmResource,
|
||||
onOkPressed: () {
|
||||
context.read<LocalAISettingBloc>().add(
|
||||
LocalAISettingEvent.startDownloadModel(
|
||||
llmModel,
|
||||
),
|
||||
);
|
||||
},
|
||||
onCancelPressed: () {
|
||||
context.read<LocalAISettingBloc>().add(
|
||||
const LocalAISettingEvent.cancelDownload(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_dropdown.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class AIModelSelection extends StatelessWidget {
|
||||
const AIModelSelection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_aiPage_keys_llmModelType.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Flexible(
|
||||
child: SettingsDropdown<AIModelPB>(
|
||||
key: const Key('_AIModelSelection'),
|
||||
onChanged: (model) => context
|
||||
.read<SettingsAIBloc>()
|
||||
.add(SettingsAIEvent.selectModel(model)),
|
||||
selectedOption: state.userProfile.aiModel,
|
||||
options: _availableModels
|
||||
.map(
|
||||
(format) => buildDropdownMenuEntry<AIModelPB>(
|
||||
context,
|
||||
value: format,
|
||||
label: _titleForAIModel(format),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<AIModelPB> _availableModels = [
|
||||
AIModelPB.DefaultModel,
|
||||
AIModelPB.Claude3Opus,
|
||||
AIModelPB.Claude3Sonnet,
|
||||
AIModelPB.GPT35,
|
||||
AIModelPB.GPT4o,
|
||||
// AIModelPB.LocalAIModel,
|
||||
];
|
||||
|
||||
String _titleForAIModel(AIModelPB model) {
|
||||
switch (model) {
|
||||
case AIModelPB.DefaultModel:
|
||||
return "Default";
|
||||
case AIModelPB.Claude3Opus:
|
||||
return "Claude 3 Opus";
|
||||
case AIModelPB.Claude3Sonnet:
|
||||
return "Claude 3 Sonnet";
|
||||
case AIModelPB.GPT35:
|
||||
return "GPT-3.5";
|
||||
case AIModelPB.GPT4o:
|
||||
return "GPT-4o";
|
||||
case AIModelPB.LocalAIModel:
|
||||
return "Local";
|
||||
default:
|
||||
Log.error("Unknown AI model: $model, fallback to default");
|
||||
return "Default";
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/plugin_state_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class CheckPluginStateIndicator extends StatelessWidget {
|
||||
const CheckPluginStateIndicator({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
PluginStateBloc()..add(const PluginStateEvent.started()),
|
||||
child: BlocBuilder<PluginStateBloc, PluginStateState>(
|
||||
builder: (context, state) {
|
||||
return state.action.when(
|
||||
init: () => const _InitPlugin(),
|
||||
ready: () => const _ReadyToUse(),
|
||||
reloadRequired: () => const _ReloadButton(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InitPlugin extends StatelessWidget {
|
||||
const _InitPlugin();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox(
|
||||
height: 20,
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ReloadButton extends StatelessWidget {
|
||||
const _ReloadButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.download_warn_s,
|
||||
color: Color(0xFFC62828),
|
||||
),
|
||||
const HSpace(6),
|
||||
FlowyText(LocaleKeys.settings_aiPage_keys_failToLoadLocalAI.tr()),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
height: 30,
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text:
|
||||
FlowyText(LocaleKeys.settings_aiPage_keys_restartLocalAI.tr()),
|
||||
onTap: () {
|
||||
context.read<PluginStateBloc>().add(
|
||||
const PluginStateEvent.restartLocalAI(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ReadyToUse extends StatelessWidget {
|
||||
const _ReadyToUse();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFEDF7ED),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(4),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(8),
|
||||
const FlowySvg(
|
||||
FlowySvgs.download_success_s,
|
||||
color: Color(0xFF2E7D32),
|
||||
),
|
||||
const HSpace(6),
|
||||
FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_localAILoaded.tr(),
|
||||
fontSize: 11,
|
||||
color: const Color(0xFF1E4620),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/local_ai_config.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/model_selection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class AIFeatureOnlySupportedWhenUsingAppFlowyCloud extends StatelessWidget {
|
||||
const AIFeatureOnlySupportedWhenUsingAppFlowyCloud({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 30),
|
||||
child: FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_loginToEnableAIFeature.tr(),
|
||||
maxLines: null,
|
||||
fontSize: 16,
|
||||
lineHeight: 1.6,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsAIView extends StatelessWidget {
|
||||
const SettingsAIView({super.key, required this.userProfile});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<SettingsAIBloc>(
|
||||
create: (_) =>
|
||||
SettingsAIBloc(userProfile)..add(const SettingsAIEvent.started()),
|
||||
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
final children = <Widget>[
|
||||
const AIModelSelection(),
|
||||
];
|
||||
|
||||
if (state.aiSettings != null &&
|
||||
state.aiSettings!.aiModel == AIModelPB.LocalAIModel) {
|
||||
children.add(const LocalModelConfig());
|
||||
}
|
||||
|
||||
children.add(const _AISearchToggle(value: false));
|
||||
|
||||
return SettingsBody(
|
||||
title: LocaleKeys.settings_aiPage_title.tr(),
|
||||
description:
|
||||
LocaleKeys.settings_aiPage_keys_aiSettingsDescription.tr(),
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AISearchToggle extends StatelessWidget {
|
||||
const _AISearchToggle({required this.value});
|
||||
|
||||
final bool value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
LocaleKeys.settings_aiPage_keys_enableAISearchTitle.tr(),
|
||||
),
|
||||
const Spacer(),
|
||||
BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
if (state.aiSettings == null) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: SizedBox(
|
||||
height: 26,
|
||||
width: 26,
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Toggle(
|
||||
value: state.enableSearchIndexing,
|
||||
onChanged: (_) => context
|
||||
.read<SettingsAIBloc>()
|
||||
.add(const SettingsAIEvent.toggleAISearch()),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
-279
@@ -1,279 +0,0 @@
|
||||
import 'package:appflowy/workspace/application/settings/ai/setting_local_ai_bloc.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_dropdown.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class AIFeatureOnlySupportedWhenUsingAppFlowyCloud extends StatelessWidget {
|
||||
const AIFeatureOnlySupportedWhenUsingAppFlowyCloud({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 30),
|
||||
child: FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_loginToEnableAIFeature.tr(),
|
||||
maxLines: null,
|
||||
fontSize: 16,
|
||||
lineHeight: 1.6,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsAIView extends StatelessWidget {
|
||||
const SettingsAIView({super.key, required this.userProfile});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<SettingsAIBloc>(
|
||||
create: (_) =>
|
||||
SettingsAIBloc(userProfile)..add(const SettingsAIEvent.started()),
|
||||
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
return SettingsBody(
|
||||
title: LocaleKeys.settings_aiPage_title.tr(),
|
||||
description:
|
||||
LocaleKeys.settings_aiPage_keys_aiSettingsDescription.tr(),
|
||||
children: const [
|
||||
AIModelSelection(),
|
||||
_AISearchToggle(value: false),
|
||||
// Disable local AI configuration for now. It's not ready for production.
|
||||
LocalAIConfiguration(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AIModelSelection extends StatelessWidget {
|
||||
const AIModelSelection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_aiPage_keys_llmModel.tr(),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Flexible(
|
||||
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
return SettingsDropdown<AIModelPB>(
|
||||
key: const Key('AIModelDropdown'),
|
||||
onChanged: (model) => context
|
||||
.read<SettingsAIBloc>()
|
||||
.add(SettingsAIEvent.selectModel(model)),
|
||||
selectedOption: state.userProfile.aiModel,
|
||||
options: _availableModels
|
||||
.map(
|
||||
(format) => buildDropdownMenuEntry<AIModelPB>(
|
||||
context,
|
||||
value: format,
|
||||
label: _titleForAIModel(format),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<AIModelPB> _availableModels = [
|
||||
AIModelPB.DefaultModel,
|
||||
AIModelPB.Claude3Opus,
|
||||
AIModelPB.Claude3Sonnet,
|
||||
AIModelPB.GPT35,
|
||||
AIModelPB.GPT4o,
|
||||
];
|
||||
|
||||
String _titleForAIModel(AIModelPB model) {
|
||||
switch (model) {
|
||||
case AIModelPB.DefaultModel:
|
||||
return "Default";
|
||||
case AIModelPB.Claude3Opus:
|
||||
return "Claude 3 Opus";
|
||||
case AIModelPB.Claude3Sonnet:
|
||||
return "Claude 3 Sonnet";
|
||||
case AIModelPB.GPT35:
|
||||
return "GPT-3.5";
|
||||
case AIModelPB.GPT4o:
|
||||
return "GPT-4o";
|
||||
case AIModelPB.LocalAIModel:
|
||||
return "Local";
|
||||
default:
|
||||
Log.error("Unknown AI model: $model, fallback to default");
|
||||
return "Default";
|
||||
}
|
||||
}
|
||||
|
||||
class _AISearchToggle extends StatelessWidget {
|
||||
const _AISearchToggle({required this.value});
|
||||
|
||||
final bool value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
LocaleKeys.settings_aiPage_keys_enableAISearchTitle.tr(),
|
||||
),
|
||||
const Spacer(),
|
||||
BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
if (state.aiSettings == null) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: SizedBox(
|
||||
height: 26,
|
||||
width: 26,
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Toggle(
|
||||
value: state.enableSearchIndexing,
|
||||
onChanged: (_) => context
|
||||
.read<SettingsAIBloc>()
|
||||
.add(const SettingsAIEvent.toggleAISearch()),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LocalAIConfiguration extends StatelessWidget {
|
||||
const LocalAIConfiguration({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
SettingsAILocalBloc()..add(const SettingsAILocalEvent.started()),
|
||||
child: BlocBuilder<SettingsAILocalBloc, SettingsAILocalState>(
|
||||
builder: (context, state) {
|
||||
return state.loadingState.when(
|
||||
loading: () {
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
finish: () {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AIConfigurateTextField(
|
||||
title: 'chat bin path',
|
||||
hitText: '',
|
||||
errorText: state.chatBinPathError ?? '',
|
||||
value: state.aiSettings?.chatBinPath ?? '',
|
||||
onChanged: (value) {
|
||||
context.read<SettingsAILocalBloc>().add(
|
||||
SettingsAILocalEvent.updateChatBin(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
const VSpace(16),
|
||||
AIConfigurateTextField(
|
||||
title: 'chat model path',
|
||||
hitText: '',
|
||||
errorText: state.chatModelPathError ?? '',
|
||||
value: state.aiSettings?.chatModelPath ?? '',
|
||||
onChanged: (value) {
|
||||
context.read<SettingsAILocalBloc>().add(
|
||||
SettingsAILocalEvent.updateChatModelPath(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
const VSpace(16),
|
||||
Toggle(
|
||||
value: state.localAIEnabled,
|
||||
onChanged: (_) => context
|
||||
.read<SettingsAILocalBloc>()
|
||||
.add(const SettingsAILocalEvent.toggleLocalAI()),
|
||||
),
|
||||
const VSpace(16),
|
||||
FlowyButton(
|
||||
disable: !state.saveButtonEnabled,
|
||||
text: const FlowyText("save"),
|
||||
onTap: () {
|
||||
context.read<SettingsAILocalBloc>().add(
|
||||
const SettingsAILocalEvent.saveSetting(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AIConfigurateTextField extends StatelessWidget {
|
||||
const AIConfigurateTextField({
|
||||
required this.title,
|
||||
required this.hitText,
|
||||
required this.errorText,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String hitText;
|
||||
final String errorText;
|
||||
final String value;
|
||||
final void Function(String) onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FlowyText(
|
||||
title,
|
||||
),
|
||||
const VSpace(8),
|
||||
RoundedInputField(
|
||||
hintText: hitText,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
normalBorderColor: Theme.of(context).colorScheme.outline,
|
||||
errorBorderColor: Theme.of(context).colorScheme.error,
|
||||
cursorColor: Theme.of(context).colorScheme.primary,
|
||||
errorText: errorText,
|
||||
initialValue: value,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+15
-7
@@ -126,26 +126,34 @@ class SettingsManageDataView extends StatelessWidget {
|
||||
buttonLabel:
|
||||
LocaleKeys.settings_manageDataPage_cache_title.tr(),
|
||||
onPressed: () {
|
||||
SettingsAlertDialog(
|
||||
showCancelAndConfirmDialog(
|
||||
context: context,
|
||||
title: LocaleKeys
|
||||
.settings_manageDataPage_cache_dialog_title
|
||||
.tr(),
|
||||
subtitle: LocaleKeys
|
||||
description: LocaleKeys
|
||||
.settings_manageDataPage_cache_dialog_description
|
||||
.tr(),
|
||||
confirm: () async {
|
||||
confirmLabel: LocaleKeys.button_ok.tr(),
|
||||
onConfirm: () async {
|
||||
// clear all cache
|
||||
await getIt<FlowyCacheManager>().clearAllCache();
|
||||
|
||||
// check the workspace and space health
|
||||
await WorkspaceDataManager.checkViewHealth(
|
||||
dryRun: false,
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
showSnackBarMessage(
|
||||
showToastNotification(
|
||||
context,
|
||||
LocaleKeys
|
||||
message: LocaleKeys
|
||||
.settings_manageDataPage_cache_dialog_successHint
|
||||
.tr(),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
).show(context);
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
+5
-4
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
@@ -12,6 +10,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SettingsPlanComparisonDialog extends StatefulWidget {
|
||||
@@ -173,9 +172,10 @@ class _SettingsPlanComparisonDialogState
|
||||
description: LocaleKeys
|
||||
.settings_comparePlanDialog_freePlan_description
|
||||
.tr(),
|
||||
// TODO(Mathias): the price should be dynamic based on the country and currency
|
||||
price: LocaleKeys
|
||||
.settings_comparePlanDialog_freePlan_price
|
||||
.tr(),
|
||||
.tr(args: ['\$0']),
|
||||
priceInfo: LocaleKeys
|
||||
.settings_comparePlanDialog_freePlan_priceInfo
|
||||
.tr(),
|
||||
@@ -231,9 +231,10 @@ class _SettingsPlanComparisonDialogState
|
||||
description: LocaleKeys
|
||||
.settings_comparePlanDialog_proPlan_description
|
||||
.tr(),
|
||||
// TODO(Mathias): the price should be dynamic based on the country and currency
|
||||
price: LocaleKeys
|
||||
.settings_comparePlanDialog_proPlan_price
|
||||
.tr(),
|
||||
.tr(args: ['\$10 ']),
|
||||
priceInfo: LocaleKeys
|
||||
.settings_comparePlanDialog_proPlan_priceInfo
|
||||
.tr(),
|
||||
|
||||
+2
-3
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/util/int64_extension.dart';
|
||||
@@ -19,6 +17,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SettingsPlanView extends StatelessWidget {
|
||||
@@ -710,7 +709,7 @@ class _PlanProgressIndicator extends StatelessWidget {
|
||||
// children: [
|
||||
// FlowyText.semibold(
|
||||
// LocaleKeys.settings_planPage_planUsage_aiCredit_price
|
||||
// .tr(),
|
||||
// .tr(args: ['5\$]),
|
||||
// fontSize: 24,
|
||||
// ),
|
||||
// FlowyText.medium(
|
||||
|
||||
+32
-7
@@ -15,6 +15,7 @@ import 'package:appflowy/workspace/application/settings/date_time/date_format_ex
|
||||
import 'package:appflowy/workspace/application/settings/date_time/time_format_ext.dart';
|
||||
import 'package:appflowy/workspace/application/settings/workspace/workspace_settings_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart';
|
||||
@@ -31,6 +32,7 @@ import 'package:appflowy/workspace/presentation/settings/shared/settings_input_f
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_radio_select.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/single_setting_action.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
@@ -44,7 +46,6 @@ import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
@@ -1144,9 +1145,21 @@ class _DocumentCursorColorSetting extends StatelessWidget {
|
||||
return SettingListTile(
|
||||
label: label,
|
||||
resetButtonKey: const Key('DocumentCursorColorResetButton'),
|
||||
onResetRequested: () => context
|
||||
..read<AppearanceSettingsCubit>().resetDocumentCursorColor()
|
||||
..read<DocumentAppearanceCubit>().syncCursorColor(null),
|
||||
onResetRequested: () {
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
title:
|
||||
LocaleKeys.settings_workspacePage_resetCursorColor_title.tr(),
|
||||
description: LocaleKeys
|
||||
.settings_workspacePage_resetCursorColor_description
|
||||
.tr(),
|
||||
style: ConfirmPopupStyle.cancelAndOk,
|
||||
confirmLabel: LocaleKeys.settings_common_reset.tr(),
|
||||
onConfirm: () => context
|
||||
..read<AppearanceSettingsCubit>().resetDocumentCursorColor()
|
||||
..read<DocumentAppearanceCubit>().syncCursorColor(null),
|
||||
);
|
||||
},
|
||||
trailing: [
|
||||
DocumentColorSettingButton(
|
||||
key: const Key('DocumentCursorColorSettingButton'),
|
||||
@@ -1202,9 +1215,21 @@ class _DocumentSelectionColorSetting extends StatelessWidget {
|
||||
return SettingListTile(
|
||||
label: label,
|
||||
resetButtonKey: const Key('DocumentSelectionColorResetButton'),
|
||||
onResetRequested: () => context
|
||||
..read<AppearanceSettingsCubit>().resetDocumentSelectionColor()
|
||||
..read<DocumentAppearanceCubit>().syncSelectionColor(null),
|
||||
onResetRequested: () {
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
title: LocaleKeys.settings_workspacePage_resetSelectionColor_title
|
||||
.tr(),
|
||||
description: LocaleKeys
|
||||
.settings_workspacePage_resetSelectionColor_description
|
||||
.tr(),
|
||||
style: ConfirmPopupStyle.cancelAndOk,
|
||||
confirmLabel: LocaleKeys.settings_common_reset.tr(),
|
||||
onConfirm: () => context
|
||||
..read<AppearanceSettingsCubit>().resetDocumentSelectionColor()
|
||||
..read<DocumentAppearanceCubit>().syncSelectionColor(null),
|
||||
);
|
||||
},
|
||||
trailing: [
|
||||
DocumentColorSettingButton(
|
||||
currentColor: state.selectionColor ??
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_ai_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_billing_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_manage_data_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_plan_view.dart';
|
||||
|
||||
+2
-1
@@ -13,6 +13,7 @@ DropdownMenuEntry<T> buildDropdownMenuEntry<T>(
|
||||
Widget? leadingWidget,
|
||||
Widget? trailingWidget,
|
||||
String? fontFamily,
|
||||
EdgeInsets padding = const EdgeInsets.symmetric(vertical: 4),
|
||||
}) {
|
||||
final fontFamilyUsed = fontFamily != null
|
||||
? getGoogleFontSafely(fontFamily).fontFamily ?? defaultFontFamily
|
||||
@@ -32,7 +33,7 @@ DropdownMenuEntry<T> buildDropdownMenuEntry<T>(
|
||||
label: label,
|
||||
leadingIcon: leadingWidget,
|
||||
labelWidget: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
padding: padding,
|
||||
child: FlowyText.regular(
|
||||
label,
|
||||
fontSize: 14,
|
||||
|
||||
+204
-18
@@ -1,10 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/util/color_to_hex_string.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/utils/hex_opacity_string_extension.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flex_color_picker/flex_color_picker.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||
|
||||
@@ -90,24 +94,11 @@ class DocumentColorSettingDialogState
|
||||
late TextEditingController hexController;
|
||||
late TextEditingController opacityController;
|
||||
|
||||
void updateSelectedColor() {
|
||||
if (widget.formKey.currentState!.validate()) {
|
||||
setState(() {
|
||||
final colorValue = int.tryParse(
|
||||
hexController.text.combineHexWithOpacity(opacityController.text),
|
||||
);
|
||||
// colorValue has been validated in the _ColorSettingTextField for hex value and it won't be null as this point
|
||||
selectedColorOnDialog = Color(colorValue!);
|
||||
widget.onChanged(selectedColorOnDialog!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedColorOnDialog = widget.currentColor;
|
||||
currentColorHexString = widget.currentColor.toHexString();
|
||||
currentColorHexString = ColorExtension(widget.currentColor).toHexString();
|
||||
hexController = TextEditingController(
|
||||
text: currentColorHexString.extractHex(),
|
||||
);
|
||||
@@ -145,17 +136,31 @@ class DocumentColorSettingDialogState
|
||||
controller: hexController,
|
||||
labelText: LocaleKeys.editor_hexValue.tr(),
|
||||
hintText: '6fc9e7',
|
||||
onChanged: (_) => updateSelectedColor(),
|
||||
onFieldSubmitted: (_) => updateSelectedColor(),
|
||||
onChanged: (_) => _updateSelectedColor(),
|
||||
onFieldSubmitted: (_) => _updateSelectedColor(),
|
||||
validator: (v) => validateHexValue(v, opacityController.text),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: FlowyIconButton(
|
||||
onPressed: () => _showColorPickerDialog(
|
||||
context: context,
|
||||
currentColor: widget.currentColor,
|
||||
updateColor: _updateColor,
|
||||
),
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.m_aa_color_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const VSpace(8),
|
||||
_ColorSettingTextField(
|
||||
controller: opacityController,
|
||||
labelText: LocaleKeys.editor_opacity.tr(),
|
||||
hintText: '50',
|
||||
onChanged: (_) => updateSelectedColor(),
|
||||
onFieldSubmitted: (_) => updateSelectedColor(),
|
||||
onChanged: (_) => _updateSelectedColor(),
|
||||
onFieldSubmitted: (_) => _updateSelectedColor(),
|
||||
validator: (value) => validateOpacityValue(value),
|
||||
),
|
||||
],
|
||||
@@ -164,6 +169,28 @@ class DocumentColorSettingDialogState
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _updateSelectedColor() {
|
||||
if (widget.formKey.currentState!.validate()) {
|
||||
setState(() {
|
||||
final colorValue = int.tryParse(
|
||||
hexController.text.combineHexWithOpacity(opacityController.text),
|
||||
);
|
||||
// colorValue has been validated in the _ColorSettingTextField for hex value and it won't be null as this point
|
||||
selectedColorOnDialog = Color(colorValue!);
|
||||
widget.onChanged(selectedColorOnDialog!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _updateColor(Color color) {
|
||||
setState(() {
|
||||
hexController.text = ColorExtension(color).toHexString().extractHex();
|
||||
opacityController.text =
|
||||
ColorExtension(color).toHexString().extractOpacity();
|
||||
});
|
||||
_updateSelectedColor();
|
||||
}
|
||||
}
|
||||
|
||||
class _ColorSettingTextField extends StatelessWidget {
|
||||
@@ -172,6 +199,7 @@ class _ColorSettingTextField extends StatelessWidget {
|
||||
required this.labelText,
|
||||
required this.hintText,
|
||||
required this.onFieldSubmitted,
|
||||
this.suffixIcon,
|
||||
this.onChanged,
|
||||
this.validator,
|
||||
});
|
||||
@@ -180,6 +208,7 @@ class _ColorSettingTextField extends StatelessWidget {
|
||||
final String labelText;
|
||||
final String hintText;
|
||||
final void Function(String) onFieldSubmitted;
|
||||
final Widget? suffixIcon;
|
||||
final void Function(String)? onChanged;
|
||||
final String? Function(String?)? validator;
|
||||
|
||||
@@ -191,6 +220,7 @@ class _ColorSettingTextField extends StatelessWidget {
|
||||
decoration: InputDecoration(
|
||||
labelText: labelText,
|
||||
hintText: hintText,
|
||||
suffixIcon: suffixIcon,
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: style.colorScheme.outline),
|
||||
),
|
||||
@@ -241,3 +271,159 @@ String? validateOpacityValue(String? value) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const _kColorCircleWidth = 32.0;
|
||||
const _kColorCircleHeight = 32.0;
|
||||
const _kColorCircleRadius = 20.0;
|
||||
const _kColorOpacityThumbRadius = 23.0;
|
||||
const _kDialogButtonPaddingHorizontal = 24.0;
|
||||
const _kDialogButtonPaddingVertical = 12.0;
|
||||
const _kColorsColumnSpacing = 12.0;
|
||||
|
||||
class _ColorPicker extends StatelessWidget {
|
||||
const _ColorPicker({
|
||||
required this.selectedColor,
|
||||
required this.onColorChanged,
|
||||
});
|
||||
|
||||
final Color selectedColor;
|
||||
final void Function(Color) onColorChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return ColorPicker(
|
||||
width: _kColorCircleWidth,
|
||||
height: _kColorCircleHeight,
|
||||
borderRadius: _kColorCircleRadius,
|
||||
enableOpacity: true,
|
||||
opacityThumbRadius: _kColorOpacityThumbRadius,
|
||||
columnSpacing: _kColorsColumnSpacing,
|
||||
enableTooltips: false,
|
||||
hasBorder: true,
|
||||
borderColor: theme.colorScheme.outline,
|
||||
pickersEnabled: const {
|
||||
ColorPickerType.both: false,
|
||||
ColorPickerType.primary: true,
|
||||
ColorPickerType.accent: true,
|
||||
ColorPickerType.wheel: true,
|
||||
},
|
||||
subheading: Text(
|
||||
LocaleKeys.settings_appearance_documentSettings_colorShade.tr(),
|
||||
style: theme.textTheme.labelLarge,
|
||||
),
|
||||
opacitySubheading: Text(
|
||||
LocaleKeys.settings_appearance_documentSettings_opacity.tr(),
|
||||
style: theme.textTheme.labelLarge,
|
||||
),
|
||||
onColorChanged: onColorChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ColorPickerActions extends StatelessWidget {
|
||||
const _ColorPickerActions({
|
||||
required this.onReset,
|
||||
required this.onUpdate,
|
||||
});
|
||||
|
||||
final VoidCallback onReset;
|
||||
final VoidCallback onUpdate;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 24,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.button_cancel.tr(),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: _kDialogButtonPaddingHorizontal,
|
||||
vertical: _kDialogButtonPaddingVertical,
|
||||
),
|
||||
fontColor: AFThemeExtension.of(context).textColor,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
radius: Corners.s12Border,
|
||||
onPressed: onReset,
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.button_done.tr(),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: _kDialogButtonPaddingHorizontal,
|
||||
vertical: _kDialogButtonPaddingVertical,
|
||||
),
|
||||
radius: Corners.s12Border,
|
||||
fontHoverColor: Colors.white,
|
||||
fillColor: Theme.of(context).colorScheme.primary,
|
||||
hoverColor: const Color(0xFF005483),
|
||||
onPressed: onUpdate,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showColorPickerDialog({
|
||||
required BuildContext context,
|
||||
String? title,
|
||||
required Color currentColor,
|
||||
required void Function(Color) updateColor,
|
||||
}) {
|
||||
Color selectedColor = currentColor;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: const Color.fromARGB(128, 0, 0, 0),
|
||||
builder: (context) => FlowyDialog(
|
||||
expandHeight: false,
|
||||
title: Row(
|
||||
children: [
|
||||
const FlowySvg(FlowySvgs.m_aa_color_s),
|
||||
const HSpace(12),
|
||||
FlowyText(
|
||||
title ??
|
||||
LocaleKeys.settings_appearance_documentSettings_pickColor.tr(),
|
||||
fontSize: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_ColorPicker(
|
||||
selectedColor: selectedColor,
|
||||
onColorChanged: (color) => selectedColor = color,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
const HSpace(8),
|
||||
_ColorPickerActions(
|
||||
onReset: () {
|
||||
updateColor(currentColor);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onUpdate: () {
|
||||
updateColor(selectedColor);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/tasks/app_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
@@ -9,7 +11,6 @@ import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
|
||||
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||
@@ -184,6 +185,7 @@ class NavigatorOkCancelDialog extends StatelessWidget {
|
||||
this.title,
|
||||
this.message,
|
||||
this.maxWidth,
|
||||
this.titleUpperCase = true,
|
||||
});
|
||||
|
||||
final VoidCallback? onOkPressed;
|
||||
@@ -193,6 +195,7 @@ class NavigatorOkCancelDialog extends StatelessWidget {
|
||||
final String? title;
|
||||
final String? message;
|
||||
final double? maxWidth;
|
||||
final bool titleUpperCase;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -204,7 +207,7 @@ class NavigatorOkCancelDialog extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
if (title != null) ...[
|
||||
FlowyText.medium(
|
||||
title!.toUpperCase(),
|
||||
titleUpperCase ? title!.toUpperCase() : title!,
|
||||
fontSize: FontSizes.s16,
|
||||
maxLines: 3,
|
||||
),
|
||||
@@ -289,10 +292,11 @@ void showToastNotification(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
String? description,
|
||||
ToastificationType type = ToastificationType.success,
|
||||
}) {
|
||||
toastification.show(
|
||||
context: context,
|
||||
type: ToastificationType.success,
|
||||
type: type,
|
||||
style: ToastificationStyle.flat,
|
||||
title: FlowyText(message),
|
||||
description: description != null
|
||||
@@ -329,7 +333,7 @@ Future<void> showConfirmDeletionDialog({
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 440,
|
||||
child: ConfirmDeletionPopup(
|
||||
child: ConfirmPopup(
|
||||
title: title,
|
||||
description: description,
|
||||
onConfirm: onConfirm,
|
||||
@@ -339,3 +343,62 @@ Future<void> showConfirmDeletionDialog({
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showConfirmDialog({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
required String description,
|
||||
VoidCallback? onConfirm,
|
||||
String? confirmLabel,
|
||||
ConfirmPopupStyle style = ConfirmPopupStyle.onlyOk,
|
||||
}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 440,
|
||||
child: ConfirmPopup(
|
||||
title: title,
|
||||
description: description,
|
||||
onConfirm: () => onConfirm?.call(),
|
||||
confirmLabel: confirmLabel,
|
||||
style: style,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showCancelAndConfirmDialog({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
required String description,
|
||||
VoidCallback? onConfirm,
|
||||
String? confirmLabel,
|
||||
}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 440,
|
||||
child: ConfirmPopup(
|
||||
title: title,
|
||||
description: description,
|
||||
onConfirm: () => onConfirm?.call(),
|
||||
confirmLabel: confirmLabel,
|
||||
confirmButtonColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
+21
-3
@@ -1,9 +1,12 @@
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/startup/tasks/rust_sdk.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
@@ -203,9 +206,24 @@ class FlowyVersionDescription extends CustomActionCell {
|
||||
thickness: 1.0,
|
||||
),
|
||||
const VSpace(6),
|
||||
FlowyText(
|
||||
"$appName $version",
|
||||
color: Theme.of(context).hintColor,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onDoubleTap: () {
|
||||
if (Env.internalBuild != '1') {
|
||||
return;
|
||||
}
|
||||
enableDocumentInternalLog = !enableDocumentInternalLog;
|
||||
showToastNotification(
|
||||
context,
|
||||
message: enableDocumentInternalLog
|
||||
? 'Enabled Internal Log'
|
||||
: 'Disabled Internal Log',
|
||||
);
|
||||
},
|
||||
child: FlowyText(
|
||||
'$appName $version',
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
export 'package:async/async.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'dart:ffi';
|
||||
import 'ffi.dart' as ffi;
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'dart:isolate';
|
||||
import 'dart:io';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
import 'ffi.dart' as ffi;
|
||||
|
||||
export 'package:async/async.dart';
|
||||
|
||||
enum ExceptionType {
|
||||
AppearanceSettingsIsEmpty,
|
||||
}
|
||||
@@ -72,7 +75,8 @@ class RustLogStreamReceiver {
|
||||
lineLength: 120, // width of the output
|
||||
colors: false, // Colorful log messages
|
||||
printEmojis: false, // Print an emoji for each log message
|
||||
printTime: false, // Should each log print contain a timestamp
|
||||
dateTimeFormat:
|
||||
DateTimeFormat.none, // Should each log print contain a timestamp
|
||||
),
|
||||
level: kDebugMode ? Level.trace : Level.info,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// ignore: import_of_legacy_library_into_null_safe
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart' as ffi;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
@@ -14,13 +15,15 @@ class Log {
|
||||
Log() {
|
||||
_logger = Logger(
|
||||
printer: PrettyPrinter(
|
||||
methodCount: 2, // number of method calls to be displayed
|
||||
errorMethodCount: 8, // number of method calls if stacktrace is provided
|
||||
lineLength: 120, // width of the output
|
||||
colors: true, // Colorful log messages
|
||||
printEmojis: true, // Print an emoji for each log message
|
||||
printTime: false, // Should each log print contain a timestamp
|
||||
),
|
||||
methodCount: 2, // number of method calls to be displayed
|
||||
errorMethodCount:
|
||||
8, // number of method calls if stacktrace is provided
|
||||
lineLength: 120, // width of the output
|
||||
colors: true, // Colorful log messages
|
||||
printEmojis: true, // Print an emoji for each log message
|
||||
dateTimeFormat:
|
||||
DateTimeFormat.none // Should each log print contain a timestamp
|
||||
),
|
||||
level: kDebugMode ? Level.trace : Level.info,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ dependencies:
|
||||
isolates: ^3.0.3+8
|
||||
protobuf: ^3.1.0
|
||||
freezed_annotation:
|
||||
logger: ^2.0.0
|
||||
logger: ^2.4.0
|
||||
plugin_platform_interface: ^2.1.3
|
||||
json_annotation: ^4.7.0
|
||||
appflowy_result:
|
||||
|
||||
@@ -53,11 +53,11 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: e8ee051
|
||||
resolved-ref: e8ee051719eded6621ccdc2722f696411c020209
|
||||
ref: e0d673a
|
||||
resolved-ref: e0d673afbbbcaf9df0276f7e0b6405d8f6e98112
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "3.0.0"
|
||||
version: "3.1.0"
|
||||
appflowy_editor_plugins:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -121,6 +121,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
barcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: barcode
|
||||
sha256: ab180ce22c6555d77d45f0178a523669db67f95856e3378259ef2ffeb43e6003
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.8"
|
||||
bidi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bidi
|
||||
sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.10"
|
||||
bitsdojo_window:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -418,6 +434,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
desktop_drop:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: desktop_drop
|
||||
sha256: d55a010fe46c8e8fcff4ea4b451a9ff84a162217bdb3b2a0aa1479776205e15d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.4"
|
||||
device_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -586,6 +610,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
flex_color_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flex_color_picker
|
||||
sha256: "809af4ec82ede3b140ed0219b97d548de99e47aa4b99b14a10f705a2dbbcba5e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.5.1"
|
||||
flex_seed_scheme:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flex_seed_scheme
|
||||
sha256: "6c595e545b0678e1fe17e8eec3d1fbca7237482da194fadc20ad8607dc7a7f3d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
flowy_infra:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -965,6 +1005,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
image_gallery_saver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1203,13 +1251,13 @@ packages:
|
||||
source: hosted
|
||||
version: "0.1.5"
|
||||
logger:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logger
|
||||
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac"
|
||||
sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2+1"
|
||||
version: "2.4.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1219,7 +1267,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
markdown:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: markdown
|
||||
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
|
||||
@@ -1434,6 +1482,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0+3"
|
||||
pdf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pdf
|
||||
sha256: "81d5522bddc1ef5c28e8f0ee40b71708761753c163e0c93a40df56fd515ea0f0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.0"
|
||||
pdf_widget_wrapper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pdf_widget_wrapper
|
||||
sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
percent_indicator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1554,6 +1618,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
printing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: printing
|
||||
sha256: cc4b256a5a89d5345488e3318897b595867f5181b8c5ed6fc63bfa5f2044aec3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.13.1"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1594,6 +1666,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.3"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
realtime_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 0.6.3
|
||||
version: 0.6.4
|
||||
|
||||
environment:
|
||||
flutter: ">=3.22.0"
|
||||
@@ -66,6 +66,7 @@ dependencies:
|
||||
styled_widget: ^0.4.1
|
||||
expandable: ^5.0.1
|
||||
flutter_colorpicker: ^1.0.3
|
||||
flex_color_picker: ^3.5.1
|
||||
highlight: ^0.7.0
|
||||
package_info_plus: ^6.0.0
|
||||
url_launcher: ^6.1.11
|
||||
@@ -141,6 +142,9 @@ 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
|
||||
|
||||
# Window Manager for MacOS and Linux
|
||||
window_manager: ^0.3.9
|
||||
@@ -187,7 +191,7 @@ dependency_overrides:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: "e8ee051"
|
||||
ref: "e0d673a"
|
||||
|
||||
appflowy_editor_plugins:
|
||||
git:
|
||||
|
||||
+338
-107
File diff suppressed because it is too large.
Load diff
@@ -29,7 +29,8 @@ tokio = "1.34.0"
|
||||
tokio-stream = "0.1.14"
|
||||
async-trait = "0.1.74"
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
yrs = "0.18.8"
|
||||
zip = "2.1.3"
|
||||
yrs = "0.19.1"
|
||||
# Please use the following script to update collab.
|
||||
# Working directory: frontend
|
||||
#
|
||||
@@ -52,7 +53,7 @@ collab-user = { version = "0.2" }
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "9884d93aa2805013f36a79c1757174a0b5063065" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "eebdbcad79a35b07305affdd36f16d9ce95c5a18" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
@@ -115,17 +116,17 @@ custom-protocol = ["tauri/custom-protocol"]
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
|
||||
# Working directory: frontend
|
||||
# To update the commit ID, run:
|
||||
# scripts/tool/update_local_ai_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "0820a0d23f7b813dee505e7e29e88a8561699fe8" }
|
||||
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "0820a0d23f7b813dee505e7e29e88a8561699fe8" }
|
||||
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "346020270a0a3d3c82a60b545cd3f52144d56beb" }
|
||||
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "346020270a0a3d3c82a60b545cd3f52144d56beb" }
|
||||
@@ -24,7 +24,7 @@
|
||||
"coverage": "pnpm run test:unit && pnpm run test:components"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appflowyinc/client-api-wasm": "0.1.1",
|
||||
"@appflowyinc/client-api-wasm": "0.1.2",
|
||||
"@atlaskit/primitives": "^5.5.3",
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
|
||||
Generated
+8
-8
@@ -1,13 +1,9 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
'@appflowyinc/client-api-wasm':
|
||||
specifier: 0.1.1
|
||||
version: 0.1.1
|
||||
specifier: 0.1.2
|
||||
version: 0.1.2
|
||||
'@atlaskit/primitives':
|
||||
specifier: ^5.5.3
|
||||
version: 5.7.0(@types/react@18.2.66)(react@18.2.0)
|
||||
@@ -451,8 +447,8 @@ packages:
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
/@appflowyinc/client-api-wasm@0.1.1:
|
||||
resolution: {integrity: sha512-7+/TCmzMi9KrxX3HFLJv9R6ON2AO5xQavV547ii7RZM8+5bZJakuf6+pnyCzOquQX07q3ZYwJCa3MIgDvficaA==}
|
||||
/@appflowyinc/client-api-wasm@0.1.2:
|
||||
resolution: {integrity: sha512-+v0hs7/7BVKtgev/Bcbr0u2HLDhUuw4ZvZTaMddI+06HK8vt5S52dMaZKUcMvh1eUjVX8hjC6Mfe0X/yHqvFgA==}
|
||||
dev: false
|
||||
|
||||
/@atlaskit/analytics-next-stable-react-context@1.0.1(react@18.2.0):
|
||||
@@ -11666,3 +11662,7 @@ packages:
|
||||
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: true
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
+277
-94
@@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -183,7 +183,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -197,22 +197,26 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-local-ai"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=0820a0d23f7b813dee505e7e29e88a8561699fe8#0820a0d23f7b813dee505e7e29e88a8561699fe8"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=346020270a0a3d3c82a60b545cd3f52144d56beb#346020270a0a3d3c82a60b545cd3f52144d56beb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"appflowy-plugin",
|
||||
"bytes",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"zip 2.1.3",
|
||||
"zip-extensions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "appflowy-plugin"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=0820a0d23f7b813dee505e7e29e88a8561699fe8#0820a0d23f7b813dee505e7e29e88a8561699fe8"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=346020270a0a3d3c82a60b545cd3f52144d56beb#346020270a0a3d3c82a60b545cd3f52144d56beb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
@@ -226,6 +230,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -254,6 +259,15 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.3.2"
|
||||
@@ -388,6 +402,12 @@ version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
@@ -453,9 +473,9 @@ checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "bitpacking"
|
||||
version = "0.8.4"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8c7d2ac73c167c06af4a5f37e6e59d84148d57ccbe4480b76f0273eefea82d7"
|
||||
checksum = "4c1d3e2bfd8d06048a179f7b17afc3188effa10385e7b00dc65af6aae732ea92"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
@@ -544,9 +564,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.15.4"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "bytecheck"
|
||||
@@ -780,7 +800,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@@ -801,6 +821,7 @@ dependencies = [
|
||||
"getrandom 0.2.12",
|
||||
"gotrue",
|
||||
"infra",
|
||||
"lazy_static",
|
||||
"mime",
|
||||
"parking_lot 0.12.1",
|
||||
"percent-encoding",
|
||||
@@ -829,7 +850,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@@ -841,7 +862,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@@ -924,7 +945,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -948,7 +969,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -978,7 +999,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@@ -998,7 +1019,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -1013,7 +1034,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1051,7 +1072,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -1090,7 +1111,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1115,7 +1136,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1132,7 +1153,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@@ -1183,6 +1204,12 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
@@ -1280,10 +1307,25 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.0"
|
||||
name = "crc"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@@ -1476,7 +1518,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -1503,6 +1545,12 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d"
|
||||
|
||||
[[package]]
|
||||
name = "delegate-display"
|
||||
version = "2.1.1"
|
||||
@@ -1547,6 +1595,17 @@ dependencies = [
|
||||
"syn 2.0.55",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
@@ -1671,6 +1730,17 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
@@ -1898,9 +1968,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.28"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
|
||||
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
@@ -1923,6 +1993,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"appflowy-local-ai",
|
||||
"appflowy-plugin",
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"dashmap",
|
||||
"flowy-chat-pub",
|
||||
@@ -1932,19 +2003,26 @@ dependencies = [
|
||||
"flowy-notification",
|
||||
"flowy-sqlite",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"lib-dispatch",
|
||||
"lib-infra",
|
||||
"log",
|
||||
"md5",
|
||||
"parking_lot 0.12.1",
|
||||
"protobuf",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"strum_macros 0.21.1",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"validator",
|
||||
"zip 2.1.3",
|
||||
"zip-extensions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2246,12 +2324,14 @@ dependencies = [
|
||||
"flowy-notification",
|
||||
"flowy-search-pub",
|
||||
"flowy-sqlite",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"lib-dispatch",
|
||||
"lib-infra",
|
||||
"nanoid",
|
||||
"parking_lot 0.12.1",
|
||||
"protobuf",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum_macros 0.21.1",
|
||||
@@ -2272,6 +2352,7 @@ dependencies = [
|
||||
"collab-entity",
|
||||
"collab-folder",
|
||||
"lib-infra",
|
||||
"serde",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -2582,12 +2663,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fs4"
|
||||
version = "0.6.6"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47"
|
||||
checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3014,7 +3095,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@@ -3031,7 +3112,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -3468,7 +3549,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -3541,9 +3622,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
@@ -3732,7 +3813,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"validator",
|
||||
"walkdir",
|
||||
"zip",
|
||||
"zip 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3843,6 +3924,12 @@ dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lockfree-object-pool"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
@@ -3867,9 +3954,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.11.1"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21"
|
||||
checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
@@ -3880,6 +3967,16 @@ version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5"
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rs"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
@@ -3989,15 +4086,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.7.1"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
|
||||
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -4228,6 +4325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4440,9 +4538,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "ownedbytes"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e8a72b918ae8198abb3a18c190288123e1d442b6b9a7d709305fd194688b4b7"
|
||||
checksum = "c3a059efb063b8f425b948e042e6b9bd85edfe60e913630ed727b23e2dfcc558"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
@@ -4978,7 +5076,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"heck 0.4.1",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.10.5",
|
||||
"log",
|
||||
"multimap",
|
||||
"once_cell",
|
||||
@@ -4999,7 +5097,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.10.5",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
@@ -5235,6 +5333,16 @@ dependencies = [
|
||||
"getrandom 0.2.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_distr"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
@@ -5977,7 +6085,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eebdbcad79a35b07305affdd36f16d9ce95c5a18#eebdbcad79a35b07305affdd36f16d9ce95c5a18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -6355,14 +6463,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tantivy"
|
||||
version = "0.21.1"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6083cd777fa94271b8ce0fe4533772cb8110c3044bab048d20f70108329a1f2"
|
||||
checksum = "f8d0582f186c0a6d55655d24543f15e43607299425c5ad8352c242b914b31856"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
"base64 0.21.7",
|
||||
"base64 0.22.1",
|
||||
"bitpacking",
|
||||
"byteorder",
|
||||
"census",
|
||||
@@ -6370,16 +6477,16 @@ dependencies = [
|
||||
"crossbeam-channel",
|
||||
"downcast-rs",
|
||||
"fastdivide",
|
||||
"fnv",
|
||||
"fs4",
|
||||
"htmlescape",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.12.1",
|
||||
"levenshtein_automata",
|
||||
"log",
|
||||
"lru",
|
||||
"lz4_flex",
|
||||
"measure_time",
|
||||
"memmap2",
|
||||
"murmurhash32",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"oneshot",
|
||||
@@ -6407,22 +6514,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tantivy-bitpacker"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecb164321482301f514dd582264fa67f70da2d7eb01872ccd71e35e0d96655a"
|
||||
checksum = "284899c2325d6832203ac6ff5891b297fc5239c3dc754c5bc1977855b23c10df"
|
||||
dependencies = [
|
||||
"bitpacking",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tantivy-columnar"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d85f8019af9a78b3118c11298b36ffd21c2314bd76bbcd9d12e00124cbb7e70"
|
||||
checksum = "12722224ffbe346c7fec3275c699e508fd0d4710e629e933d5736ec524a1f44e"
|
||||
dependencies = [
|
||||
"downcast-rs",
|
||||
"fastdivide",
|
||||
"fnv",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.12.1",
|
||||
"serde",
|
||||
"tantivy-bitpacker",
|
||||
"tantivy-common",
|
||||
@@ -6432,9 +6539,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tantivy-common"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af4a3a975e604a2aba6b1106a04505e1e7a025e6def477fab6e410b4126471e1"
|
||||
checksum = "8019e3cabcfd20a1380b491e13ff42f57bb38bf97c3d5fa5c07e50816e0621f4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"byteorder",
|
||||
@@ -6445,50 +6552,52 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tantivy-fst"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc3c506b1a8443a3a65352df6382a1fb6a7afe1a02e871cee0d25e2c3d5f3944"
|
||||
checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"regex-syntax 0.6.29",
|
||||
"regex-syntax 0.8.2",
|
||||
"utf8-ranges",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tantivy-query-grammar"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d39c5a03100ac10c96e0c8b07538e2ab8b17da56434ab348309b31f23fada77"
|
||||
checksum = "847434d4af57b32e309f4ab1b4f1707a6c566656264caa427ff4285c4d9d0b82"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tantivy-sstable"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0c1bb43e5e8b8e05eb8009610344dbf285f06066c844032fbb3e546b3c71df"
|
||||
checksum = "c69578242e8e9fc989119f522ba5b49a38ac20f576fc778035b96cc94f41f98e"
|
||||
dependencies = [
|
||||
"tantivy-bitpacker",
|
||||
"tantivy-common",
|
||||
"tantivy-fst",
|
||||
"zstd 0.12.4",
|
||||
"zstd 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tantivy-stacker"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2c078595413f13f218cf6f97b23dcfd48936838f1d3d13a1016e05acd64ed6c"
|
||||
checksum = "c56d6ff5591fc332739b3ce7035b57995a3ce29a93ffd6012660e0949c956ea8"
|
||||
dependencies = [
|
||||
"murmurhash32",
|
||||
"rand_distr",
|
||||
"tantivy-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tantivy-tokenizer-api"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "347b6fb212b26d3505d224f438e3c4b827ab8bd847fe9953ad5ac6b8f9443b66"
|
||||
checksum = "2a0dcade25819a89cfe6f17d932c9cedff11989936bf6dd4f336d50392053b04"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -6852,18 +6961,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.58"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6904,9 +7013,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.34"
|
||||
version = "0.3.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
|
||||
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa 1.0.10",
|
||||
@@ -6925,9 +7034,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.17"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
|
||||
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -7044,16 +7153,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.10"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
|
||||
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hashbrown 0.14.3",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8445,9 +8557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yrs"
|
||||
version = "0.18.8"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da227d69095141c331d9b60c11496d0a3c6505cd9f8e200898b197219e8e394f"
|
||||
checksum = "71df0198938b69f1eec0d5f19f591c6e4f2f770b0bf16f858428f6d91b8bb280"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"atomic_refcell",
|
||||
@@ -8479,6 +8591,26 @@ dependencies = [
|
||||
"syn 2.0.55",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.6"
|
||||
@@ -8488,7 +8620,7 @@ dependencies = [
|
||||
"aes",
|
||||
"byteorder",
|
||||
"bzip2",
|
||||
"constant_time_eq",
|
||||
"constant_time_eq 0.1.5",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
@@ -8499,6 +8631,58 @@ dependencies = [
|
||||
"zstd 0.11.2+zstd.1.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arbitrary",
|
||||
"bzip2",
|
||||
"constant_time_eq 0.3.0",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"deflate64",
|
||||
"displaydoc",
|
||||
"flate2",
|
||||
"hmac",
|
||||
"indexmap 2.2.6",
|
||||
"lzma-rs",
|
||||
"memchr",
|
||||
"pbkdf2 0.12.2",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"time",
|
||||
"zeroize",
|
||||
"zopfli",
|
||||
"zstd 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip-extensions"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb0a99499b3497d765525c5d05e3ade9ca4a731c184365c19472c3fd6ba86341"
|
||||
dependencies = [
|
||||
"zip 2.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"lockfree-object-pool",
|
||||
"log",
|
||||
"once_cell",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.11.2+zstd.1.5.2"
|
||||
@@ -8510,11 +8694,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.12.4"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c"
|
||||
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||
dependencies = [
|
||||
"zstd-safe 6.0.6",
|
||||
"zstd-safe 7.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8529,19 +8713,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "6.0.6"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581"
|
||||
checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.9+zstd.1.5.5"
|
||||
version = "2.0.12+zstd.1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
|
||||
checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
|
||||
@@ -29,7 +29,7 @@ tokio = "1.34.0"
|
||||
tokio-stream = "0.1.14"
|
||||
async-trait = "0.1.74"
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
yrs = "0.18.8"
|
||||
yrs = "0.19.1"
|
||||
# Please use the following script to update collab.
|
||||
# Working directory: frontend
|
||||
#
|
||||
@@ -52,7 +52,7 @@ collab-user = { version = "0.2" }
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "9884d93aa2805013f36a79c1757174a0b5063065" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "eebdbcad79a35b07305affdd36f16d9ce95c5a18" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
@@ -116,18 +116,18 @@ custom-protocol = ["tauri/custom-protocol"]
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
|
||||
# Working directory: frontend
|
||||
# To update the commit ID, run:
|
||||
# scripts/tool/update_local_ai_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "0820a0d23f7b813dee505e7e29e88a8561699fe8" }
|
||||
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "0820a0d23f7b813dee505e7e29e88a8561699fe8" }
|
||||
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "346020270a0a3d3c82a60b545cd3f52144d56beb" }
|
||||
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "346020270a0a3d3c82a60b545cd3f52144d56beb" }
|
||||
|
||||
@@ -513,6 +513,7 @@ export function observeDeepRow(
|
||||
|
||||
export function useRowDataSelector(rowId: string) {
|
||||
const rowMap = useRowDocMap();
|
||||
|
||||
const rowSharedRoot = rowMap?.get(rowId)?.getMap(YjsEditorKey.data_section);
|
||||
const row = rowSharedRoot?.get(YjsEditorKey.database_row);
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ export type ViewMeta = {
|
||||
|
||||
child_views: PublishViewInfo[];
|
||||
ancestor_views: PublishViewInfo[];
|
||||
|
||||
visible_view_ids: string[];
|
||||
} & PublishViewInfo;
|
||||
|
||||
export type ViewMetasTable = {
|
||||
|
||||
Vendored
+3
-3
@@ -9,6 +9,7 @@ import {
|
||||
} from '@/application/services/js-services/cache';
|
||||
import { openCollabDB, db } from '@/application/db';
|
||||
import { StrategyType } from '@/application/services/js-services/cache/types';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
jest.mock('@/application/ydoc/apply', () => ({
|
||||
applyYDoc: jest.fn(),
|
||||
@@ -118,9 +119,8 @@ describe('Cache functions', () => {
|
||||
|
||||
describe('getBatchCollabs', () => {
|
||||
it('should return empty array when no cache found', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(undefined);
|
||||
const collabs = await getBatchCollabs(['1', '2', '3']);
|
||||
expect(collabs).toEqual([]);
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(new Y.Doc());
|
||||
await expect(getBatchCollabs(['1', '2', '3'])).rejects.toThrow('No cache found');
|
||||
});
|
||||
|
||||
it('should return collabs when cache found', async () => {
|
||||
|
||||
+24
-2
@@ -110,6 +110,8 @@ export async function getPublishViewMeta<
|
||||
export async function getPublishView<
|
||||
T extends {
|
||||
data: number[];
|
||||
rows?: Record<string, number[]>;
|
||||
visibleViewIds?: string[];
|
||||
meta: {
|
||||
view: PublishViewInfo;
|
||||
child_views: PublishViewInfo[];
|
||||
@@ -176,12 +178,15 @@ export async function revalidatePublishViewMeta<
|
||||
>(name: string, fetcher: Fetcher<T>) {
|
||||
const { view, child_views, ancestor_views } = await fetcher();
|
||||
|
||||
const dbView = await db.view_metas.get(name);
|
||||
|
||||
await db.view_metas.put(
|
||||
{
|
||||
publish_name: name,
|
||||
...view,
|
||||
child_views: child_views,
|
||||
ancestor_views: ancestor_views,
|
||||
visible_view_ids: dbView?.visible_view_ids ?? [],
|
||||
},
|
||||
name
|
||||
);
|
||||
@@ -193,10 +198,11 @@ export async function revalidatePublishView<
|
||||
T extends {
|
||||
data: number[];
|
||||
rows?: Record<string, number[]>;
|
||||
visibleViewIds?: string[];
|
||||
meta: PublishViewMetaData;
|
||||
}
|
||||
>(name: string, fetcher: Fetcher<T>, collab: YDoc) {
|
||||
const { data, meta, rows } = await fetcher();
|
||||
const { data, meta, rows, visibleViewIds = [] } = await fetcher();
|
||||
|
||||
await db.view_metas.put(
|
||||
{
|
||||
@@ -204,6 +210,7 @@ export async function revalidatePublishView<
|
||||
...meta.view,
|
||||
child_views: meta.child_views,
|
||||
ancestor_views: meta.ancestor_views,
|
||||
visible_view_ids: visibleViewIds,
|
||||
},
|
||||
name
|
||||
);
|
||||
@@ -222,7 +229,22 @@ export async function revalidatePublishView<
|
||||
}
|
||||
|
||||
export async function getBatchCollabs(names: string[]) {
|
||||
const collabs = await Promise.all(names.map((name) => openCollabDB(name)));
|
||||
const getRowDoc = async (name: string) => {
|
||||
const doc = await openCollabDB(name);
|
||||
const exist = hasCollabCache(doc);
|
||||
|
||||
if (!exist) {
|
||||
return Promise.reject(new Error('No cache found'));
|
||||
}
|
||||
|
||||
return doc;
|
||||
};
|
||||
|
||||
const collabs = await Promise.all(
|
||||
names.map((name) => {
|
||||
return getRowDoc(name);
|
||||
})
|
||||
);
|
||||
|
||||
return collabs;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,18 @@ import {
|
||||
} from '@/application/services/js-services/cache';
|
||||
import { StrategyType } from '@/application/services/js-services/cache/types';
|
||||
import { fetchPublishView, fetchPublishViewMeta, fetchViewInfo } from '@/application/services/js-services/fetch';
|
||||
import {
|
||||
initAPIService,
|
||||
signInGoogle,
|
||||
signInWithMagicLink,
|
||||
signInGithub,
|
||||
signInDiscord,
|
||||
signInWithUrl,
|
||||
} from '@/application/services/js-services/wasm/client_api';
|
||||
import { AFService, AFServiceConfig } from '@/application/services/services.type';
|
||||
import { emit, EventType } from '@/application/session';
|
||||
import { afterAuth, AUTH_CALLBACK_URL, withSignIn } from '@/application/session/sign_in';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { initAPIService } from '@/application/services/js-services/wasm/client_api';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export class AFClientService implements AFService {
|
||||
@@ -38,6 +47,10 @@ export class AFClientService implements AFService {
|
||||
});
|
||||
}
|
||||
|
||||
getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
async getPublishViewMeta(namespace: string, publishName: string) {
|
||||
const viewMeta = await getPublishViewMeta(
|
||||
() => {
|
||||
@@ -109,12 +122,13 @@ export class AFClientService implements AFService {
|
||||
}
|
||||
|
||||
const rowsFolder: Y.Map<YDoc> = rootRowsDoc.getMap();
|
||||
const docs = await getBatchCollabs(rowIds);
|
||||
const docs = await getBatchCollabs(rowIds.map((id) => `${name}_${id}`));
|
||||
|
||||
docs.forEach((doc, index) => {
|
||||
rowsFolder.set(rowIds[index], doc);
|
||||
});
|
||||
|
||||
console.log('getPublishDatabaseViewRows', docs);
|
||||
return {
|
||||
rows: rowsFolder,
|
||||
destroy: () => {
|
||||
@@ -149,4 +163,37 @@ export class AFClientService implements AFService {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async loginAuth(url: string) {
|
||||
try {
|
||||
console.log('loginAuth', url);
|
||||
await signInWithUrl(url);
|
||||
emit(EventType.SESSION_VALID);
|
||||
afterAuth();
|
||||
return;
|
||||
} catch (e) {
|
||||
emit(EventType.SESSION_INVALID);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
@withSignIn()
|
||||
async signInMagicLink({ email }: { email: string; redirectTo: string }) {
|
||||
return await signInWithMagicLink(email, AUTH_CALLBACK_URL);
|
||||
}
|
||||
|
||||
@withSignIn()
|
||||
async signInGoogle(_: { redirectTo: string }) {
|
||||
return await signInGoogle(AUTH_CALLBACK_URL);
|
||||
}
|
||||
|
||||
@withSignIn()
|
||||
async signInGithub(_: { redirectTo: string }) {
|
||||
return await signInGithub(AUTH_CALLBACK_URL);
|
||||
}
|
||||
|
||||
@withSignIn()
|
||||
async signInDiscord(_: { redirectTo: string }) {
|
||||
return await signInDiscord(AUTH_CALLBACK_URL);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getToken, invalidToken, isTokenValid, refreshToken } from '@/application/session/token';
|
||||
import { ClientAPI } from '@appflowyinc/client-api-wasm';
|
||||
import { AFCloudConfig } from '@/application/services/services.type';
|
||||
import { PublishViewMetaData } from '@/application/collab.type';
|
||||
import { PublishViewMetaData, ViewLayout } from '@/application/collab.type';
|
||||
|
||||
let client: ClientAPI;
|
||||
|
||||
@@ -14,13 +15,9 @@ export function initAPIService(
|
||||
return;
|
||||
}
|
||||
|
||||
window.refresh_token = () => {
|
||||
//
|
||||
};
|
||||
window.refresh_token = refreshToken;
|
||||
|
||||
window.invalid_token = () => {
|
||||
// invalidToken();
|
||||
};
|
||||
window.invalid_token = invalidToken;
|
||||
|
||||
client = ClientAPI.new({
|
||||
base_url: config.baseURL,
|
||||
@@ -34,16 +31,45 @@ export function initAPIService(
|
||||
},
|
||||
});
|
||||
|
||||
if (isTokenValid()) {
|
||||
client.restore_token(getToken() || '');
|
||||
}
|
||||
|
||||
client.subscribe();
|
||||
}
|
||||
|
||||
export async function getPublishView(publishNamespace: string, publishName: string) {
|
||||
const data = await client.get_publish_view(publishNamespace, publishName);
|
||||
|
||||
return {
|
||||
data: data.data,
|
||||
meta: JSON.parse(data.meta.data) as PublishViewMetaData,
|
||||
};
|
||||
const meta = JSON.parse(data.meta.data) as PublishViewMetaData;
|
||||
|
||||
if (meta.view.layout === ViewLayout.Document) {
|
||||
return {
|
||||
data: data.data,
|
||||
meta,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
const jsonStr = decoder.decode(new Uint8Array(data.data));
|
||||
const res = JSON.parse(jsonStr) as {
|
||||
database_collab: number[];
|
||||
database_row_collabs: Record<string, number[]>;
|
||||
database_row_document_collabs: Record<string, number[]>;
|
||||
visible_database_view_ids: string[];
|
||||
};
|
||||
|
||||
console.log('getPublishView', res);
|
||||
return {
|
||||
data: res.database_collab,
|
||||
rows: res.database_row_collabs,
|
||||
visibleViewIds: res.visible_database_view_ids,
|
||||
meta,
|
||||
};
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPublishInfoWithViewId(viewId: string) {
|
||||
@@ -56,3 +82,33 @@ export async function getPublishViewMeta(publishNamespace: string, publishName:
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export async function signInWithUrl(url: string) {
|
||||
return client.sign_in_with_url(url);
|
||||
}
|
||||
|
||||
export async function signInWithMagicLink(email: string, redirectTo: string) {
|
||||
return client.sign_in_with_magic_link(email, redirectTo);
|
||||
}
|
||||
|
||||
export async function signInGoogle(redirectTo: string) {
|
||||
return signInProvider('google', redirectTo);
|
||||
}
|
||||
|
||||
export async function signInProvider(provider: string, redirectTo: string) {
|
||||
try {
|
||||
const { url } = await client.generate_oauth_url_with_provider(provider, redirectTo);
|
||||
|
||||
window.open(url, '_current');
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function signInGithub(redirectTo: string) {
|
||||
return signInProvider('github', redirectTo);
|
||||
}
|
||||
|
||||
export async function signInDiscord(redirectTo: string) {
|
||||
return signInProvider('discord', redirectTo);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ export interface AFCloudConfig {
|
||||
}
|
||||
|
||||
export interface PublishService {
|
||||
getClientId: () => string;
|
||||
getPublishViewMeta: (namespace: string, publishName: string) => Promise<ViewMeta>;
|
||||
getPublishView: (namespace: string, publishName: string) => Promise<YDoc>;
|
||||
getPublishInfo: (viewId: string) => Promise<{ namespace: string; publishName: string }>;
|
||||
@@ -26,4 +27,10 @@ export interface PublishService {
|
||||
rows: Y.Map<YDoc>;
|
||||
destroy: () => void;
|
||||
}>;
|
||||
|
||||
loginAuth: (url: string) => Promise<void>;
|
||||
signInMagicLink: (params: { email: string; redirectTo: string }) => Promise<void>;
|
||||
signInGoogle: (params: { redirectTo: string }) => Promise<void>;
|
||||
signInGithub: (params: { redirectTo: string }) => Promise<void>;
|
||||
signInDiscord: (params: { redirectTo: string }) => Promise<void>;
|
||||
}
|
||||
@@ -21,4 +21,28 @@ export class AFClientService implements AFService {
|
||||
async getPublishDatabaseViewRows(_namespace: string, _publishName: string, _rowIds: string[]) {
|
||||
return Promise.reject('Method not implemented');
|
||||
}
|
||||
|
||||
getClientId(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
loginAuth(_: string): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
signInDiscord(_params: { redirectTo: string }): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
signInGithub(_params: { redirectTo: string }): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
signInGoogle(_params: { redirectTo: string }): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
signInMagicLink(_params: { email: string; redirectTo: string }): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const event = new EventEmitter();
|
||||
|
||||
export enum EventType {
|
||||
SESSION_EXPIRED = 'session_expired',
|
||||
SESSION_REFRESH = 'session_refresh',
|
||||
SESSION_INVALID = 'session_invalid',
|
||||
SESSION_VALID = 'session_valid',
|
||||
}
|
||||
|
||||
export type Listener<T> = (data: T) => void;
|
||||
|
||||
export function on<T>(eventType: EventType, listener: Listener<T>) {
|
||||
event.on(eventType, listener);
|
||||
|
||||
return () => {
|
||||
event.off(eventType, listener);
|
||||
};
|
||||
}
|
||||
|
||||
export function emit<T>(eventType: EventType, data?: T) {
|
||||
event.emit(eventType, data);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './event';
|
||||
@@ -0,0 +1,51 @@
|
||||
export function saveRedirectTo(redirectTo: string) {
|
||||
localStorage.setItem('redirectTo', redirectTo);
|
||||
}
|
||||
|
||||
export function getRedirectTo() {
|
||||
return localStorage.getItem('redirectTo');
|
||||
}
|
||||
|
||||
export function clearRedirectTo() {
|
||||
localStorage.removeItem('redirectTo');
|
||||
}
|
||||
|
||||
export const AUTH_CALLBACK_PATH = '/auth/callback';
|
||||
export const AUTH_CALLBACK_URL = `${window.location.origin}${AUTH_CALLBACK_PATH}`;
|
||||
|
||||
export function withSignIn() {
|
||||
return function (
|
||||
// eslint-disable-next-line
|
||||
_target: any,
|
||||
_propertyKey: string,
|
||||
descriptor: PropertyDescriptor
|
||||
) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
// eslint-disable-next-line
|
||||
descriptor.value = async function (args: { redirectTo: string }) {
|
||||
const redirectTo = args.redirectTo;
|
||||
|
||||
saveRedirectTo(redirectTo);
|
||||
|
||||
console.log('=====saveRedirectTo', redirectTo);
|
||||
try {
|
||||
await originalMethod.apply(this, [args]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
export function afterAuth() {
|
||||
const redirectTo = getRedirectTo();
|
||||
|
||||
if (redirectTo) {
|
||||
clearRedirectTo();
|
||||
window.location.href = redirectTo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { emit, EventType } from '@/application/session/event';
|
||||
|
||||
export function refreshToken(token: string) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.setItem('token', token);
|
||||
emit(EventType.SESSION_REFRESH, token);
|
||||
}
|
||||
|
||||
export function invalidToken() {
|
||||
localStorage.removeItem('token');
|
||||
emit(EventType.SESSION_INVALID);
|
||||
}
|
||||
|
||||
export function isTokenValid() {
|
||||
return !!localStorage.getItem('token');
|
||||
}
|
||||
|
||||
export function getToken() {
|
||||
return localStorage.getItem('token');
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="login">
|
||||
<g id="login_2">
|
||||
<path id="Vector" d="M11.6406 14.9475L14.5206 12.0675L11.6406 9.1875" stroke="currentColor"
|
||||
stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M3 12.0674H14.4413" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_3" d="M12 3C16.9725 3 21 6.375 21 12C21 17.625 16.9725 21 12 21" stroke="currentColor"
|
||||
stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 793 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16" fill="none">
|
||||
<path d="M16.3916 2.18032C16.3865 2.17042 16.378 2.16267 16.3677 2.15844C15.1764 1.61184 13.9191 1.22207 12.6275 0.998881C12.6158 0.996699 12.6037 0.998273 12.5929 1.00338C12.5821 1.00848 12.5732 1.01686 12.5674 1.02732C12.3962 1.33803 12.2408 1.65719 12.1018 1.98357C10.7095 1.77222 9.29329 1.77222 7.901 1.98357C7.76105 1.65636 7.60315 1.33713 7.42803 1.02732C7.42202 1.01709 7.41307 1.0089 7.40235 1.00383C7.39162 0.99876 7.37962 0.997034 7.3679 0.998881C6.07615 1.2216 4.81885 1.6114 3.62765 2.15847C3.61745 2.1628 3.60885 2.17018 3.60303 2.1796C1.22087 5.73704 0.568308 9.20701 0.888433 12.634C0.889334 12.6424 0.891914 12.6505 0.896021 12.6579C0.900128 12.6653 0.905677 12.6718 0.912339 12.677C2.29945 13.704 3.85094 14.488 5.50062 14.9954C5.51224 14.9989 5.52464 14.9987 5.53617 14.9949C5.5477 14.9912 5.55779 14.9839 5.56509 14.9743C5.9194 14.4922 6.23335 13.9817 6.50375 13.4479C6.50746 13.4406 6.50958 13.4326 6.50997 13.4244C6.51036 13.4162 6.509 13.408 6.50599 13.4004C6.50298 13.3927 6.49839 13.3858 6.49251 13.3801C6.48664 13.3743 6.47961 13.3699 6.4719 13.3671C5.97683 13.1776 5.49754 12.9493 5.03853 12.6842C5.03019 12.6793 5.02319 12.6724 5.01814 12.6641C5.01309 12.6559 5.01015 12.6465 5.00958 12.6369C5.00901 12.6272 5.01082 12.6176 5.01486 12.6088C5.0189 12.6 5.02504 12.5923 5.03275 12.5865C5.12933 12.5143 5.22424 12.44 5.3174 12.3634C5.32557 12.3567 5.33546 12.3524 5.34595 12.351C5.35644 12.3496 5.36712 12.3511 5.37678 12.3554C8.38393 13.7278 11.6396 13.7278 14.6112 12.3554C14.6208 12.3508 14.6316 12.3491 14.6423 12.3504C14.6529 12.3517 14.663 12.3559 14.6713 12.3627C14.7645 12.4396 14.8597 12.5143 14.9567 12.5865C14.9644 12.5923 14.9706 12.5999 14.9747 12.6086C14.9788 12.6174 14.9807 12.627 14.9802 12.6367C14.9797 12.6463 14.9768 12.6557 14.9718 12.664C14.9668 12.6723 14.9599 12.6792 14.9516 12.6842C14.4936 12.9515 14.0139 13.1797 13.5175 13.3663C13.5098 13.3693 13.5028 13.3738 13.497 13.3796C13.4911 13.3855 13.4866 13.3925 13.4836 13.4002C13.4807 13.4079 13.4794 13.4161 13.4799 13.4243C13.4803 13.4326 13.4825 13.4406 13.4863 13.4479C13.7612 13.9787 14.0747 14.4885 14.4242 14.9734C14.4313 14.9834 14.4414 14.9908 14.4529 14.9947C14.4645 14.9987 14.477 14.9989 14.4887 14.9953C16.1413 14.4896 17.6955 13.7056 19.0844 12.677C19.0911 12.672 19.0968 12.6657 19.1009 12.6584C19.105 12.6511 19.1075 12.6431 19.1083 12.6347C19.4915 8.67276 18.4667 5.23122 16.3916 2.18032ZM6.95284 10.5473C6.04746 10.5473 5.30146 9.71647 5.30146 8.6961C5.30146 7.67572 6.033 6.84482 6.95284 6.84482C7.87987 6.84482 8.61865 7.68294 8.60418 8.69604C8.60418 9.71647 7.87262 10.5473 6.95284 10.5473ZM13.0585 10.5473C12.1531 10.5473 11.4071 9.71647 11.4071 8.6961C11.4071 7.67572 12.1387 6.84482 13.0585 6.84482C13.9856 6.84482 14.7243 7.68294 14.7098 8.69604C14.7098 9.71647 13.9856 10.5473 13.0585 10.5473Z"
|
||||
fill="#5865F2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M10.0002 0.83128C7.59472 0.806836 5.27798 1.73845 3.55911 3.42138C1.84023 5.10431 0.85988 7.40085 0.833496 9.80628C0.842765 11.7073 1.45428 13.5564 2.58021 15.0881C3.70615 16.6198 5.28856 17.7552 7.10016 18.3313C7.5585 18.4146 7.72516 18.1396 7.72516 17.8979V16.3729C5.17516 16.9146 4.6335 15.1729 4.6335 15.1729C4.46374 14.6262 4.10286 14.1588 3.61683 13.8563C2.7835 13.3063 3.6835 13.3146 3.6835 13.3146C3.97154 13.3531 4.24712 13.4563 4.48959 13.6165C4.73205 13.7767 4.93509 13.9898 5.0835 14.2396C5.34211 14.688 5.76644 15.017 6.26517 15.1557C6.7639 15.2944 7.29716 15.2318 7.75016 14.9813C7.79675 14.5249 8.00341 14.0998 8.3335 13.7813C6.30016 13.5563 4.16683 12.7896 4.16683 9.34795C4.14865 8.45018 4.48036 7.58054 5.09183 6.92295C4.81335 6.15217 4.84623 5.30321 5.1835 4.55628C5.1835 4.55628 5.9585 4.31461 7.6835 5.47295C9.18507 5.07281 10.7653 5.07281 12.2668 5.47295C14.0168 4.31461 14.7668 4.55628 14.7668 4.55628C15.1041 5.30321 15.137 6.15217 14.8585 6.92295C15.4842 7.56849 15.8339 8.43229 15.8335 9.33128C15.8335 12.7813 13.6835 13.5396 11.6668 13.7646C11.8876 13.979 12.0582 14.2397 12.1663 14.5278C12.2744 14.816 12.3172 15.1246 12.2918 15.4313V17.8896C12.2918 18.1813 12.4585 18.4146 12.9168 18.3229C14.7233 17.7433 16.3004 16.6076 17.4228 15.0781C18.5453 13.5485 19.1557 11.7034 19.1668 9.80628C19.1404 7.40085 18.1601 5.10431 16.4412 3.42138C14.7223 1.73845 12.4056 0.806836 10.0002 0.83128Z"
|
||||
fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
|
||||
<g clip-path="url(#clip0_346_13747)">
|
||||
<path d="M4.68181 9.99793C4.68181 9.36293 4.79014 8.75376 4.98181 8.18293L1.61681 5.66626C0.940723 7.00999 0.589632 8.4937 0.591807 9.99793C0.591807 11.5546 0.96014 13.0229 1.61514 14.3263L4.97847 11.8054C4.78236 11.223 4.68217 10.6125 4.68181 9.99793Z"
|
||||
fill="#FBBC05"/>
|
||||
<path d="M10.5917 4.22046C12 4.22046 13.2725 4.70879 14.2725 5.50879L17.1817 2.66463C15.4092 1.15379 13.1367 0.220459 10.5917 0.220459C6.64003 0.220459 3.24337 2.43296 1.6167 5.66629L4.98337 8.18296C5.75837 5.87796 7.96837 4.22046 10.5917 4.22046Z"
|
||||
fill="#EA4335"/>
|
||||
<path d="M10.5917 15.7755C7.96753 15.7755 5.75753 14.118 4.9817 11.813L1.6167 14.3297C3.24253 17.563 6.6392 19.7755 10.5917 19.7755C13.03 19.7755 15.3584 18.928 17.1067 17.3388L13.9117 14.9205C13.0109 15.4763 11.8759 15.7755 10.5909 15.7755"
|
||||
fill="#34A853"/>
|
||||
<path d="M20.1367 9.99796C20.1367 9.42046 20.045 8.79796 19.9092 8.22046H10.5908V11.998H15.9542C15.6867 13.2863 14.9567 14.2763 13.9125 14.9205L17.1067 17.3388C18.9425 15.6705 20.1367 13.1855 20.1367 9.99796Z"
|
||||
fill="#4285F4"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_346_13747">
|
||||
<rect width="20" height="20" fill="white" transform="translate(0.5 -0.0020752)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -1,4 +1,7 @@
|
||||
import { AUTH_CALLBACK_PATH } from '@/application/session/sign_in';
|
||||
import NotFound from '@/components/error/NotFound';
|
||||
import LoginAuth from '@/components/login/LoginAuth';
|
||||
import LoginPage from '@/pages/LoginPage';
|
||||
import PublishPage from '@/pages/PublishPage';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
import withAppWrapper from '@/components/app/withAppWrapper';
|
||||
@@ -8,6 +11,8 @@ const AppMain = withAppWrapper(() => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={'/:namespace/:publishName'} element={<PublishPage />} />
|
||||
<Route path={'/login'} element={<LoginPage />} />
|
||||
<Route path={AUTH_CALLBACK_PATH} element={<LoginAuth />} />
|
||||
<Route path='/404' element={<NotFound />} />
|
||||
<Route path='*' element={<NotFound />} />
|
||||
</Routes>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { EventType, on } from '@/application/session';
|
||||
import { isTokenValid } from '@/application/session/token';
|
||||
import { useAppLanguage } from '@/components/app/useAppLanguage';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import React, { createContext, useEffect, useState } from 'react';
|
||||
@@ -19,6 +21,7 @@ const defaultConfig: AFServiceConfig = {
|
||||
export const AFConfigContext = createContext<
|
||||
| {
|
||||
service: AFService | undefined;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
| undefined
|
||||
>(undefined);
|
||||
@@ -26,7 +29,29 @@ export const AFConfigContext = createContext<
|
||||
function AppConfig({ children }: { children: React.ReactNode }) {
|
||||
const [appConfig] = useState<AFServiceConfig>(defaultConfig);
|
||||
const [service, setService] = useState<AFService>();
|
||||
const [isAuthenticated, setIsAuthenticated] = React.useState<boolean>(isTokenValid());
|
||||
|
||||
useEffect(() => {
|
||||
return on(EventType.SESSION_VALID, () => {
|
||||
setIsAuthenticated(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleStorageChange = (event: StorageEvent) => {
|
||||
if (event.key === 'token') setIsAuthenticated(isTokenValid());
|
||||
};
|
||||
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
return () => {
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
return on(EventType.SESSION_INVALID, () => {
|
||||
setIsAuthenticated(false);
|
||||
});
|
||||
}, []);
|
||||
useAppLanguage();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -67,6 +92,7 @@ function AppConfig({ children }: { children: React.ReactNode }) {
|
||||
<AFConfigContext.Provider
|
||||
value={{
|
||||
service,
|
||||
isAuthenticated,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { YDatabase, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
|
||||
import { ViewMeta } from '@/application/db/tables/view_metas';
|
||||
import ComponentLoading from '@/components/_shared/progress/ComponentLoading';
|
||||
import DatabaseHeader from '@/components/database/components/header/DatabaseHeader';
|
||||
import DatabaseRow from '@/components/database/DatabaseRow';
|
||||
import DatabaseViews from '@/components/database/DatabaseViews';
|
||||
import { ViewMetaPreview, ViewMetaProps } from '@/components/view-meta/ViewMetaPreview';
|
||||
import { ViewMetaProps } from '@/components/view-meta/ViewMetaPreview';
|
||||
import React, { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import * as Y from 'yjs';
|
||||
@@ -27,7 +28,7 @@ function Database({ doc, getViewRowsMap, navigateToView, loadViewMeta, loadView,
|
||||
const database = doc.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database) as YDatabase;
|
||||
const rows = database.get(YjsDatabaseKey.views).get(viewId).get(YjsDatabaseKey.row_orders);
|
||||
|
||||
return rows.toArray().map((row) => row.get(YjsDatabaseKey.id));
|
||||
return rows.toJSON().map((row) => row.id);
|
||||
}, [doc, viewId]);
|
||||
|
||||
const iidIndex = useMemo(() => {
|
||||
@@ -70,7 +71,12 @@ function Database({ doc, getViewRowsMap, navigateToView, loadViewMeta, loadView,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'flex w-full justify-center'}>
|
||||
<div
|
||||
style={{
|
||||
height: 'calc(100vh - 48px)',
|
||||
}}
|
||||
className={'flex w-full justify-center'}
|
||||
>
|
||||
<Suspense fallback={<ComponentLoading />}>
|
||||
<DatabaseContextProvider
|
||||
isDatabaseRowPage={!!rowId}
|
||||
@@ -87,10 +93,15 @@ function Database({ doc, getViewRowsMap, navigateToView, loadViewMeta, loadView,
|
||||
<DatabaseRow rowId={rowId} />
|
||||
) : (
|
||||
<div className={'relative flex h-full w-full flex-col'}>
|
||||
{viewMeta && <ViewMetaPreview {...viewMeta} />}
|
||||
{viewMeta && <DatabaseHeader {...viewMeta} />}
|
||||
|
||||
<div className='appflowy-database relative flex w-full flex-1 select-text flex-col overflow-y-hidden'>
|
||||
<DatabaseViews iidIndex={iidIndex} onChangeView={handleChangeView} viewId={viewId} />
|
||||
<DatabaseViews
|
||||
iidIndex={iidIndex}
|
||||
viewName={viewMeta.name}
|
||||
onChangeView={handleChangeView}
|
||||
viewId={viewId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Loaded 100 of 164 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user